E3CommandParser: Unterschied zwischen den Versionen

Aus Eressea
Zur Navigation springenZur Suche springen
(Update des E3CommandParsers)
(neue Version)
 
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt)
Zeile 15: Zeile 15:
E3CommandParser.DEFAULT_SUPPLY_PRIORITY = 0;
E3CommandParser.DEFAULT_SUPPLY_PRIORITY = 0;


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


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


// falls während der Ausführung der Kommandos ein Fehler auftreten sollte, kann man nach dessen Behebung direkt so bei der Region weitermachen, wo der Fehler auftrat:
// falls während der Ausführung der Kommandos ein Fehler auftreten sollte, kann man nach dessen Behebung direkt so bei der Region weitermachen, wo der Fehler auftrat:
Zeile 125: Zeile 126:
== E3CommandParser ==
== E3CommandParser ==


Und hier das komplette Skript. Es muss zunächst in die "Library" der ExtendedCommands kopiert werden wie oben beschrieben.   
Und hier das komplette Skript in einer alten aber funktionierenden Version (neueste Version [https://github.com/magellan2/magellan2/tree/master/src-test/magellan/plugin/extendedcommands/scripts bei Github]). Es muss zunächst in die "Library" der ExtendedCommands kopiert werden wie oben beschrieben.   


   <nowiki>
   <nowiki>
// created by magellan.plugin.BeanShellCleaner at Fri Jan 08 09:35:32 CET 2016
// from file ./src-test/magellan/plugin/extendedcommands/scripts/E3CommandParser.java
// Author : stm
// Author : stm
//
//
Zeile 144: Zeile 142:


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


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


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


Zeile 205: Zeile 223:
  * @author stm
  * @author stm
  */
  */
public class E3CommandParser {
class E3CommandParser {
 
  static class Flag {
    boolean positive;
    String name;
    int value;
 
    public Flag(boolean positive, String name, int value) {
      this.positive = positive;
      this.name = name;
      this.value = value;
    }
 
    @Override
    public String toString() {
      return name + "(" + positive + "," + value + ")";
    }


   /**
   }
  * 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. */
   static class Warning {
  public static int UNIT_LIMIT = 250;
    // warning constants
    /** The NEVER warning type token */
    public static String W_NEVER = "nie";
    /** The SKILL warning type token */
    public static String W_SKILL = "Talent";
    /** The WEAPON warning type token */
    public static String W_WEAPON = "Waffe";
    /** The SHIELD warning type token */
    public static String W_SHIELD = "Schilde";
    /** The ARMOR warning type token */
    public static String W_ARMOR = "Rüstung";
    /** The UNIT warning type token */
    public static final String W_UNIT = "Einheit";
    /** The AMOUNT warning type token */
    public static final String W_AMOUNT = "Menge";
    /** The HIDDEN warning type token */
    public static String W_HIDDEN = "versteckt";
    /** The ALWAYS warning type token */
    public static final String W_ALWAYS = "immer";
    /** The ALWAYS warning type token */
    public static final String W_FOREIGN = "fremd";


  /** If this is true, some more hints will be added to the orders if expected units are missing */
    /** warning constants */
  public static boolean ADD_NOT_THERE_INFO = false;
    protected static final int C_AMOUNT = 1, C_UNIT = 1 << 1, C_HIDDEN = 1 << 2, C_FOREIGN = 1 << 3,
        C_WEAPON = 1 << 5, C_ARMOR = 1 << 6, C_SHIELD = 1 << 7, C_SKILL = 1 << 8;


  /**
    private List<Flag> flags = new ArrayList<Flag>();
  * 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 */
    public static final Flag[] ALL_FLAGS = new Flag[8];
  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. */
    private static void initFlags() {
  public static final String scriptMarker = "$cript";
      ALL_FLAGS[0] = new Flag(true, W_AMOUNT, C_AMOUNT);
  /** The GIVE order */
      ALL_FLAGS[1] = new Flag(true, W_ARMOR, C_ARMOR);
  public static String GIVEOrder = "GIB";
      ALL_FLAGS[2] = new Flag(false, W_FOREIGN, C_FOREIGN);
  /** The RESERVE order */
      ALL_FLAGS[3] = new Flag(false, W_HIDDEN, C_HIDDEN);
  public static String RESERVEOrder = "RESERVIERE";
      ALL_FLAGS[4] = new Flag(true, W_SHIELD, C_SHIELD);
  /** The EACH order parameter */
      ALL_FLAGS[5] = new Flag(true, W_SKILL, C_SKILL);
  public static String EACHOrder = "JE";
      ALL_FLAGS[6] = new Flag(true, W_UNIT, C_UNIT);
  /** The ALL order parameter */
      ALL_FLAGS[7] = new Flag(true, W_WEAPON, C_WEAPON);
  public static String ALLOrder = "ALLES";
    }
  /** The KRÄUTER order parameter */
 
  public static String KRAUTOrder = "KRAUT";
    public static boolean initialized = false;
  /** The LUXUS order parameter */
    public static Map<String, Flag> NAMES;
  public static String LUXUSOrder = "LUXUS";
 
  /** The LUXUS order parameter */
    public Warning(boolean all) {
  public static String TRANKOrder = "TRANK";
      this(new String[] { W_NEVER });
  /** The persistent comment order */
      if (all) {
  public static String PCOMMENTOrder = EresseaConstants.O_PCOMMENT;
        setAll();
  /** The persistent comment order */
      }
  public static String COMMENTOrder = EresseaConstants.O_COMMENT;
    }
  /** The LEARN order */
 
  public static String LEARNOrder = "LERNE";
    public Warning(String[] tokens) {
  /** The TEACH order */
      /** Bean Shell does not know static initializers, so this is a bit awkward */
  public static String TEACHOrder = "LEHRE";
      if (!initialized) {
  /** The ENTERTAIN order */
        initStatic();
  private static String ENTERTAINOrder = "UNTERHALTE";
      }
  /** The TAX order */
      parse(tokens);
  private static String TAXOrder = "TREIBE";
    }
  /** The WORK order */
 
  private static String WORKOrder = "ARBEITE";
    protected void initStatic() {
  /** The BUY order */
      initialized = true;
  private static String BUYOrder = "KAUFE";
      initFlags();
  /** The SELL order */
      NAMES = new HashMap<String, Flag>();
  private static String SELLOrder = "VERKAUFE";
      for (Flag f : ALL_FLAGS) {
  /** The MAKE order */
        NAMES.put(f.name, f);
  private static String MAKEOrder = "MACHE";
      }
  /** The NACH order */
    }
  private static String MOVEOrder = "NACH";
 
  /** The ROUTE order */
    protected void setAll() {
  private static String ROUTEOrder = "ROUTE";
      for (Flag f : ALL_FLAGS) {
  /** The PAUSE order */
        add(f);
  private static String PAUSEOrder = "PAUSE";
      }
  /** The RESEARCH order */
    }
  private static String RESEARCHOrder = "FORSCHE";
 
  /** The RECRUIT order */
    private void setAll(boolean positive) {
  private static String RECRUITOrder = "REKRUTIERE";
      for (Flag f : ALL_FLAGS) {
        if (f.positive == positive) {
          add(f);
        }
      }
    }
 
    protected String[] parse(String[] tokens) {
      flags = new ArrayList<Flag>();
      if (tokens == null)
        return null;


  // warning constants
      if (tokens.length == 0) {
  /** The NEVER warning type token */
        for (Flag f : ALL_FLAGS) {
  public static String W_NEVER = "nie";
          add(f);
  /** The SKILL warning type token */
        }
  public static String W_SKILL = "Talent";
      }
  /** The WEAPON warning type token */
  public static String W_WEAPON = "Waffe";
  /** 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) */
      int i = tokens.length - 1;
  public static String LONG = "$lang";
      boolean hasPositive = false;
  /** The SHORT token (for clear) */
      setAll(false);
  public static String SHORT = "$kurz";
      for (; i >= 0; --i)
  /** The COMMENT token (for clear) */
        if (NAMES.containsKey(tokens[i])) {
  public static String COMMENT = "$kommentar";
          Flag f = NAMES.get(tokens[i]);
          if (f.positive) {
            hasPositive = true;
            add(f);
          } else {
            remove(f);
          }
        } else if (W_ALWAYS.equals(tokens[i])) {
          flags = new ArrayList<Flag>();
          setAll();
        } else if (W_NEVER.equals(tokens[i])) {
          flags = new ArrayList<Flag>();
          hasPositive = true;
        } else {
          break;
        }
      if (!hasPositive) {
        setAll(true);
      }


  private static String S_ENDURANCE = EresseaConstants.S_AUSDAUER.toString();
      if (i == tokens.length - 1)
        return tokens;
      return Arrays.copyOf(tokens, i + 1);
    }


  /** warning constants */
    public void add(String name) {
  protected static final int C_AMOUNT = 1, C_UNIT = 1 << 1, C_HIDDEN = 1 << 2, C_FOREIGN = 1 << 3,
      if (NAMES.containsKey(name)) {
       C_WEAPON = 1 << 5, C_ARMOR = 1 << 6, C_SHIELD = 1 << 7, C_SKILL = 1 << 8;
        add(NAMES.get(name));
       }
    }


  private OrderParser parser;
    public void add(Flag f) {
  private Logger log;
      if (!flags.contains(f)) {
        flags.add(f);
      }
    }


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


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


  // variables available in scripts; this is here mainly to be able to test this class outside
    public boolean contains(int flag) {
  // BeanShell
      for (Flag f : flags)
  static GameData world;
        if ((f.value & flag) != 0)
  static ExtendedCommandsHelper helper;
          return true;
      return false;
    }
 
    @Override
    public String toString() {
      return flags.toString();
    }


   private Unit someUnit;
   }


   // variables for current script state
   static class SupplyMap {
//  private Map<Faction, Integer> currentFactions = CollectionFactory.createOrderedMap();
    Map<String, Map<Unit, Supply>> supplyMap;
  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;


  /**
    public SupplyMap(E3CommandParser parser) {
  * The item/unit/need map. Stores all needed items.
      supplyMap = new LinkedHashMap<String, Map<Unit, Supply>>();
  */
    }
//  protected Map<String, Map<Integer, Map<Unit, Need>>> needMap;
  protected Map needMap;


  /**
    public Collection<String> items() {
  * The item/unit/supply map. Stores all available items.
      return supplyMap.keySet();
  */
    }
//  protected Map<String, Map<Unit, Supply>> supplyMap;
  protected Map supplyMap;


  /**
    public int getSupply(String item) {
  * Lines matching these patterns should be removed.
      Map<Unit, Supply> map = supplyMap.get(item);
  */
      int goodAmount = 0;
//  protected List<String> removedOrderPatterns;
      if (map != null) {
  protected List removedOrderPatterns;
        for (Supply s : map.values()) {
          if (s.hasPriority()) {
            goodAmount += s.getAmount();
          }
        }
      }
      return goodAmount;
    }


  /**
    public Supply get(String item, Unit unit) {
  * Lists of allowed units for Erlaube/Ueberwache
      Map<Unit, Supply> map = supplyMap.get(item);
  */
      if (map == null)
//  protected Map<Faction, Set<UnitID>> allowedUnits = new HashMap<Faction, Set<UnitID>>();
        return null;
  protected Map allowedUnits = new HashMap();
      return map.get(unit);
  /**
    }
  * Lists of required units for Verlange/Ueberwache
  */
//  protected Map<Faction, Set<UnitID>> requiredUnits = new HashMap<Faction, Set<UnitID>>();
  protected Map requiredUnits = new HashMap();


  /**
    public Collection<Supply> get(String item) {
  * Current state for the Loesche command
      Map<Unit, Supply> result = supplyMap.get(item);
  */
      if (result == null)
  protected String clear = null;
        return Collections.emptyList();
  /** Current prefix for the Loesche command */
      return result.values();
  protected String clearPrefix = "";
    }
 
    public void sortByPriority() {
      for (String item : supplyMap.keySet()) {
        // sort supplies by priority
        Map<Unit, Supply> itemSupply = supplyMap.get(item);
        if (itemSupply == null) {
          itemSupply = Collections.emptyMap();
        } else {
          Supply[] sorted = null;
          sorted = itemSupply.values().toArray(new Supply[0]);
 
          // this causes problems in BeansShell; I don't know why
          // Arrays.sort(sorted);
          // doing insertion sort instead
          sort(sorted);


  private int progress = -1;
          itemSupply.clear();
          for (Supply supply : sorted) {
            itemSupply.put(supply.getUnit(), supply);
          }
        }
      }
    }


  /**
    private void sort(Supply[] sorted) {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
      for (int j = 1; j < sorted.length; ++j) {
  * container to execute all unit commands.
        for (int i = 0; i < j; ++i) {
  *
          if (sorted[j].compareTo(sorted[i]) < 0) {
  * @param faction scripts for all units of this faction
            // if (current.priority > sorted[i].priority) {
  * @throws NullPointerException if <code>faction == null</code>
            Supply temp = sorted[i];
  */
            sorted[i] = sorted[j];
  public void execute(Faction faction) {
            sorted[j] = temp;
    executeFrom(Collections.singleton(faction), null, null);
          }
  }
        }
      }
    }


  /**
    public Supply put(String item, Unit unit, int amount, long serial) {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
      Map<Unit, Supply> itemSupplyMap = supplyMap.get(item);
  * container to execute all unit commands.
      if (itemSupplyMap == null) {
  *
        itemSupplyMap = new LinkedHashMap<Unit, Supply>();
  * @param factions scripts for all units of all factions in this set
        supplyMap.put(item, itemSupplyMap);
  * @throws NullPointerException if <code>faction == null</code>
      }
  */
      Supply result = new Supply(unit, item, amount, serial);
//  public void execute(Collection<Faction> factions) {
      itemSupplyMap.put(unit, result);
  public void execute(Collection factions) {
      return result;
    executeFrom(factions, null, null);
    }
  }


  /**
    public void clear() {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
       supplyMap.clear();
  * 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);
     @Override
    public String toString() {
      return supplyMap.toString();
    }
   }
   }


   /**
   interface ReserveVisitor {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
    public void execute(Unit u, String item, int amount);
  * 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);
   }
   }


   /**
   static class Reserves {
  * Parses scripts and confirms units according to the "confirm" tag. Ignore regions before first
 
  * (in the report order).
    Map<String, Map<Unit, Integer>> reserves = new HashMap<String, Map<Unit, Integer>>();
  *
  * @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);
     public void add(String item, Unit unit, int amount) {
    for (Faction f : factions) {
       E3CommandParser.increaseMultiInv(reserves, item, unit, amount);
       currentFactions.put(f, null);
     }
     }


     helper.getUI().setMaximum(world.getRegions().size() + 4);
     public void execute(ReserveVisitor reserveVisitor) {
    helper.getUI().setProgress("init", ++progress);
      for (String item : reserves.keySet()) {
        Map<Unit, Integer> iMap = reserves.get(item);
        for (Unit u : iMap.keySet()) {
          reserveVisitor.execute(u, item, iMap.get(u));
        }
 
      }
    }
  }


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


     findSomeUnit(currentFactions);
     public SkillType skill;
    public int level;
    public int max;


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


     for (Faction faction : factions) {
     @Override
       if (faction.units().size() >= UNIT_LIMIT) {
    public String toString() {
        addWarning(someUnit, "Einheitenlimit erreicht (" + faction.units().size() + "/" + UNIT_LIMIT
       return skill.getName() + " " + level + "/" + max;
            + ")! ");
      } else if (faction.units().size() * 1.1 > UNIT_LIMIT) {
        addWarning(someUnit, "Einheitenlimit fast erreicht (" + faction.units().size() + "/"
            + UNIT_LIMIT + ")! ");
      }
     }
     }
  }


     collectStats();
  public static class ItemSorter implements Comparator<Item> {
 
     private String[] priorities;
 
    public ItemSorter(String[] priorities) {
      this.priorities = priorities;
    }


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


    // comment out the following line if you don't have the newest nightly build of Magellan
    helper.getUI().setProgress("postprocessing", ++progress);
    for (Faction faction : factions) {
      for (Unit u : faction.units()) {
        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.
   * A standard soldier's endurance skill should be this fraction of his (first row) weapon skill
  *
  * @return Returns currentRegion.
   */
   */
   private Region getCurrentRegion() {
   public static float ENDURANCERATIO_FRONT = .6f;
    return currentRegion;
  }
 
   /**
   /**
   * Sets the value of currentRegion.
   * A standard soldier's endurance skill should be this fraction of his (second row) weapon skill
  *
  * @param currentRegion The value for currentRegion.
   */
   */
   private void setCurrentRegion(Region currentRegion) {
   public static float ENDURANCERATIO_BACK = .35f;
    this.currentRegion = currentRegion;
 
   }
  /** Unit limit, used to warn if we get too many units. */
  public static int UNIT_LIMIT = 250;
 
  /** If this is true, some more hints will be added to the orders if expected units are missing */
  public static boolean ADD_NOT_THERE_INFO = false;
 
  /** Set to something in order to use TeachPlugin */
  public static String TEACH_PREFIX = null;
   /** The skills learned by helmspeople, see {@link #commandHelmsman(String[])}. */
  public static String LEARN_HELMSMAN = "$tm$ 100.0 Segeln 25 12 Schiffbau 8 4 Unterhaltung 6 5";


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


   /**
   /**
   * Sets the value of currentUnit.
   * If this is &gt; 0, all units are suppliers, otherwise suppliers must be set with Versorge (the
   *
   * default)
  * @param currentUnit The value for currentUnit.
   */
   */
   private void setCurrentUnit(Unit currentUnit) {
   public static int DEFAULT_SUPPLY_PRIORITY = 0;
    this.currentUnit = currentUnit;
  }


//  private void findSomeUnit(Map<Faction, Integer> factions) {
  /** default need priority */
   private void findSomeUnit(Map factions) {
   private static final int DEFAULT_PRIORITY = 100;
    // sometimes we need an arbitrary unit. This is a shorthand for it.
  /** need priority for GibWenn command */
    for (Faction faction : factions.keySet())
   private static final int GIVE_IF_PRIORITY = 999999;
      if (!faction.units().isEmpty()) {
  /** need priority for Depot command and silver */
        someUnit = faction.units().iterator().next();
  private static final int DEPOT_SILVER_PRIORITY = 150;
        break;
  /** 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;


    if (someUnit == null)
  /** All script commands begin with this text. */
      throw new RuntimeException("No units in report!");
  public static final String scriptMarker = "$cript";


    setCurrentUnit(someUnit);
  /** The GIVE order */
   }
  public static String GIVEOrder = "GIB";
 
  /** The RESERVE order */
   protected void execute(Region region) {
  public static String RESERVEOrder = "RESERVIERE";
    try {
  /** The EACH order parameter */
      setCurrentRegion(region);
  public static String EACHOrder = "JE";
      initSupply();
  /** The ALL order parameter */
 
  public static String ALLOrder = "ALLES";
      for (Unit u : region.units()) {
   /** The KRÄUTER order parameter */
        if (currentFactions.containsKey(u.getFaction())) {
  public static String KRAUTOrder = "KRAUT";
          setCurrentUnit(u);
  /** The LUXUS order parameter */
          // comment out the following line if you don't have the newest nightly build of Magellan
   public static String LUXUSOrder = "LUXUS";
          helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
  /** The LUXUS order parameter */
 
  public static String TRANKOrder = "TRANK";
          try {
  /** The "on foot" order */
            parseScripts();
  public static String FOOTOrder = "FUSS";
          } catch (RuntimeException e) {
  /** The "on horse" order */
            addWarning(u, "error " + e.getClass().getSimpleName());
  public static String HORSEOrder = "PFERD";
            log.error(e.getClass().getSimpleName() + " script error for " + u);
  /** The "on ship" order */
            throw new RuntimeException("script error for " + u, e);
  public static String SHIPOrder = "SCHIFF";
          }
  /** The item "horses" */
        }
  public static String HORSEItem = "Pferd";
      }
  /** The persistent comment order */
      // comment out the following line if you don't have the newest nightly build of Magellan
  public static String PCOMMENTOrder = EresseaConstants.O_PCOMMENT;
      helper.getUI().setProgress(region.toString() + " - postprocessing", ++progress);
  /** The persistent comment order */
 
  public static String COMMENTOrder = EresseaConstants.O_COMMENT;
      satisfyNeeds();
  /** The LEARN order */
    } catch (RuntimeException e) {
  public static String LEARNOrder = "LERNE";
      e.printStackTrace();
  /** The (TEACH) AUTO order */
      // log.error(e.getClass().getSimpleName() + " script error for " + region);
  public static String AUTOOrder = "AUTO";
      throw new RuntimeException("script error for " + region, e);
  /** The TEACH order */
    }
  public static String TEACHOrder = "LEHRE";
    // refresh relations, just in case
  /** The ENTERTAIN order */
    region.refreshUnitRelations(true);
  private static String ENTERTAINOrder = "UNTERHALTE";
   }
  /** The TAX order */
  private static String TAXOrder = "TREIBE";
  /** The WORK order */
  private static String WORKOrder = "ARBEITE";
  /** The BUY order */
  private static String BUYOrder = "KAUFE";
  /** The SELL order */
  private static String SELLOrder = "VERKAUFE";
  /** The MAKE order */
  private static String MAKEOrder = "MACHE";
  /** The NACH order */
  private static String MOVEOrder = "NACH";
  /** The ROUTE order */
  private static String ROUTEOrder = "ROUTE";
  /** The PAUSE order */
  private static String PAUSEOrder = "PAUSE";
  /** The RESEARCH order */
  private static String RESEARCHOrder = "FORSCHE";
  /** The RECRUIT order */
  private static String RECRUITOrder = "REKRUTIERE";
 
  /** The BEST token (for soldier) */
  public static String BEST = "best";
  /** The NULL token (for soldier) */
  public static String NULL = "null";
   /** The NOT token (for auto and others) */
  public static String NOT = "NICHT";


   /**
   /** The LONG token (for clear) */
  * Adds some statistic information to the orders of the first unit.
  public static String LONG = "$lang";
  *
  /** The SHORT token (for clear) */
  * @param region
   public static String SHORT = "$kurz";
  */
  /** The COMMENT token (for clear) */
   protected void collectStats() {
  public static String COMMENT = "$kommentar";
    int buildingScripts = 0;
 
    int shipScripts = 0;
  private static String S_ENDURANCE = EresseaConstants.S_AUSDAUER.toString();
    int unitScripts = 0;
 
    int regionScripts = 0;
  private static final int TEACH_MULTI = 10;
    // comment out the following lines if you don't have the newest nightly build of Magellan
 
    for (Building b : world.getBuildings()) {
  private String[] WEAPON_PRIORITIES =
       if (helper.hasScript(b)) {
       new String[] {
        buildingScripts++;
          "Adamantiumaxt",
      }
          "Laenschwert",
    }
          "Kriegsaxt",
    for (Ship s : world.getShips()) {
          "Bihänder",
      if (helper.hasScript(s)) {
          "Schwert",
        shipScripts++;
          "Rostige~Kriegsaxt",
       }
          "Rostiger~Bihänder",
    }
          "Schartiges~Schwert",
    for (Unit u : world.getUnits()) {
          "Hellebarde",
      if (helper.hasScript(u)) {
          "Mallornspeer",
        u.addOrder(COMMENTOrder + " hat Skript", false);
          "Speer",
        unitScripts++;
          "Rostige~Hellebarde",
       }
          "Mallornlanze",
    }
          "Lanze",
    for (Region r : world.getRegions()) {
          "Mallornarmbrust",
      if (helper.hasScript(r)) {
          "Armbrust",
        regionScripts++;
          "Elfenbogen",
       }
          "Mallornbogen",
    }
          "Bogen"
       };
  private String[] SHIELD_PRIORITIES =
      new String[] {
          "Laenschild",
          "Schild",
          "Rostiger~Schild"
       };
  private String[] ARMOR_PRIORITIES =
      new String[] {
          "Adamantiumrüstung",
          "Laenkettenhemd",
          "Plattenpanzer",
          "Kettenhemd",
          "Rostiges~Kettenhemd"
       };


    someUnit.addOrderAt(0, "; " + unitScripts + " unit scripts, " + buildingScripts
  private OrderParser parser;
        + " building scripts, " + shipScripts + " ship scripts, " + regionScripts
  private Logger log;
        + " region scripts", true);
  }


   /**
   /**
   * Parses the orders of the unit u for commands of the form "// $cript ..." and tries to execute
   * Creates and initializes the parser.
  * them. Known commands:<br />
   *
  * <tt>// $cript +X text</tt> -- If X<=1 then a warning containing text is added to the unit's
   * @param world
  * orders. Otherwise X is decreased by one.<br />
   * @param helper
  * <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() {
   public E3CommandParser(GameData world, ExtendedCommandsHelper helper) {
//     newOrders = new ArrayList<String>();
     E3CommandParser.world = world;
     newOrders = new ArrayList();
     E3CommandParser.helper = helper;
//     removedOrderPatterns = new ArrayList<String>();
     parser = world.getGameSpecificStuff().getOrderParser(world);
    removedOrderPatterns = new ArrayList();
     log = Logger.getInstance("E3CommandParser");
     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!
  protected OrderParser getParser() {
    for (Order o : getCurrentUnit().getOrders2()) {
    return parser;
      ++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")) {
  // variables available in scripts; this is here mainly to be able to test this class outside
              commandClear(tokens);
  // BeanShell
            } else if (command.equals("GibWenn")) {
  static GameData world;
              commandGiveIf(tokens);
  static ExtendedCommandsHelper helper;
            } 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;


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


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


   /**
   /**
   * If order is a script command ("// $cript ..."), returns a List of the tokens. Otherwise returns
   * The item/unit/need map. Stores all needed items.
   * <code>null</code>. The first in the list is the first token after the "// $cript".
   */
  protected List<Need> needQueue;
  /**
  * The item/unit/supply map. Stores all available items.
   */
   */
   protected String[] detectScriptCommand(String order) {
   protected SupplyMap supplyMapp;
    StringTokenizer tokenizer = new StringTokenizer(order, " ");
 
    if (tokenizer.hasMoreTokens()) {
  protected Map<Unit, Integer> capacities;
      String part = tokenizer.nextToken();
 
      if (part.equals(PCOMMENTOrder) || part.equals(COMMENTOrder)) {
  private Map<String, Unit> dummyUnits;
        if (tokenizer.hasMoreTokens()) {
  private Map<Unit, Map<String, Integer>> transfersMap;
          part = tokenizer.nextToken();
  private List<Transfer> transferList;
          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 />
   * Lines matching these patterns should be removed.
   * 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.
  protected List<String> removedOrderPatterns;
  *
 
   * @param tokens
  /**
  * Lists of allowed units for Erlaube/Ueberwache
  */
  protected Map<Faction, Set<UnitID>> allowedUnits = new HashMap<Faction, Set<UnitID>>();
  /**
   * Lists of required units for Verlange/Ueberwache
   */
  protected Map<Faction, Set<UnitID>> requiredUnits = new HashMap<Faction, Set<UnitID>>();
 
  /**
   * Current state for the Loesche command
   */
   */
   protected void commandClear(String[] tokens) {
   protected String clear = null;
    clearPrefix = "";
  /** Current prefix for the Loesche command */
    if (tokens.length == 1) {
  protected String clearPrefix = "";
      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) {
   private int progress = -1;
    if (clear == null)
  private long supplySerial = 0;
      return false;
  private int showStats = 1;
    String trimmed = order.trim();
  private String cachedScriptCommand;
  private Map<Unit, Collection<SkillSpec>> skills = new HashMap<Unit, Collection<SkillSpec>>();
  private Map<String, Consumer<String[]>> commands;


     return !trimmed.startsWith(PCOMMENTOrder) && !trimmed.startsWith(COMMENTOrder)
  public int getShowStats() {
        && (clear == ALLOrder || (clear == LONG && world.getGameSpecificStuff().getOrderChanger()
     return showStats;
            .isLongOrder(order))) && (clearPrefix == null || clearPrefix.length() == 0 || trimmed
  }
                .startsWith(clearPrefix));
 
  public void setShowStats(int showStats) {
    this.showStats = showStats;
  }
 
  /**
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
  * container to execute all unit commands.
  *
  * @param faction scripts for all units of this faction
  * @throws NullPointerException if <code>faction == null</code>
  */
  public void execute(Faction faction) {
    executeFrom(Collections.singleton(faction), null, null);
   }
   }


   /**
   /**
   * <code>// $cript [rest [period [length]]] text</code><br />
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
  * Adds text (or commands) to the orders after rest rounds. If <code>rest==1</code>, text is added
   * container to execute all unit commands.
   * 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
   * @param factions scripts for all units of all factions in this set
   * @return <code>text</code>, if <code>rest==1</code>, otherwise <code>null</code>
   * @throws NullPointerException if <code>faction == null</code>
   */
   */
   protected String commandRepeat(String[] tokens) {
   public void execute(Collection<Faction> factions) {
     StringBuilder result = null;
     executeFrom(factions, null, null);
    try {
  }
      int rest = Integer.parseInt(tokens[0]);
 
      int period = 0;
  /**
      int length = Integer.MAX_VALUE;
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
      int textIndex = 1;
  * container to execute all unit commands.
      if (tokens.length >= 2) {
  *
        try {
  * @param faction scripts for all units of this faction are executed
          period = Integer.parseInt(tokens[1]);
  * @param region only commands of unit in this region are executed
          textIndex = 2;
  * @throws NullPointerException if <code>faction == null</code>
          if (tokens.length >= 3) {
  */
            try {
  public void execute(Faction faction, Region region) {
              length = Integer.parseInt(tokens[2]);
    if (faction == null)
              textIndex = 3;
      throw new NullPointerException();
            } catch (NumberFormatException nfe) {
 
              // third argument not a number
    executeFrom(faction, region, region);
              length = Integer.MAX_VALUE;
  }
              textIndex = 2;
 
            }
  /**
          }
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
        } catch (NumberFormatException nfe) {
  * container to execute all unit commands. Ignore regions before first (in the report order).
          // second argument not a number
  *
          period = 0;
  * @param faction scripts for all units of this faction are executed
          textIndex = 1;
  * @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
      if (rest == 1) {
  * @throws NullPointerException if <code>faction == null</code>
        result = new StringBuilder();
  */
        if (period > 0) {
  public void executeFrom(Faction faction, Region region, Region first) {
          rest = period + 1;
    executeFrom(Collections.singleton(faction), region, first);
        }
        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 />
   * Parses scripts and confirms units according to the "confirm" tag. Ignore regions before first
   * Autoconfirms a unit (or prevents autoconfirmation if NICHT). If length is given, the unit is
   * (in the report order).
   * 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 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
   * @param tokens
  *          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>
   */
   */
   protected void commandAuto(String[] tokens) {
   public void executeFrom(Collection<Faction> factions, Region region, Region first) {
     if (tokens.length > 4) {
     if (factions == null || factions.isEmpty())
       addNewError("zu viele Argumente");
       throw new NullPointerException();
       return;
 
    currentFactions = CollectionFactory.createOrderedMap(1);
    for (Faction f : factions) {
       currentFactions.put(f, null);
     }
     }
     if (tokens.length == 1) {
 
      setConfirm(getCurrentUnit(), true);
     helper.getUI().setMaximum(world.getRegions().size() + 4);
      addNewOrder(currentOrder, false);
    helper.getUI().setProgress("init", ++progress);
     } else if (NOT.equalsIgnoreCase(tokens[1])) {
 
      setConfirm(getCurrentUnit(), false);
     // comment out the following two lines if you don't have the newest nighthly build of Magellan
      addNewOrder(currentOrder, false);
    helper.getUI().setProgress("preprocessing", ++progress);
     } else {
 
      int length = 0, period = 0;
     EresseaRelationFactory relationFactory = ((EresseaRelationFactory) world.getGameSpecificStuff()
      try {
         .getRelationFactory());
        length = Integer.parseInt(tokens[1]);
    relationFactory.stopUpdating();
         if (tokens.length > 2) {
 
          period = Integer.parseInt(tokens[2]);
    findSomeUnit(currentFactions);
        }
 
        if (length > 0) {
    initLocales();
          setConfirm(getCurrentUnit(), true);
 
        } else {
    for (Faction faction : factions) {
          setConfirm(getCurrentUnit(), false);
      if (faction.units().size() >= UNIT_LIMIT) {
          if (period > 0) {
         addWarning(someUnit, "Einheitenlimit erreicht (" + faction.units().size() + "/"
            length = period;
             + UNIT_LIMIT + ")! ");
          }
      } else if (faction.units().size() * 1.1 > UNIT_LIMIT) {
        }
        addWarning(someUnit, "Einheitenlimit fast erreicht (" + faction.units().size() + "/"
         StringBuilder newOrder = new StringBuilder();
            + UNIT_LIMIT + ")! ");
        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;
       }
       }
     }
     }
  }


  /**
     collectStats();
  * <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) {
    // Parses the orders of the units for commands of the form "// $cript ..." and
      addNewError("falsche Anzahl Argumente");
    // tries to execute them.
       return;
     if (region == null) {
      boolean go = first == null;
      for (Region r : world.getRegions()) {
        if (r == first) {
          go = true;
        }
        if (go) {
          execute(r);
        }
      }
    } else {
       execute(region);
     }
     }


     Unit target = helper.getUnit(tokens[1]);
     // comment out the following line if you don't have the newest nightly build of Magellan
    helper.getUI().setProgress("postprocessing", ++progress);


     // test if EACH is present
     relationFactory.restartUpdating();
    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
     for (Faction faction : factions) {
    if (ALLOrder.equalsIgnoreCase(tokens[2])) {
       for (Unit u : faction.units()) {
       if (!testUnit(tokens[1], target, w))
         if ("1".equals(getProperty(u, "confirm"))) {
         return;
           u.setOrdersConfirmed(true);
      if (tokens.length == 3) {
        } else if ("0".equals(getProperty(u, "confirm"))) {
        for (Item item : getCurrentUnit().getItems()) {
          u.setOrdersConfirmed(false);
           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;
         notifyMagellan(u);
       }
       }
     }
     }
    // comment out the following line if you don't have the newest nightly build of Magellan
    helper.getUI().setProgress("ready", ++progress);
  }
  protected void execute(Region region) {
    try {
      currentRegion = region;
      skills.clear();
      initSupply();


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


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


      // handle GIB xyz LUXUS
          setCurrentUnit(u);
      if (LUXUSOrder.equalsIgnoreCase(tokens[2])) {
          // comment out the following line if you don't have the newest nightly build of Magellan
        if (world.getRules().getItemCategory("luxuries") == null) {
          helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          giveAll(tokens, target, w, new Filter() {


             public boolean approve(Item item) {
          try {
              return world.getRules().getItemCategory("luxuries").equals(item.getItemType()
             parseScripts();
                  .getCategory());
          } catch (RuntimeException e) {
             }
            addWarning(u, "error " + e.getClass().getSimpleName());
           });
            log.error(e.getClass().getSimpleName() + " script error for " + u);
             throw new RuntimeException("script error for " + u, e);
           }
         }
         }
       }
       }
      setCurrentUnit(null);


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


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


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


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


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


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


     // make GIVE order
     if (someUnit == null)
    if (amount > 0) {
       throw new RuntimeException("No units in report!");
      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 {
   protected boolean testUnit(String sOther, Unit other, Warning warning) {
 
     return testUnit(sOther, other, warning, false);
     boolean approve(Item item);
 
   }
   }


   private void giveAll(String[] tokens, Unit target, Warning w, Filter filter) {
   protected boolean testUnit(String sOther, Unit other, Warning w, boolean testFaction) {
     if (testUnit(tokens[1], target, w)) {
     if (other == null || other.getRegion() != currentUnit.getRegion()) {
       if (tokens.length > 3) {
      if (w.contains(Warning.C_UNIT) && w.contains(Warning.C_HIDDEN)) {
         addNewError("zu viele Parameter");
        addNewWarning(sOther + " nicht da");
        return false;
       } else if (ADD_NOT_THERE_INFO) {
         addNewOrder("; " + sOther + " nicht da", true);
       }
       }
      return !w.contains(Warning.C_HIDDEN);
    } else if (testFaction && w.contains(Warning.C_FOREIGN)
        && !currentFactions.containsKey(other.getFaction())) {
      addNewWarning("Einheit " + sOther + " gehört nicht zu uns");
    }


      for (Item item : getCurrentUnit().getItems())
    return true;
        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);
            }
          }
        }


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


  private boolean testUnit(String sOther, Unit other, Warning warning) {
    GIVEOrder = getLocalizedOrder(EresseaConstants.OC_GIVE, GIVEOrder).toString();
     return testUnit(sOther, other, warning, false);
    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();


  private boolean testUnit(String sOther, Unit other, Warning w, boolean testFaction) {
     if (currentFactions.keySet().iterator().next().getLocale().getLanguage() != "de") {
     if (other == null || other.getRegion() != getCurrentUnit().getRegion()) {
      // warning constants
      if (w.contains(C_UNIT) && !w.contains(C_HIDDEN)) {
      Warning.W_NEVER = "never";
        addNewWarning(sOther + " nicht da");
      Warning.W_SKILL = "skill";
        return false;
       Warning.W_WEAPON = "weapon";
       } else if (ADD_NOT_THERE_INFO) {
      Warning.W_SHIELD = "shields";
        addNewOrder("; " + sOther + " nicht da", true);
      Warning.W_ARMOR = "armor";
       }
       BEST = "best";
       return w.contains(C_HIDDEN);
       NULL = "null";
    } else if (testFaction && w.contains(C_FOREIGN) && !currentFactions.containsKey(other
       LUXUSOrder = "LUXURY";
        .getFaction())) {
      KRAUTOrder = "HERBS";
       addNewWarning("Einheit " + sOther + " gehört nicht zu uns");
     }
     }
    setCurrentUnit(null);
  }


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


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


     String sOther = "???";
  /**
     if (tokens[0].equals("BenoetigeFremd")) {
  * Tries to translate the given order to the current locale.
       sOther = tokens[1];
  */
       unit = helper.getUnit(tokens[1]);
  protected String getLocalizedOrder(String orderKey, String fallBack) {
     String translation = Resources.getOrderTranslation(orderKey, currentUnit.getLocale());
     if (translation == orderKey)
       return fallBack;
    else
       return translation;
  }


      // erase unit token for easier processing afterwards
  /**
      tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
  * Adss an order to the current unit's new orders.
      tokens[0] = "BenoetigeFremd";
  *
 
  * @param order The new order
       // only benoetigefremd can have warnings!
  * @param changed Set to true if this is a change (not merely a copy of an old order)
       tokens = w.parse(tokens);
  */
      // foreign implies amount
  protected void addNewOrder(String order, boolean changed) {
      if (w.contains(C_FOREIGN)) {
    setChangedOrders(isChangedOrders() || changed);
        w.add(W_AMOUNT);
    if (!changed) {
      }
       // remove old orders matching a removed order
       for (String pattern : removedOrderPatterns)
        if (order.matches(pattern))
          return;
     }
     }


     int tokenCount = tokens.length;
     newOrders.add(getParser().parse(order, currentUnit.getLocale()).getText());
  }


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


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


    if (tokens.length < 2 || tokens.length > 5) {
  /**
      addNewError("falsche Anzahl Argumente");
  * Adds a warning message (with a to do tag) to the new orders.
      return;
  *
    }
  * @param text
  */
  protected void addNewMessage(String text) {
    addNewOrder(COMMENTOrder + " ----- " + text + " -----", true);
  }


     if (!testUnit(sOther, unit, w, true))
  /**
      return;
  * Adds an error line to new orders.
  *
  * @param hint
  */
  protected void addNewWarning(String hint) {
     addNewWarning(hint, true);
  }
 
  protected void addNewWarning(String hint, boolean addLine) {
    error = line;
    // errMsg = hint;
    addNewOrder(COMMENTOrder + " TODO: " + hint
        + (addLine ? " (Warnung in Zeile " + error + ")" : ""), true);
    setConfirm(currentUnit, false);
  }


    try {
  /**
      if (ALLOrder.equals(tokens[1])) {
  * Adds some statistic information to the orders of the first unit.
        if (tokens.length > 2) {
  *
          addNeed(tokens[2], unit, 0, Integer.MAX_VALUE, priority, w);
  */
        } else {
  protected void collectStats() {
          for (String item : supplyMap.keySet()) {
    int buildingScripts = 0;
            addNeed(item, unit, 0, Integer.MAX_VALUE, priority, w);
    int shipScripts = 0;
          }
    int unitScripts = 0;
        }
    int regionScripts = 0;
      } else if (EACHOrder.equals(tokens[1])) {
    // comment out the following lines if you don't have the newest nightly build of Magellan
        if (tokens.length != 4) {
    for (Building b : world.getBuildings()) {
          addNewError("ungültige Argumente für Benoetige JE x Ding");
       if (helper.hasScript(b)) {
        } else {
         buildingScripts++;
          int amount = (int) Math.ceil(unit.getPersons() * Double.parseDouble(tokens[2]));
      }
          String item = tokens[3];
    }
          addNeed(item, unit, amount, amount, priority, w);
    for (Ship s : world.getShips()) {
        }
      if (helper.hasScript(s)) {
      } else if (KRAUTOrder.equals(tokens[1])) {
         shipScripts++;
        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());
     }
     }
  }
     for (Unit u : world.getUnits()) {
 
       if (helper.hasScript(u)) {
  /**
         addOrder(u, COMMENTOrder + " hat Skript", false);
  * <code>// $cript BerufDepotVerwalter [[ZusatzMin] ZusatzMax]</code><br />
        unitScripts++;
  * 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;
     for (Region r : world.getRegions()) {
    if (tokens.length > 1) {
       if (helper.hasScript(r)) {
       try {
         regionScripts++;
        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) });
     if (showStats > 0) {
    commandSupply(new String[] { "Versorge", "100" });
      someUnit.addOrderAt(0, "; " + unitScripts + " unit scripts, " + buildingScripts
          + " building scripts, " + shipScripts + " ship scripts, " + regionScripts
          + " region scripts", true);
    }
   }
   }


   /**
   /**
   * <code>Versorge [[item1]...] priority</code> -- set supply priority. Units with negative
  * Parses the orders of the unit u for commands of the form "// $cript ..." and tries to execute
   * priority only deliver for minimum needs. Needs are satisfied in descending order of priority.
  * them. Known commands:<br />
   * If no items are given, the priority is adjusted for alle the unit's items.
  * <kbd>// $cript +X text</kbd> -- If X<=1 then a warning containing text is added to the unit's
   */
  * orders. Otherwise X is decreased by one.<br />
   protected void commandSupply(String[] tokens) {
  * <code>// $cript [rest [period [length]] text</code> -- Adds text (or commands) to the
     int priority = 0;
  * orders<br />
     if (tokens.length < 2) {
  * <code>// $cript auto [NICHT]|[length [period]]</code> -- autoconfirm orders<br />
      addNewError("zu wenig Argumente");
  * <code>// $cript Loeschen [$kurz] [<prefix>]</code> -- clears orders except comments<br />
     }
  * <code>// $cript GibWenn receiver [[JE] amount|ALLES|KRAUT|LUXUS|TRANK] [item] [warning...]</code>
     try {
  * -- add give order (if possible)<br />
      priority = Integer.parseInt(tokens[tokens.length - 1]);
   * <code>// $cript Benoetige minAmount [maxAmount] item [priority]</code><br />
     } catch (NumberFormatException e) {
  * <code>// $cript Benoetige JE amount item [priority]</code><br />
       addNewError("Zahl erwartet");
  * <code>// $cript Benoetige ALLES [item] [priority]</code> -- acquire things from other
       return;
  * units<br />
    }
  * <code>// $cript BenoetigeFremd unit [JE] minAmount [maxAmount] item [priority] [warning]</code><br
    if (tokens.length == 2) {
  * />
      for (Item item : getCurrentUnit().getItems()) {
  * <code>// $cript Versorge [[item]...] priority</code> -- set supply priority.<br />
        Supply supply = getSupply(item.getOrderName(), getCurrentUnit());
   * <code>// $cript BerufDepotVerwalter [Zusatzbetrag]</code> Collects all free items in the
        if (supply != null) {
  * region, Versorge 100, calls Ueberwache<br />
          supply.priority = priority;
  * <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
    } else {
  * ratio <br />
      for (int i = 1; i < tokens.length - 1; ++i) {
   * <code>// $cript BerufBotschafter [minimum money] [Talent]</code> -- earn money if necessary,
        Supply supply = getSupply(tokens[i], getCurrentUnit());
  * otherwise learn skill<br />
        if (supply != null) {
  * <code>// $cript Ueberwache</code> -- look out for unknown units<br />
           supply.priority = priority;
  * <code>// $cript Erlaube faction unit [unit...]</code> -- allow units for Ueberwache<br />
         }
  * <code>// $cript Verlange faction unit [unit...]</code> -- allow and require units for
       }
  * Ueberwache<br />
  * <code>// $cript Ernaehre [amount]</code> -- earn money<br />
  * <code>// $cript Handel Menge|xM [xR] [ALLES | Verkaufsgut...] Warnung</code> -- trade luxuries<br />
  * <code>// $cript Steuermann minSilver maxSilver [Talent ...]</code> -- be responsible for ship<br />
  * <code>// $cript Mannschaft [Talen ...]</code> -- be crew and learn<br />
  * <code>// $cript Quartiermeister [[amount item]...]</code> -- be lookout<br />
  * <code>// $cript Sammler [interval]</code> -- collect and research HERBS<br />
  * <code>// $cript KrautKontrolle [route]</code> -- FORSCHE KRÄUTER in several regions<br />
  * <code>// $cript RekrutiereMax</code> -- recruit as much as possible<br />
  * <code>// $cript Kommentar text</code> -- add ; comment<br />
   */
   protected void parseScripts() {
     // errMsg = null;
    line = 0;
     allowedUnits.clear();
    requiredUnits.clear();
     clear = null;
 
     // NOTE: must not change currentUnit's orders directly! Always change newOrders!
    Deque<String> queue = new LinkedList<String>();
     for (Order o : currentUnit.getOrders2()) {
       ++line;
      queue.add(o.getText());
       while (!queue.isEmpty()) {
        currentOrder = queue.poll();
        try {
          String[] tokens = detectScriptCommand(currentOrder);
          if (tokens == null) {
            // add order if
            if (shallClear(currentOrder)) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
            } else {
              addNewOrder(currentOrder, false);
            }
            currentOrder = null;
          } else {
            // as of Java 7 the first character of an integer may be a '+' sign
            boolean repeat = true;
            while (tokens != null && !tokens[0].startsWith("+") && repeat) {
              try {
                Integer.parseInt(tokens[0]);
                String[] nextOrders = commandRepeat(tokens);
                if (nextOrders == null) {
                  currentOrder = null;
                  tokens = null;
                } else {
                  currentOrder = nextOrders.length > 0 ? nextOrders[0] : "";
                  tokens = detectScriptCommand(currentOrder);
                  if (tokens == null) {
                    addNewOrder(currentOrder, true);
                    currentOrder = null;
                  }
                  for (int i = 1; i < nextOrders.length; ++i) {
                    queue.add(nextOrders[i]);
                  }
                }
              } catch (NumberFormatException e) {
                repeat = false;
                // not a repeating order
              }
            }
            if (tokens != null) {
              Consumer<String[]> interpreter = commands.get(tokens[0]);
              if (interpreter != null) {
                interpreter.accept(tokens);
              } else {
                String command = tokens[0];
                if (command.startsWith("+")) {
                  commandWarning(tokens);
                  setChangedOrders(true);
                } else if (command.equals("BerufBotschafter")) {
                  addNewOrder(currentOrder, false);
                  Collection<String> addedCommands = commandEmbassador(tokens);
                  Collections.reverse((List<?>) addedCommands);
                  for (String newOrder : addedCommands) {
                    queue.addFirst(newOrder);
                  }
                  setChangedOrders(true);
                } else {
                  addNewError("unbekannter Befehl: " + command);
                }
              }
              currentOrder = null;
            }
          }
        } catch (Throwable t) {
           StringWriter w = new StringWriter();
 
          t.printStackTrace(new PrintWriter(w));
          addNewError("Fehler bei der Ausführung von '" + currentOrder + "': " + w.toString());
          log.error(t);
         }
       }
     }
     }
    updateCurrentOrders();
   }
   }


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


   /**
   /**
   * <code>// $cript Lerne Talent1 Stufe1 [[Talent2 Stufe2]...]</code><br />
   * If order is a script command ("// $cript ..."), returns a List of the tokens. Otherwise returns
  * Tries to learn skills in given ratio. For example,
  * <code>null</code>. The first in the list is the first token after the "// $cript".
  * <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) {
   protected String[] detectScriptCommand(String order) {
     if (tokens.length < 3 || tokens.length % 2 != 1) {
     StringTokenizer tokenizer = new StringTokenizer(order, " ");
      addNewError("falsche Anzahl Argumente");
     if (tokenizer.hasMoreTokens()) {
      return;
      String part = tokenizer.nextToken();
     }
      if (part.equals(PCOMMENTOrder) || part.equals(COMMENTOrder)) {
//    List<Skill> targetSkills = new LinkedList<Skill>();
         if (tokenizer.hasMoreTokens()) {
    List targetSkills = new LinkedList();
           part = tokenizer.nextToken();
 
          if (part.startsWith(scriptMarker)) {
    for (int i = 1; i < tokens.length; i++) {
            if (!part.equals(scriptMarker)) {
      try {
              addNewWarning("lines starting with '" + part + "' not allowed");
         if (i < tokens.length - 1) {
            } else {
           SkillType skill = world.getRules().getSkillType(tokens[i++]);
              List<String> result = new ArrayList<String>();
          int level = Integer.parseInt(tokens[i]);
              while (tokenizer.hasMoreTokens()) {
          if (skill == null) {
                result.add(tokenizer.nextToken());
            addNewError("unbekanntes Talent " + tokens[i - 1]);
              }
              if (result.size() == 0)
                return null;
              return result.toArray(new String[] {});
            }
           }
           }
          targetSkills.add(new Skill(skill, 0, level, 1, true));
        } else {
          addNewError("unerwartetes Token " + tokens[i]);
         }
         }
      } catch (NumberFormatException e) {
        addNewError("ungültige Stufe " + tokens[i]);
       }
       }
     }
     }
 
     return null;
     learn(getCurrentUnit(), targetSkills);
   }
   }


   /**
   /**
   * <code>Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code><br />
   * <code>// $cript Loeschen [$kurz] [<prefix>]</code><br />
   * Tries to learn best skill and acquire equipment. If no skill is given, best weapon skill is
   * Remove orders except comments from here on. If "$kurz", remove all orders, otherwise only long
   * selected. If no weapon is given, best matching weapon is acquired and so on. Preference is
   * and permanent (@) orders. If prefix is set, remove only orders starting with that prefix.
  * 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 />
   * @param tokens
  * Warning can be:<br />
  * nie: issues no warnings at all, Talent: only warns if no skill is given and no best skill
   * exists, Waffe: additionally warn if no weapon can be acquired, Schild: additionally warn if no
   * shield, Rüstung: additionally warn if no armor. Default warning level is "Waffe".
   */
   */
   protected void commandSoldier(String[] tokens) {
   protected void commandClear(String[] tokens) {
     String warning = tokens[tokens.length - 1];
     clearPrefix = "";
     if (!(W_NEVER.equals(warning) || W_SKILL.equals(warning) || W_WEAPON.equals(warning) || W_SHIELD
    if (tokens.length == 1) {
        .equals(warning) || W_ARMOR.equals(warning))) {
      clear = LONG;
      warning = null;
     } else {
    }
      if (tokens[1].equalsIgnoreCase(SHORT)) {
    String skill = null;
        clear = ALLOrder;
    if (tokens.length > 1 + (warning == null ? 0 : 1)) {
        clearPrefix =
       skill = tokens[1];
            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));
       }
     }
     }
     String weapon = null;
     for (int i = 0; i < newOrders.size(); i++) {
    if (tokens.length > 2 + (warning == null ? 0 : 1)) {
       String order = newOrders.get(i);
       weapon = tokens[2];
      if (shallClear(order)) {
    }
        setChangedOrders(true);
    String shield = null;
        newOrders.set(i, COMMENTOrder + " " + order);
    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) {
  protected boolean shallClear(String order) {
       warning = W_WEAPON;
     if (clear == null)
     }
       return false;
     String trimmed = order.trim();


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


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


    if (helper.getSilver(getCurrentUnit()) < 100) {
      if (tokens.length >= 2) {
      if (hasEntertain() && getCurrentUnit().getSkill(EresseaConstants.S_UNTERHALTUNG) != null
        try {
           && getCurrentUnit().getSkill(EresseaConstants.S_UNTERHALTUNG).getLevel() > 0) {
          period = Integer.parseInt(tokens[1]);
        addNewOrder(ENTERTAINOrder, true);
          textIndex = 2;
      } else if (hasWork()) {
           if (tokens.length >= 3) {
        addNewOrder(WORKOrder, true);
            try {
      } else {
              length = Integer.parseInt(tokens[2]);
         addNewWarning("Einheit verhungert");
              textIndex = 3;
            } catch (NumberFormatException nfe) {
              // third argument not a number
              length = Integer.MAX_VALUE;
              textIndex = 2;
            }
          }
         } catch (NumberFormatException nfe) {
          // second argument not a number
          period = 0;
          textIndex = 1;
        }
       }
       }
    } else if (skill == null) {
      if (rest == 1) {
      StringBuilder order = new StringBuilder();
        lines = new ArrayList<String>(1);
      if (tokens.length > 1) {
        StringBuilder result = new StringBuilder();
        order.append(tokens[1]);
        if (period > 0) {
          rest = period + 1;
        }
        if (textIndex < tokens.length && tokens[textIndex].equals(scriptMarker)) {
          result.append(COMMENTOrder).append(" ");
        }
        for (int tokenIndex = textIndex; tokenIndex < tokens.length; ++tokenIndex) {
          if (tokenIndex > textIndex) {
            result.append(" ");
          }
          String[] subTokens = tokens[tokenIndex].split("\\\\n");
          result.append(subTokens[0]);
          for (int j = 1; j < subTokens.length; ++j) {
            lines.add(result.toString());
            result.setLength(0);
            result.append(subTokens[j]);
          }
          if (tokenIndex + 1 >= tokens.length) {
            lines.add(result.toString());
          }
        }
      } else if (length == 0) {
        // return empty string to signal success
        lines = new ArrayList<String>(1);
        lines.add("");
       }
       }
       for (int i = 2; i < tokens.length; ++i) {
       if ((rest > 1 || period > 0) && length > 0) {
        order.append(" ").append(tokens[i]);
        StringBuilder newOrder = new StringBuilder();
      }
        newOrder.append(createScriptCommand()).append(rest - 1);
      addNewOrder(order.toString(), true);
        if (period > 0) {
    } else {
          newOrder.append(" ").append(period);
      learn(getCurrentUnit(), Collections.singleton(skill));
        }
      if (tokens.length > 2) {
        if (length != Integer.MAX_VALUE) {
        addNewError("zu viele Argumente");
          newOrder.append(" ").append(length - 1);
        }
        for (int i = textIndex; i < tokens.length; ++i) {
          newOrder.append(" ").append(tokens[i]);
        }
        addNewOrder(newOrder.toString(), true);
       }
       }
    } catch (NumberFormatException e) {
      addNewOrder(currentOrder, false);
      addNewError("Zahl erwartet");
     }
     }
    return lines != null ? lines.toArray(new String[0]) : null;
   }
   }


   protected void commandMonitor(String[] tokens) {
  /**
     if (tokens.length > 1) {
  * <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");
       addNewError("zu viele Argumente");
      return;
     }
     }
 
     if (tokens.length == 1) {
     // check if region units are allowed
      setConfirm(currentUnit, true);
//    Map<Faction, List<Unit>> warnings = new HashMap<Faction, List<Unit>>();
      addNewOrder(currentOrder, false);
    Map warnings = new HashMap();
     } else if (NOT.equalsIgnoreCase(tokens[1])) {
     for (Unit u : getCurrentRegion().units()) {
       setConfirm(currentUnit, false);
       if (u.getFaction() != getCurrentUnit().getFaction()) {
      addNewOrder(currentOrder, false);
        if (!(allowedUnits.containsKey(u.getFaction()) && (allowedUnits.get(u.getFaction())
    } else {
            .contains(u.getID()) || allowedUnits.get(u.getFaction()).contains(getCurrentRegion()
      int length = 0, period = 0;
                .getZeroUnit().getID())))) {
      try {
          if (!(requiredUnits.containsKey(u.getFaction()) && requiredUnits.get(u.getFaction())
        length = Integer.parseInt(tokens[1]);
              .contains(u.getID()))) {
        if (tokens.length > 2) {
//            List<Unit> list = warnings.get(u.getFaction());
          period = Integer.parseInt(tokens[2]);
            List list = warnings.get(u.getFaction());
        }
            if (list == null) {
        if (length > 0) {
//              list = new LinkedList<Unit>();
          setConfirm(currentUnit, true);
              list = new LinkedList();
        } else {
              warnings.put(u.getFaction(), list);
          setConfirm(currentUnit, false);
             }
          if (period > 0) {
            list.add(u);
             length = period;
           }
           }
         }
         }
      }
        StringBuilder newOrder = new StringBuilder();
    }
        newOrder.append(createScriptCommand()).append(tokens[0]);
 
         newOrder.append(" ").append(length - 1);
//    for (Entry<Faction, List<Unit>> entry : warnings.entrySet()) {
         if (period > 0) {
    for (Entry entry : warnings.entrySet()) {
           newOrder.append(" ").append(period);
      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;
         }
         }
        addNewOrder(newOrder.toString(), true);
      } catch (NumberFormatException e) {
        addNewOrder(currentOrder, false);
        addNewError("Zahl erwartet");
        return;
       }
       }
      entry.getValue().clear();
      addNewWarning(sb.toString());
     }
     }
  }


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


   protected void commandAllow(String[] tokens) {
  /**
     if (tokens.length < 3) {
  * <code>// $cript GibWenn receiver [[JE] amount|ALLES|KRAUT|LUXUS|TRANK] [item] [warning...]</code>
      addNewError("zu wenige Argumente");
  * <br />
      return;
  * Adds a GIB order to the unit. Warning may be one of "immer" (the default), "Menge", "Einheit",
     }
  * "nie".
 
  */
    Faction faction = helper.getFaction(tokens[1]);
   protected void commandGiveIf(String[] tokens) {
//    Map<Faction, Set<UnitID>> map;
     Warning w = new Warning(true);
    Map map;
     tokens = w.parse(tokens);
     if (tokens[0].equals("Erlaube")) {
 
      map = allowedUnits;
     if (tokens.length < 3 || tokens.length > 5) {
    } else {
      addNewError("falsche Anzahl Argumente");
       map = requiredUnits;
       return;
     }
     }
    if (faction == null) {
 
      addNewError("unbekannte Partei");
     Unit target = helper.getUnit(tokens[1]);
     } else {
    String targetId = tokens[1];
//      Set<UnitID> set = map.get(faction);
 
      Set set = map.get(faction);
    // test if EACH is present
      if (set == null) {
    int je = 0;
//         set = new HashSet<UnitID>();
    if (EACHOrder.equals(tokens[2])) {
        set = new HashSet();
       je = 1;
        map.put(faction, set);
       if (ALLOrder.equals(tokens[3]) || KRAUTOrder.equals(tokens[3])
       }
          || LUXUSOrder.equals(tokens[3]) || TRANKOrder.equals(tokens[3])) {
       if (ALLOrder.equals(tokens[2])) {
        addNewError("JE " + tokens[3] + " geht nicht");
        set.add(getCurrentRegion().getZeroUnit().getID());
         return;
        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));
         }
       }
       }
     }
     }
  }


  /**
    if (!testUnit(targetId, target, w))
  * <code>// $cript Ernaehre [amount]</code> -- Earn as much money as possible (or the specified
      return;
  * amount), Versorge {@value #DEFAULT_EARN_PRIORITY}
 
  */
     // handle GIB xyz ALLES
  protected void commandEarn(String[] tokens) {
     if (ALLOrder.equalsIgnoreCase(tokens[2])) {
     int amount = -1;
       if (tokens.length == 3) {
     if (tokens.length > 1) {
         giveAll(targetId, w, null, null);
      try {
        amount = Integer.parseInt(tokens[1]);
       } catch (NumberFormatException e) {
         addNewError("ungültige Zahl " + tokens[1]);
         return;
         return;
      }
      if (tokens.length > 2) {
        addNewError("zu viele Argumente");
       }
       }
     }
     }


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


    // remove previous orders
      // handle GIB xyz LUXUS
    removeOrdersLike(TAXOrder + ".*", true);
      if (LUXUSOrder.equalsIgnoreCase(tokens[2])) {
    removeOrdersLike(ENTERTAINOrder + ".*", true);
        if (world.getRules().getItemCategory("luxuries") == null) {
    removeOrdersLike(WORKOrder + ".*", true);
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          giveAll(targetId, w, null, world.getRules().getItemCategory("luxuries"));
        }
      }


    int maxWorkers = Utils.getIntValue(world.getGameSpecificRules().getMaxWorkers(
      // handle GIB xyz TRANK
         getCurrentRegion()), 0);
      if (TRANKOrder.equalsIgnoreCase(tokens[2])) {
    int workers = Math.min(maxWorkers, getCurrentRegion().getPeasants());
         if (world.getRules().getItemCategory("potions") == null) {
    Skill entertaining = getCurrentUnit().getModifiedSkill(world.getRules().getSkillType(
          addNewError("Spiel kennt keine Tränke");
         EresseaConstants.S_UNTERHALTUNG));
         } else {
    Skill taxing = getCurrentUnit().getModifiedSkill(world.getRules().getSkillType(
          giveAll(targetId, w, null, world.getRules().getItemCategory("potions"));
        EresseaConstants.S_STEUEREINTREIBEN));
        }
    int entertain = 0, entertain2 = 0, tax = 0, tax2 = 0;
      }
    if (entertaining != null && hasEntertain()) {
 
      entertain2 = 20 * entertaining.getLevel() * getCurrentUnit().getPersons();
       return;
       entertain = Math.max(0, Math.min(getCurrentRegion().maxEntertain(), entertain2));
     }
     }
     if (taxing != null && isSoldier(getCurrentUnit())) {
 
       tax2 = 20 * taxing.getLevel() * getCurrentUnit().getPersons();
     if (tokens.length != 4 + je) {
       tax = Math.min(getCurrentRegion().getSilver(), tax2);
       addNewError("zu viele Parameter");
       return;
     }
     }


     if (tax > entertain) {
     // get amount
      addNewOrder(TAXOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " " + tax + ">"
    final String item = tokens[3 + je];
          + entertain, true);
    int amount = 0;
      if (tax >= getCurrentRegion().getSilver() + workers * 10 - getCurrentRegion().getPeasants()
     if (ALLOrder.equalsIgnoreCase(tokens[2 + je])) {
          * 10) {
       if (KRAUTOrder.equals(item) || LUXUSOrder.equals(item)) {
        addNewWarning("Bauern verhungern");
         addNewError("GIB xyz ALLES " + item + " statt GIB xyz " + item);
      }
      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();
       }
       }
      giveAll(targetId, w, item, null);
      return;
     } else {
     } else {
       addNewOrder(WORKOrder + " " + (amount > 0 ? amount : ""), true);
       try {
       if ((maxWorkers - workers) * 10 < Math.min(amount, getCurrentUnit().getModifiedPersons()
        amount = Integer.parseInt(tokens[2 + je]);
          * 10)) {
       } catch (NumberFormatException e) {
         addNewWarning("zu viele Arbeiter");
        amount = 0;
         addNewError("Zahl oder ALLES erwartet");
        return;
       }
       }
     }
     }
    setConfirm(getCurrentUnit(), true);
  }


  /**
    // get full amount (=amount * persons)
  * <code>// $cript Handel Menge [ALLES | Verkaufsgut...] Warnung</code>: trade luxuries, Versorge
    int fullAmount = amount;
  * {@value #DEFAULT_EARN_PRIORITY}. Menge may be a multipler ("x2" of the region maximum) Warnung
    if (je == 1) {
  * can be "Talent", "Menge", or "nie"<br />
      if (target == null) {
  */
        addNewMessage("Einheit nicht gefunden; kann Menge nicht überprüfen");
  protected void commandTrade(String[] tokens) {
      } else {
    if (tokens.length < 2) {
        fullAmount = target.getModifiedPersons() * amount;
      addNewError("zu wenige Argumente");
      }
     }
     }


     commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });
     // check availibility
 
     if (getItemCount(currentUnit, item) < fullAmount) {
     Warning warning = new Warning(true);
       if (w.contains(Warning.C_AMOUNT)) {
    tokens = warning.parse(tokens);
         addNewWarning("zu wenig " + item);
 
    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 {
       } else {
         buyAmount = Integer.parseInt(tokens[1]);
         addNewMessage("zu wenig " + item);
       }
       }
    } catch (NumberFormatException e) {
       fullAmount = getItemCount(currentUnit, item);
       addNewError("ungültige Zahl " + tokens[1]);
       je = 0;
       return;
     }
     }


     Skill buySkill = getCurrentUnit().getModifiedSkill(world.getRules().getSkillType(
     // make GIVE order
        EresseaConstants.S_HANDELN));
     if (fullAmount > 0) {
     if (buySkill == null) {
       giveTransfer(targetId, item, fullAmount, false);
       addNewError("kein Handelstalent");
      return;
     }
     }
  }


     if (getCurrentRegion().getPrices() == null) {
  private void giveAll(String targetId, Warning w, String filterItem, ItemCategory filterCategory) {
      addNewError("kein Handel möglich");
     for (Item item : getUnitItems(currentUnit, filterItem, filterCategory)) {
       return;
      String itemName = item.getOrderName();
      int amount = getSupply(itemName, currentUnit).getAmount();
       giveTransfer(targetId, itemName, amount, true);
     }
     }
  }


    LuxuryPrice buyGood = null;
  private Collection<Item> getUnitItems(Unit unit, String filterItem, ItemCategory filterCategory) {
//    for (Entry<StringID, LuxuryPrice> entry : getCurrentRegion().getPrices().entrySet()) {
     Collection<Item> items = new ArrayList<Item>();
     for (Entry entry : getCurrentRegion().getPrices().entrySet()) {
    for (Item item : unit.getItems()) {
      LuxuryPrice price = entry.getValue();
       if ((filterItem != null && filterItem.equals(
      if (price.getPrice() < 0) {
          item.getOrderName()))
        buyGood = price;
          ||
       } else {
          (filterCategory != null && filterCategory.equals(
        if (getCurrentRegion().getOldPrices() != null && getCurrentRegion().getOldPrices().get(entry
              item.getItemType().getCategory()))
            .getKey()) != null && price.getPrice() < getCurrentRegion().getOldPrices().get(entry
          || (filterItem == null && filterCategory == null)) {
                .getKey()).getPrice()) {
        items.add(item);
          addNewWarning("Preis gesunken: " + price.getItemType());
      }
        }
    }
      }
     return items;
     }
  }


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


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


    if (buyAmount > 0 && (volume <= 0 || buyGood == null)) {
      // erase unit token for easier processing afterwards
       addNewError("Kein Handel möglich");
      tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
      tokens[0] = "BenoetigeFremd";
 
      // only benoetigefremd can have warnings!
      tokens = w.parse(tokens);
      // foreign implies amount
      if (w.contains(Warning.C_FOREIGN)) { // FIXME test
        w.add(Warning.W_AMOUNT);
      }
    }
 
    int tokenCount = tokens.length;
 
    int priority;
    try {
       priority = Integer.parseInt(tokens[tokenCount - 1]);
      tokenCount--;
    } catch (NumberFormatException e) {
      priority = DEFAULT_PRIORITY;
     }
     }


//     LinkedList<String> orders = new LinkedList<String>();
     tokens = Arrays.copyOf(tokens, tokenCount);
    LinkedList orders = new LinkedList();


     int maxAmount = buySkill.getLevel() * getCurrentUnit().getPersons() * 10;
     if (tokens.length < 2 || tokens.length > 6) {
     int skillNeeded = 0;
      addNewError("falsche Anzahl Argumente");
      return;
     }


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


  private int getAmountWithHorse(Unit unit, String amount, String item) {
    if (isHorse(item))
      if (HORSEOrder.equals(amount))
        return world.getGameSpecificRules().getMaxHorsesRiding(unit);
      else if (FOOTOrder.equals(amount))
        return world.getGameSpecificRules().getMaxHorsesWalking(unit);
    return Integer.parseInt(amount);
  }
  /**
  * <code>// $cript BerufDepotVerwalter [[ZusatzMin] ZusatzMax]</code><br />
  * Collects all free items in the region, Versorge 100, calls Ueberwache
  */
  protected void commandDepot(String[] tokens) {
    if (tokens.length > 3) {
      addNewError("zu viele Argumente");
     }
     }
    commandMonitor(new String[] { "Ueberwache" });


     // Soll eingekauft werden?
     int costs = 0;
    if (volume > 0 && buyAmount > 0 && buyGood != null) {
    for (Unit u : currentRegion.units()) {
      int remainingFromSkill = maxAmount - totalVolume + 1 - 1;
       if (currentFactions.containsKey(u.getFaction())) {
      // Berechne noetige Geldmenge fuer Einkauf (einfacher: Modulorechnung, aber wegen
         costs += u.getRace().getMaintenance() * u.getPersons();
      // Rundungsfehler nicht umsetzbar)
      }
      int remainingToBuy = buyAmount;
    }
      skillNeeded += buyAmount;
    int zusatz1 = 0, zusatz2 = 0;
       if (remainingToBuy > remainingFromSkill) {
    if (tokens.length > 1) {
         buyAmount += (remainingFromSkill - remainingToBuy);
      try {
        remainingToBuy = remainingFromSkill;
        zusatz1 = Integer.parseInt(tokens[1]);
      }
         if (tokens.length > 2) {
      int priceFactor = 1;
           zusatz2 = Integer.parseInt(tokens[2]);
      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;
         }
         }
      } catch (NumberFormatException e) {
        addNewError("Zahl erwartet");
       }
       }
    }
    addNeed("Silber", currentUnit, costs + zusatz1, costs + zusatz1 + zusatz2,
        DEPOT_SILVER_PRIORITY);


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


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


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


    // Einheit gut genug?
     if (items != null) {
     if (skillNeeded > maxAmount && warning.contains(C_SKILL)) {
      for (Item item : items) {
      addNewError("Einheit hat zu wenig Handelstalent (min: " + maxAmount / 10 + " < " + (int) Math
        Supply supply = getSupply(item.getOrderName(), currentUnit);
           .ceil(skillNeeded / 10.0) + ")");
        if (supply != null) {
           supply.setPriority(priority);
        }
      }
     }
     }
  }


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


   /**
   /**
   * <code>// $cript Quartiermeister [[Menge1 Gut 1]...]</code>: learn perception, allow listed
   * <code>Kapazitaet FUSS|PFERD|SCHIFF|amount</code> -- ensure that a unit's capacity is not
   * amount of goods. If other goods are detected, do not confirm orders.
   * exceeded
   */
   */
   protected void commandQuartermaster(String[] tokens) {
   protected void commandCapacity(String[] tokens) {
     learn(getCurrentUnit(), Collections.singleton(new Skill(world.getRules().getSkillType(
     int capacity = 0;
        EresseaConstants.S_WAHRNEHMUNG), 30, 10, 1, true)));
    int slack = 0;
     try {
    if (tokens.length < 2) {
       for (Item item : getCurrentUnit().getItems()) {
      addNewError("zu wenig Argumente");
         boolean okay = false;
      return;
        if (item.getItemType().getID().equals(EresseaConstants.I_USILVER) && item
     } else if (tokens.length > 2) {
            .getAmount() < 1000) {
       if (tokens.length == 4 && "-".equals(tokens[2])) {
           okay = true;
         try {
          slack = Integer.parseInt(tokens[3]);
        } catch (NumberFormatException e) {
           addNewError("Zahl erwartet");
         }
         }
         for (int i = 1; !okay && i < tokens.length - 1; i += 2) {
      } else {
          if (item.getName().equals(tokens[i + 1])) {
         addNewError("zu viele Argumente");
            if (item.getAmount() <= Integer.parseInt(tokens[i])) {
      }
              okay = true;
    }
              break;
    MovementEvaluator movement = world.getGameSpecificStuff().getMovementEvaluator();
            }
    int load = movement.getModifiedLoad(currentUnit);
           }
    try {
      capacity = Integer.parseInt(tokens[1]);
    } catch (NumberFormatException e) {
      if (HORSEOrder.equals(tokens[1])) {
        capacity = movement.getPayloadOnHorse(currentUnit);
      } else if (FOOTOrder.equals(tokens[1])) {
        capacity = movement.getPayloadOnFoot(currentUnit);
      } else if (SHIPOrder.equals(tokens[1])) {
        Ship ship = currentUnit.getModifiedShip();
        if (ship == null || ship.getOwnerUnit() != currentUnit) {
          capacity = Integer.MAX_VALUE;
          addNewWarning("Einheit ist nicht Kapitän.");
        } else {
           capacity = ship.getMaxCapacity() - ship.getModifiedLoad() + load;
         }
         }
         setConfirm(getCurrentUnit(), okay);
      } else {
         addNewError("Zahl erwartet");
        return;
      }
      if (isCapacityError(capacity)) {
        addNewWarning("Zu viele Pferde.");
        capacity = load;
       }
       }
    } catch (NumberFormatException e) {
      addNewError("ungültige Zahl ");
     }
     }
     setConfirm(getCurrentUnit(), true);
 
     // int load = movement.getModifiedLoad(currentUnit);
    capacities.put(currentUnit, capacity - slack - load);
  }
 
  private boolean isCapacityError(int capacity) {
    return capacity == MovementEvaluator.CAP_NO_HORSES
        || capacity == MovementEvaluator.CAP_UNSKILLED;
   }
   }


   /**
   /**
   * <code>// $cript Sammler [frequenz]</code>: collect herbs if there are at least "viele",
   * <code>// $script +x [Arguments...]</code><br />
   * research herbs every frequenz rounds.
  * 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 commandCollector(String[] tokens) {
   protected void commandWarning(String[] tokens) {
    if (tokens.length > 2) {
     int delay = -1;
      addNewError("zu viele Argumente");
     try {
    }
       delay = Integer.parseInt(tokens[0].substring(1));
     int modulo = Integer.MAX_VALUE;
    } catch (NumberFormatException e) {
     if (tokens.length > 1) {
      addNewError("Zahl erwartet");
       try {
      return;
        modulo = Integer.parseInt(tokens[1]);
      } catch (NumberFormatException e) {
        addNewError("ungültige Zahl " + tokens[1]);
        return;
      }
     }
     }
 
     if (delay <= 1) {
     if (getCurrentRegion().getRegionType().isOcean()) {
      StringBuilder warning = new StringBuilder();
       addNewError("Sammeln nicht möglich!");
      String foo = currentOrder.substring(currentOrder.indexOf("+"));
       return;
       warning.append(foo.indexOf(" ") >= 0 ? foo.substring(foo.indexOf(" ") + 1) : "");
       addNewWarning(warning.toString(), false);
     }
     }
     removeOrdersLike(MAKEOrder + " " + "[^T].*", true);
     if (delay != 1) {
    removeOrdersLike(getResearchOrder() + ".*", true);
      StringBuilder newCommand = new StringBuilder(createScriptCommand()).append("+");
    if (modulo != Integer.MAX_VALUE && (world.getDate().getDate() % modulo == 0
      newCommand.append(Math.max(0, delay - 1));
        || getCurrentRegion().getHerbAmount() == null || (!getCurrentRegion().getHerbAmount()
      for (int i = 1; i < tokens.length; ++i) { // skip "+x"
            .equals("viele") && !getCurrentRegion().getHerbAmount().equals("sehr viele")))) {
        newCommand.append(" ").append(tokens[i]);
       addNewOrder(getResearchOrder(), true);
       }
    } else {
       addNewOrder(newCommand.toString(), true);
       addNewOrder(MAKEOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER"), true);
     }
     }
   }
   }


   /**
   /**
   * <code>KrautKontrolle [[[direction...] PAUSE]...]</code> move until the next PAUSE, if pause is
   * <code>// $cript Lerne Talent1 Stufe1 [Max1] [[Talent2 Stufe2 [Max2]...]</code><br />
   * reached, research herbs.
  * Tries to learn skills in given ratio. For example,
  * <code>// $cript Lerne Hiebwaffen 10 99 Ausdauer 5 3</code> tries to learn to (Hiebwaffen 2, Ausdauer
   * 1), (Hiebwaffen 4, Ausdauer 2), and so forth, but Ausdauer only until level 3 is reached.
   */
   */
   protected void commandControl(String[] tokens) {
   protected void commandLearn(String[] tokens) {
     for (Order o : getCurrentUnit().getOrders2()) {
     if (tokens.length < 3) {
       String order = o.getText();
      addNewError("falsche Anzahl Argumente");
      if (order.startsWith(ROUTEOrder)) {
       return;
         if (order.substring(order.indexOf(" ")).trim().startsWith(PAUSEOrder)) {
    }
           // end of route, FORSCHE
    List<SkillSpec> targetSkills = new LinkedList<SkillSpec>();
           addNewOrder(currentOrder, false);
 
           addNewOrder(getResearchOrder(), true);
    for (int i = 1; i < tokens.length;) {
           removeOrdersLike(ROUTEOrder + ".*", true);
      int j = i;
      try {
         if (i < tokens.length - 1) {
          SkillType skill = world.getRules().getSkillType(tokens[j++]);
          int level = Integer.parseInt(tokens[j]), max = 99;
           j++; // no exception
           if (i < tokens.length - 2) {
            try {
              max = Integer.parseInt(tokens[j]);
              j++;
            } catch (NumberFormatException e) {
              max = 99;
            }
          }
           if (skill == null) {
            addNewError("unbekanntes Talent " + tokens[i]);
           } else {
            targetSkills.add(new SkillSpec(skill, level, max));
          }
         } else {
         } else {
           // continue on route
           addNewError("unerwartetes Token " + tokens[i]);
          addNewOrder(currentOrder, false);
         }
         }
         return;
      } catch (NumberFormatException e) {
         addNewError("ungültige Stufe " + tokens[i]);
      } finally {
        i = Math.max(j, i + 1);
       }
       }
     }
     }
     // no route order -- create new one
     learn(currentUnit, targetSkills);
    removeOrdersLike(getResearchOrder() + ".*", true);
  }
    StringBuilder newOrder = new StringBuilder();
 
    newOrder.append(PCOMMENTOrder).append(" ").append(scriptMarker).append(" ").append(tokens[0]);
  /**
     StringBuilder moveOrder = new StringBuilder(ROUTEOrder);
  * <code>Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code><br />
    boolean pause = false;
  * Tries to learn best skill and acquire equipment. If no skill is given, best weapon skill is
    for (int i = 1; i < tokens.length; ++i) {
  * selected. If no weapon is given, best matching weapon is acquired and so on. Preference is
      if (!pause) {
  * always for RESERVEing stuff the unit already has. Waffe, Schild, Rüstung can be "null" if
        // add move order until first PAUSE
  * nothing should be reserved or "best" (the default behavior).<br />
        moveOrder.append(" ").append(tokens[i]);
  * Warning can be:<br />
      } else {
  * nie: issues no warnings at all,
         // add to $cript order after first PAUSE
  * Talent: only warns if no skill is given and no best skill exists,
         newOrder.append(" ").append(tokens[i]);
  * Waffe: additionally warn if no weapon can be acquired,
      }
  * Schild: additionally warn if no shield,
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
  * Rüstung: additionally warn if no armor.
        pause = true;
  * Default warning level is "Waffe".
      }
  */
  protected void commandSoldier(String[] tokens) {
     String warning = tokens[tokens.length - 1];
    if (!(Warning.W_NEVER.equals(warning) || Warning.W_SKILL.equals(warning) || Warning.W_WEAPON
         .equals(warning)
         || Warning.W_SHIELD.equals(warning) || Warning.W_ARMOR.equals(warning))) {
      warning = null;
     }
     }
     moveOrder.append(" ").append(PAUSEOrder);
     String skill = null;
    pause = false;
     if (tokens.length > 1 + (warning == null ? 0 : 1)) {
     for (int i = 1; i < tokens.length; ++i) {
       skill = tokens[1];
      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);
     String weapon = null;
    addNewOrder(moveOrder.toString(), true);
     if (tokens.length > 2 + (warning == null ? 0 : 1)) {
  }
       weapon = tokens[2];
 
  /**
  * <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 shield = null;
     String race = null;
     if (tokens.length > 3 + (warning == null ? 0 : 1)) {
    int max = Integer.MAX_VALUE;
       shield = tokens[3];
     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");
        }
      }
     }
     }
 
     String armor = null;
     Race effRace = race == null ? getCurrentUnit().getRace() : helper.getRace(race);
     if (tokens.length > 4 + (warning == null ? 0 : 1)) {
     if (effRace == null) {
       armor = tokens[4];
       addNewError("Unbekannte Rasse");
      return;
     }
     }


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


     int amount = world.getGameSpecificRules().getRecruitmentLimit(getCurrentUnit(), effRace);
     // if (NULL.equals(skill)) {
    // skill = null;
    // }
    // if (NULL.equals(weapon)) {
    // weapon = null;
    // }
    // if (NULL.equals(shield)) {
    // shield = null;
    // }
    // if (NULL.equals(armor)) {
    // armor = null;
    // }


     if (getCurrentUnit().getPersons() + amount >= max) {
     soldier(currentUnit, skill, weapon, shield, armor, warning);
      addNewWarning("Rekrutierung fertig");
  }
      amount = Math.min(max - getCurrentUnit().getPersons(), amount);
    }


     if (amount < min) {
  /**
       addNewError("Nicht genug Rekruten");
  * <code>// $cript BerufBotschafter [minimum money] [Talent|command]</code> -- if we have at least
  * minimum money (default 100), learn skill or execute command<br />
  *
  */
  protected Collection<String> commandEmbassador(String[] tokens) {
    SkillType skill = null;
    int minimum = 100;
    int actionToken = 1;
     if (tokens.length > 1) {
       try {
        minimum = Integer.parseInt(tokens[1]);
        actionToken = 2;
      } catch (NumberFormatException e) {
        actionToken = 1;
      }
     }
     }
 
    String mode = "";
     if (amount > 0) {
     if (tokens.length > actionToken) {
       if (effRace.getRecruitmentCosts() > 0) {
       if (WORKOrder.equals(tokens[actionToken])) {
         int costs = amount * effRace.getRecruitmentCosts();
         mode = WORKOrder;
        int maxCosts = max == Integer.MAX_VALUE ? costs : (max - getCurrentUnit().getPersons())
      } else if (ENTERTAINOrder.equals(tokens[actionToken])) {
            * effRace.getRecruitmentCosts();
        mode = ENTERTAINOrder;
        addNeed("Silber", getCurrentUnit(), costs, maxCosts, DEFAULT_PRIORITY);
      } else if (TAXOrder.equals(tokens[actionToken])) {
        mode = TAXOrder;
       } else {
       } else {
         addNewWarning("Rekrutierungskosten unbekannt");
         --actionToken;
      }
      ++actionToken;
    }
    if (tokens.length > actionToken) {
      skill = getSkillType(tokens[actionToken]);
    } else {
      skill = getSkillType(EresseaConstants.S_WAHRNEHMUNG.toString());
      if (skill == null) {
        skill = getSkillType(EresseaConstants.S_AUSDAUER.toString());
       }
       }
      getRecruitOrder(amount, effRace);
      addNewOrder(getRecruitOrder(amount, effRace != getCurrentUnit().getRace() ? effRace : null),
          true);
     }
     }
  }


  /**
    commandClear(new String[] { "Loeschen" });
  * <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);


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


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


   protected boolean hasWork() {
   private boolean isGuarded(Region currentRegion2, Unit currentUnit2) {
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(getLocalizedOrder(
     for (Unit guard : currentRegion.getGuards()) {
        EresseaConstants.OC_WORK, WORKOrder));
      if (!Units.isAllied(guard.getFaction(), currentUnit.getFaction(),
          EresseaConstants.A_GUARD))
        return true;
    }
    return false;
   }
   }


   protected void initSupply() {
   protected void commandMonitor(String[] tokens) {
     if (needMap == null) {
     if (tokens.length > 1) {
//      needMap = new LinkedHashMap<String, Map<Integer, Map<Unit, Need>>>();
       addNewError("zu viele Argumente");
      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()) {
    // check if region units are allowed
      if (currentFactions.containsKey(u.getFaction())) {
    Map<Faction, List<Unit>> warnings = new HashMap<Faction, List<Unit>>();
        for (Item item : u.getItems()) {
     for (Unit u : currentRegion.units()) {
          putSupply(item.getOrderName(), u, item.getAmount());
      if (u.getFaction() != currentUnit.getFaction()) {
        if (!(allowedUnits.containsKey(u.getFaction()) && (allowedUnits.get(u.getFaction())
            .contains(u.getID()) || allowedUnits.get(u.getFaction()).contains(
                currentRegion.getZeroUnit().getID())))) {
          if (!(requiredUnits.containsKey(u.getFaction()) && requiredUnits.get(u.getFaction())
              .contains(u.getID()))) {
            List<Unit> list = warnings.get(u.getFaction());
            if (list == null) {
              list = new LinkedList<Unit>();
              warnings.put(u.getFaction(), list);
            }
            list.add(u);
          }
         }
         }
      }
    }


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


        // // subtract reserved items from supply amount of unit
    // check if required units are present
        // for (ReserveRelation relation : u.getRelations(ReserveRelation.class)) {
    for (Faction f : requiredUnits.keySet()) {
        // Supply supply = getSupply(relation.itemType.getName(), u);
      for (UnitID id : requiredUnits.get(f)) {
         // if (supply != null && relation.source == u) {
         Unit u = world.getUnit(id);
        // supply.setAmount(supply.getAmount() - relation.amount);
         if (u == null || u.getRegion() != currentUnit.getRegion()) {
         // }
          addNewWarning("Einheit " + id + " der Partei " + f + " nicht mehr da.");
        // }
         }
        // // 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);
         // }
        // }
       }
       }
     }
     }
   }
   }


  /**
   protected void commandAllow(String[] tokens) {
  * Adds a supply to the supplyMap
     if (tokens.length < 3) {
  *
       addNewError("zu wenige Argumente");
  * @param item
       return;
  * @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;
  }


  /**
    Faction faction = helper.getFaction(tokens[1]);
  * Tries to satisfy all needs in the current needMap by adding GIVE orders to supplyers.
    Map<Faction, Set<UnitID>> map;
  */
     if (tokens[0].equals("Erlaube")) {
  protected void satisfyNeeds() {
       map = allowedUnits;
     for (String item : needMap.keySet()) {
    } else {
       // sort supplies by priority
       map = requiredUnits;
//       Map<Unit, Supply> itemSupply = supplyMap.get(item);
    }
       Map itemSupply = supplyMap.get(item);
    if (faction == null) {
       if (itemSupply == null) {
      addNewError("unbekannte Partei");
         itemSupply = Collections.emptyMap();
    } else {
       Set<UnitID> set = map.get(faction);
       if (set == null) {
         set = new HashSet<UnitID>();
        map.put(faction, set);
      }
      if (ALLOrder.equals(tokens[2])) {
        set.add(currentRegion.getZeroUnit().getID());
        if (tokens.length > 3) {
          addNewError("zu viele Argumente");
        }
       } else {
       } else {
        Supply[] sorted = null;
         for (int i = 2; i < tokens.length; ++i) {
        sorted = itemSupply.values().toArray(new Supply[0]);
           try {
 
             set.add(UnitID.createUnitID(tokens[i], world.base));
        // this causes problems in BeansShell; I don't know why
          } catch (NumberFormatException exc) {
        // Arrays.sort(sorted);
            addNewError("Ungültige Einheitennummer " + tokens[i]);
        // 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();
  * <code>// $cript Ernaehre [amount] [nie]</code> -- Earn as much money as possible (or the specified
 
  * amount), Versorge {@value #DEFAULT_EARN_PRIORITY}
//      Map<Integer, Map<Unit, Need>> pMap = needMap.get(item);
  */
       Map pMap = needMap.get(item);
  protected void commandEarn(String[] tokens) {
 
    int amount = -1;
//       List<Integer> prios = new ArrayList<Integer>();
    Warning warning = new Warning(true);
      List prios = new ArrayList();
    tokens = warning.parse(tokens);
       for (Integer key : pMap.keySet()) {
    if (tokens.length > 1) {
         prios.add(key);
       try {
        amount = Integer.parseInt(tokens[1]);
       } catch (NumberFormatException e) {
        addNewError("ungültige Zahl " + tokens[1]);
        return;
       }
      if (tokens.length > 2) {
         addNewError("zu viele Argumente");
       }
       }
      Collections.sort(prios);
    }
      Collections.reverse(prios);


      for (Integer prio : prios) {
    // Ernaehre includes Versorge
//         Map<Unit, Need> nMap = pMap.get(prio);
    commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });
        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
    // remove previous orders
        for (Need need : nMap.values()) {
    removeOrdersLike(TAXOrder + ".*", true);
          giveNeed(need, true);
    removeOrdersLike(ENTERTAINOrder + ".*", true);
        }
    removeOrdersLike(WORKOrder + ".*", true);


         // add warnings for unsatisfied needs
    int maxWorkers =
         for (Need need : nMap.values()) {
         Utils.getIntValue(world.getGameSpecificRules().getMaxWorkers(currentRegion), 0);
          if (need.getMinAmount() > 0 && need.getWarning().contains(C_AMOUNT)) {
    int workers = Math.min(maxWorkers, currentRegion.getPeasants());
            addWarning(need.getUnit(), "braucht " + need.getMinAmount() + " mehr " + need
    Skill entertaining =
                .getItem());
         currentUnit
          }
            .getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_UNTERHALTUNG));
        }
    Skill taxing =
        currentUnit.getModifiedSkill(world.getRules().getSkillType(
            EresseaConstants.S_STEUEREINTREIBEN));
    int entertain = 0, entertain2 = 0, tax = 0, tax2 = 0;
    if (entertaining != null && hasEntertain()) {
      entertain2 = 20 * entertaining.getLevel() * currentUnit.getPersons();
      entertain = Math.max(0, Math.min(currentRegion.maxEntertain(), entertain2));
    }
    if (taxing != null && isSoldier(currentUnit)) {
      tax2 = 20 * taxing.getLevel() * currentUnit.getPersons();
      tax = Math.min(currentRegion.getSilver(), tax2);
    }


        // try to satisfy max needs, ignore infinite needs first
    if (tax > entertain) {
        for (Need need : nMap.values()) {
      addNewOrder(TAXOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " " + tax + ">"
          if (need.getAmount() != Integer.MAX_VALUE) {
          + entertain, true);
            reserveNeed(need, false, reserves);
      if (tax >= currentRegion.getSilver() + workers * 10 - currentRegion.getPeasants() * 10) {
          }
        addNewWarning("Bauern verhungern");
      }
      if (tax2 * 2 > tax * 3) {
        if (warning.contains(Warning.W_AMOUNT)) {
          addNewWarning("Treiber unterbeschäftigt " + tax2 + ">" + tax);
         }
         }
 
         entertain = currentRegion.maxEntertain();
         for (Need need : nMap.values()) {
      }
          if (need.getAmount() != Integer.MAX_VALUE) {
    } else if (entertain > 0) {
            giveNeed(need, false);
      addNewOrder(ENTERTAINOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " "
          }
          + entertain + ">" + tax, true);
        }
      if (amount > currentRegion.maxEntertain() * 1.1) {
 
        if (warning.contains(Warning.W_NEVER)) {
        // now, finally, satisfy infinite needs
          addNewWarning("zu viele Arbeiter");
        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);
          }
         }
         }
 
      } else if (entertain2 * 2 > entertain * 3) {
        // add messages for unsatisfied needs
        if (warning.contains(Warning.W_AMOUNT)) {
        for (Need need : nMap.values()) {
          addNewWarning("Unterhalter unterbeschäftigt " + entertain2 + ">" + entertain);
          if (need.getMinAmount() <= 0 && need.getMaxAmount() > 0 && need
              .getMaxAmount() != Integer.MAX_VALUE) {
            need.getUnit().addOrder("; braucht " + need.getMaxAmount() + " mehr " + need.getItem(),
                false);
          }
         }
         }
        entertain = currentRegion.maxEntertain();
       }
       }
      for (Unit u : reserves.keySet()) {
    } else {
 
      addNewOrder(WORKOrder + " " + (amount > 0 ? amount : ""), true);
        int amount = reserves.get(u);
      if ((maxWorkers - workers) * 10 < Math.min(amount, currentUnit.getModifiedPersons() * 10)) {
        if (amount > 0) {
        if (warning.contains(Warning.W_AMOUNT)) {
          if (amount == u.getPersons()) {
           addNewWarning("zu viele Arbeiter");
            u.addOrder(getReserveOrder(u, item // + COMMENTOrder + need.toString()
                , 1, true), false);
          } else {
            u.addOrder(getReserveOrder(u, item, amount, false), false);
           }
         }
         }
       }
       }
     }
     }
 
    setConfirm(currentUnit, true);
   }
   }


   /**
   /**
   * Tries to satisfy (minimum) need by a RESERVE order
   * <code>// $cript Handel Menge|xM [xR] [ALLES | Verkaufsgut...] Warnung</code>: trade luxuries,
  *
   * Versorge {@value #DEFAULT_EARN_PRIORITY}. Menge M may be a multipler ("x2" of the region
   * @param need
   * maximum). xR: reserve goods for R rounds. Warnung can be "Talent", "Menge", or "nie"<br />
   * @param min
  * @param reserves
   */
   */
//   protected void reserveNeed(Need need, boolean min, Map<Unit, Integer> reserves) {
   protected void commandTrade(String[] tokens) {
  protected void reserveNeed(Need need, boolean min, Map reserves) {
     if (tokens.length < 2) {
     int amount = min ? need.getMinAmount() : need.getAmount();
      addNewError("zu wenige Argumente");
    Supply supply = getSupply(need.getItem(), need.getUnit());
    if (supply == null)
       return;
       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);
      }
     }
     }
  }


  /**
    commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });
  * Tries to satisfy (minimum) need by a give order from supplyers.
 
  *
     Warning warning = new Warning(true);
  * @param need
     tokens = warning.parse(tokens);
  * @param min
 
  */
    int volume = currentRegion.maxLuxuries();
  protected void giveNeed(Need need, boolean min) {
 
     int amount = min ? need.getMinAmount() : need.getAmount();
    int buyAmount = -1;
     if (amount > 0) {
    try {
      if (!supplyMap.containsKey(need.getItem()))
      if (tokens[1].substring(0, 1).equalsIgnoreCase("x")) {
        return;
        buyAmount = Integer.parseInt(tokens[1].substring(1));
      for (Supply supply : supplyMap.get(need.getItem()).values()) {
        buyAmount = volume * buyAmount;
        if (supply.getUnit() != need.getUnit() && (min || supply.priority > 0)) {
      } else {
          int giveAmount = Math.min(amount, supply.getAmount());
         buyAmount = Integer.parseInt(tokens[1]);
          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;
        }
       }
       }
    } catch (NumberFormatException e) {
      addNewError("ungültige Zahl " + tokens[1]);
      return;
     }
     }
  }


  /**
    Skill buySkill =
  * Returns the total supply for an item.
        currentUnit.getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_HANDELN));
  *
    if (buySkill == null) {
  * @param item Order name of the supplied item
      addNewError("kein Handelstalent");
  * @return The supply or 0, if none has been registered.
      return;
  */
    }
  protected int getSupply(String item) {
 
//     Map<Unit, Supply> map = supplyMap.get(item);
    if (currentRegion.getPrices() == null) {
    Map map = supplyMap.get(item);
      addNewError("kein Handel möglich");
    int goodAmount = 0;
      return;
    if (map != null) {
    }
      for (Supply s : map.values()) {
 
        goodAmount += s.getAmount();
    LuxuryPrice buyGood = null;
     for (Entry<StringID, LuxuryPrice> entry : currentRegion.getPrices().entrySet()) {
      LuxuryPrice price = entry.getValue();
      if (price.getPrice() < 0) {
        buyGood = price;
      } else {
        if (currentRegion.getOldPrices() != null
            && currentRegion.getOldPrices().get(entry.getKey()) != null
            && price.getPrice() < currentRegion.getOldPrices().get(entry.getKey()).getPrice()) {
          addNewWarning("Preis gesunken: " + price.getItemType());
        }
       }
       }
     }
     }
    return goodAmount;
  }


  /**
    removeOrdersLike(SELLOrder + ".*", true);
  * Returns a supply of a unit for an item.
     removeOrdersLike(BUYOrder + ".*", true);
  *
 
  * @param item Order name of the supplied item
     // Gueterzahl intitialisieren
  * @param unit
     int totalVolume = 0;
  * @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);
  }


  /**
    if (buyAmount > 0 && (volume <= 0 || buyGood == null)) {
  * Adds the specified amounts to the need of a unit for an item to the needMap.
      addNewError("kein Handel möglich");
  *
    }
  * @param item Order name of the required item
 
  * @param unit
    LinkedList<String> orders = new LinkedList<String>();
  * @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));
  }


  /**
    int maxAmount = buySkill.getLevel() * currentUnit.getPersons() * 10;
  * Adds the specified amounts to the need of a unit for an item to the needMap.
     int skillNeeded = 0;
  *
  * @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 (volume > 0 && buyGood != null) {
       if (maxAmount == Integer.MAX_VALUE) {
       int reserveMultiplier = 1, reserveToken = 0;
        need.setAmount(maxAmount);
      try {
       } else {
        if (tokens.length > 2 && tokens[2].substring(0, 1).equalsIgnoreCase("x")) {
         need.reduceAmount(-maxAmount);
          reserveToken = 1;
          reserveMultiplier = Integer.parseInt(tokens[2].substring(1));
        }
       } catch (NumberFormatException e) {
         addNewError("ungültige Zahl " + tokens[2]);
        return;
       }
       }
    }
    if (need.getMinAmount() != Integer.MAX_VALUE) {
      if (minAmount == Integer.MAX_VALUE) {
        need.setMinAmount(minAmount);
      } else {
        need.reduceMinAmount(-minAmount);
      }
    }
  }


  /**
      List<String> goods = new LinkedList<String>();
  * Returns a need of a unit for an item.
      // Verkaufsbefehl setzen, wenn notwendig
  *
      if (tokens.length > 2 + reserveToken && ALLOrder.equals(tokens[2 + reserveToken])) {
  * @param item Order name of the required item
        if (world.getRules().getItemCategory("luxuries") == null) {
  * @param unit
          addNewError("Spiel kennt keine Luxusgüter");
  * @return The need or <code>null</code> if none has been registered.
        } else {
  */
          for (ItemType luxury : world.getRules().getItemTypes()) {
  protected Need getNeed(String item, Unit unit, int priority) {
            if (!luxury.equals(buyGood.getItemType())
//     Map<Integer, Map<Unit, Need>> map = needMap.get(item);
                && world.getRules().getItemCategory("luxuries").equals(luxury.getCategory())) {
    Map map = needMap.get(item);
              goods.add(luxury.getOrderName());
    if (map == null)
            }
      return null;
          }
//    Map<Unit, Need> pMap = map.get(priority);
        }
    Map pMap = map.get(priority);
      } else {
    if (pMap == null)
        for (int i = 2 + reserveToken; i < tokens.length; ++i) {
       return null;
          goods.add(tokens[i]);
        }
       }


    return pMap.get(unit);
      for (String luxury : goods) {
  }
        int goodAmount = volume;
        if (goodAmount > getSupply(luxury)) {
          goodAmount = getSupply(luxury);
        }
        skillNeeded += goodAmount;


  /**
        if (goodAmount > maxAmount - totalVolume) {
  * Marks the unit as soldier. Learns its best weapon skill. Reserves suitable weapon, armor, and
          goodAmount = maxAmount - totalVolume;
  * 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();
        addNeed(luxury, currentUnit, ALLOrder.equals(tokens[2 + reserveToken]) ? goodAmount
            : volume, volume
                * reserveMultiplier,
            TRADE_PRIORITY, warning);
        if (goodAmount > 0) {


    SkillType weaponSkill = sWeaponSkill == null ? null : rules.getSkillType(StringID.create(
          if (goodAmount == volume) {
        sWeaponSkill));
            orders.add(SELLOrder + " " + ALLOrder + " " + luxury);
    ItemType weapon = sWeapon == null ? null : rules.getItemType(StringID.create(sWeapon));
          } else {
    ItemType armor = sArmor == null ? null : rules.getItemType(StringID.create(sArmor));
            orders.add(SELLOrder + " " + goodAmount + " " + luxury);
    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();
         }
         }
        totalVolume += goodAmount;
       }
       }
      if (weaponSkill == null && !W_NEVER.equals(warning)) {
 
        addNewWarning("kein Kampftalent");
        return;
      }
     }
     }


//     ArrayList<Item> weapons = new ArrayList<Item>();
    // Soll eingekauft werden?
    ArrayList weapons = new ArrayList();
     if (volume > 0 && buyAmount > 0 && buyGood != null) {
     if (weapon == null) {
       int remainingFromSkill = maxAmount - totalVolume + 1 - 1;
       for (Item item : u.getItems()) {
      // Berechne noetige Geldmenge fuer Einkauf (einfacher: Modulorechnung, aber wegen
        if (isUsable(item, weaponSkill)) {
      // Rundungsfehler nicht umsetzbar)
          weapons.add(item);
      int remainingToBuy = buyAmount;
         }
      skillNeeded += buyAmount;
      if (remainingToBuy > remainingFromSkill) {
        buyAmount += (remainingFromSkill - remainingToBuy);
         remainingToBuy = remainingFromSkill;
       }
       }
       if (weapons.isEmpty()) {
       int priceFactor = 1;
         for (ItemType type : rules.getItemTypes()) {
      int money = 0;
           if (isUsable(type, weaponSkill)) {
      while (remainingToBuy > 0) {
            weapons.add(new Item(type, 0));
         if (remainingToBuy > volume) {
            break;
           remainingToBuy -= volume;
          }
          money -= (volume * priceFactor * buyGood.getPrice()); // price is negative
          priceFactor++;
        } else {
          money -= (remainingToBuy * priceFactor * buyGood.getPrice());
          remainingToBuy = 0;
         }
         }
       }
       }
       if (weapons.isEmpty()) {
 
        addNewError("keine passenden Waffen bekannt für " + weaponSkill);
       addNeed("Silber", currentUnit, money, money, TRADE_PRIORITY);
       }
 
    } else {
       // Einkaufsbefehl setzen, wenn notwendig
       if (u.getItem(weapon) != null) {
       if (buyAmount > 0) {
         weapons.add(u.getItem(weapon));
         orders.addFirst(BUYOrder + " " + buyAmount + " " + buyGood.getItemType().getOrderName());
      } else {
         totalVolume += buyAmount;
         addNewError("keine " + weapon);
       }
       }
     }
     }


     // note that "shield" is a subcategory of "armour"
     // add orders (buy orders first)
//    ArrayList<Item> shields = findItems(shield, u, "shield");
     for (String order : orders) {
     ArrayList shields = findItems(shield, u, "shield");
       addNewOrder(order, true);
    if (shields.isEmpty()) {
       addNewError("keine Schilde bekannt");
     }
     }


//    ArrayList<Item> armors = findItems(armor, u, "armour");
    // Einheit gut genug?
    ArrayList armors = findItems(armor, u, "armour");
     if (skillNeeded > maxAmount && warning.contains(Warning.C_SKILL)) {
    if (armors.isEmpty()) {
      addNewError("Einheit hat zu wenig Handelstalent (min: " + maxAmount / 10 + " < "
      addNewError("keine Rüstungn bekannt");
          + (int) Math.ceil(skillNeeded / 10.0) + ")");
     }
     }


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


     if (!NULL.equals(sWeapon) && !reserveEquipment(weapon, weapons, !W_NEVER.equals(warning)
     for (Order order : currentUnit.getOrders2()) {
        && !W_SKILL.equals(warning))) {
      String text = order.getText();
      addNewError("konnte Waffe nicht reservieren");
      if (text.startsWith(ROUTEOrder + " " + PAUSEOrder)) {
        addNewWarning("Route beendet");
      }
     }
     }
     if (!NULL.equals(sShield) && !reserveEquipment(shield, shields, W_ARMOR.equals(warning)
     removeOrdersLike(ROUTEOrder + " " + PAUSEOrder + ".*", true);
        || W_SHIELD.equals(warning))) {
 
       addNewError("konnte Schilde nicht reservieren");
    String[] soldierTokens;
    if (tokens.length > 3) {
      // Soldat [Talent [Waffe [Schild [Rüstung]]]]
      soldierTokens = new String[tokens.length - 1];
      soldierTokens[0] = tokens[0];
      for (int i = 1; i < tokens.length - 2; ++i) {
        soldierTokens[i] = tokens[i + 2];
      }
    } else {
       soldierTokens = (tokens[0] + " " + SOLDIER_HELMSMAN).split(" ");
     }
     }
     if (!NULL.equals(sArmor) && !reserveEquipment(armor, armors, W_ARMOR.equals(warning))) {
     if (LEARN_HELMSMAN != null) {
      addNewError("konnte Rüstung nicht reservieren");
      addToTeachPlugin(currentUnit, parseLearn(LEARN_HELMSMAN));
     }
     }
    commandSoldier(soldierTokens);
   }
   }


   /**
   /**
   * Tries to learn the skills at the ratio reflected in the skills argument.
   * <code>// $cript Mannschaft [Talent ...]</code> -- be crew and learn<br />
   *
  *
   * @param u
  * If given, uses the same parameters as
   * @param targetSkills
  * commandSoldier, starting with Talent.
  * Otherwise uses parameters specified in {@link #SOLDIER_CREW}.
   *  
   * If {@link #LEARN_CREW} is not null, adds those parameters to the teach plugin.
  *
   * @param tokens
   */
   */
//  protected void learn(Unit u, Collection<Skill> targetSkills) {
   protected void commandCrew(String[] tokens) {
   protected void learn(Unit u, Collection targetSkills) {
     String[] soldierTokens;
 
     boolean legacy = false;
     // find skill with maximum priority
     if (tokens.length == 3) {
     double maxWeight = 0;
      try {
     Skill maxSkill = null;
        Integer.parseInt(tokens[2]);
    StringBuilder comment = new StringBuilder(";");
        addNewOrder(createScriptCommand() + tokens[0], true);
    for (Skill skill : targetSkills) {
        legacy = true;
      double weight = calcSkillWeight(u, skill, targetSkills);
       } catch (NumberFormatException e) {
      comment.append(" ").append(skill.toString()).append(" ").append(weight);
         //
       if (weight > maxWeight) {
         maxWeight = weight;
        maxSkill = skill;
       }
       }
     }
     }
 
    if (!legacy) {
    addNewOrder(comment.toString(), true);
      addNewOrder(currentOrder, false);
 
    }
     if (maxSkill == null) {
     if (tokens.length > 1 && !legacy) {
       addNewError("kein Kampftalent");
      soldierTokens = new String[tokens.length];
      soldierTokens[0] = tokens[0];
       for (int i = 1; i < tokens.length; ++i) {
        soldierTokens[i] = tokens[i];
      }
     } else {
     } else {
       removeOrdersLike(LEARNOrder + ".*", true);
       soldierTokens = (tokens[0] + " " + SOLDIER_CREW).split(" ");
      removeOrdersLike(TEACHOrder + ".*", true);
      addNewOrder(LEARNOrder + " " + maxSkill.getName(), true);
     }
     }
  }
     if (LEARN_CREW != null) {
 
       addToTeachPlugin(currentUnit, parseLearn(LEARN_CREW));
  /**
  * 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;
      }
      // }
     }
     }
    commandSoldier(soldierTokens);


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


     if (maxMult > 0) {
  private Collection<SkillSpec> parseLearn(String learnLine) {
       // calc max normalized learning weeks
    OrderedHashtable<SkillSpec, Object> parsed = new OrderedHashtable<SkillSpec, Object>();
      double maxWeeks = 0;
    StringTokenizer tokens = new StringTokenizer(learnLine);
       for (Skill skill2 : targetSkills) {
     while (tokens.hasMoreTokens()) {
         int currentLevel = Math.max(1, getSkillLevel(u, skill2.getSkillType()));
       String t = tokens.nextToken();
         // if (lev < getMax(skill2)) {
       if (t.matches("[A-Z].*")) {
         double weeks = skill2.getLevel() - currentLevel / maxMult + 2;
         int level = -1, max = -1;
         if (maxWeeks < weeks) {
        SkillType skill = getSkillType(t);
           maxWeeks = weeks;
        try {
          level = Integer.parseInt(tokens.nextToken());
          max = Integer.parseInt(tokens.nextToken());
         } catch (NumberFormatException e) {
          //
        } catch (NoSuchElementException e) {
          //
         }
        if (skill != null && level > 0 && max > 0) {
          parsed.put(new SkillSpec(skill, level, max), "");
         } else {
           addNewWarning("error in skillLine :" + learnLine);
         }
         }
        // }
      }
      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;
     return parsed.keySet();
  }
 
  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
   * <code>// $cript Quartiermeister [[Menge1 Gut 1]...]</code>: learn perception, allow listed
  * found, at least one item (with amount 0) is returned.
  * amount of goods. If other goods are detected, do not confirm orders.
   */
   */
//   protected static ArrayList<Item> findItems(ItemType itemType, Unit u, String category) {
   protected void commandQuartermaster(String[] tokens) {
  protected static ArrayList findItems(ItemType itemType, Unit u, String category) {
    int silver = 0;
//    ArrayList<Item> items = new ArrayList<Item>(1);
    try {
    ArrayList items = new ArrayList(1);
      for (Item item : currentUnit.getItems()) {
    ItemCategory itemCategory = world.getRules().getItemCategory(StringID.create(category));
        boolean okay = false;
    if (itemType == null) {
        if (item.getItemType().getID().equals(EresseaConstants.I_USILVER)) {
      for (Item item : u.getItems()) {
          silver = item.getAmount();
        if (itemCategory.equals(item.getItemType().getCategory())) {
          if (silver < 2000) {
          items.add(item);
            okay = true;
          }
        }
        for (int i = 1; !okay && i < tokens.length - 1; i += 2) {
          if (item.getName().equals(tokens[i + 1]) || item.getOrderName().equals(tokens[i + 1])) {
            if (item.getAmount() <= Integer.parseInt(tokens[i])) {
              okay = true;
              break;
            }
          }
         }
         }
        setConfirm(currentUnit, okay);
       }
       }
     } else {
     } catch (NumberFormatException e) {
      if (u.getItem(itemType) != null) {
      addNewError("ungültige Zahl ");
        items.add(u.getItem(itemType));
      }
     }
     }
     if (items.isEmpty()) {
 
      for (Object o : itemCategory.getInstances()) {
     ArrayList<SkillSpec> qskills = new ArrayList<SkillSpec>(2);
        ItemType type = (ItemType) o;
    qskills.add(new SkillSpec(world.getRules().getSkillType(EresseaConstants.S_WAHRNEHMUNG), 10, 99));
         items.add(new Item(type, 0));
    SkillType tactics = world.getRules().getSkillType(EresseaConstants.S_TAKTIK);
        break;
    if (tactics != null) {
      Skill utactics = currentUnit.getSkill(tactics);
      if ((utactics == null || utactics.getLevel() < QM_TACTICS) && silver > tactics.getCost(utactics == null ? 0
          : utactics.getLevel())) {
         qskills.add(new SkillSpec(tactics, QM_TACTICS * 2, QM_TACTICS));
       }
       }
     }
     }
     return items;
     learn(currentUnit, qskills);
 
    setConfirm(currentUnit, true);
   }
   }


//   protected boolean reserveEquipment(ItemType preferred, List<Item> ownStuff, boolean warn) {
  /**
   protected boolean reserveEquipment(ItemType preferred, List ownStuff, boolean warn) {
  * <code>// $cript Sammler [frequenz]</code>: collect herbs if there are at least "viele",
     if (preferred != null) {
  * research herbs every frequenz rounds.
       // reserve requested weapon
  */
      commandNeed(new String[] { "Benoetige", EACHOrder, "1", preferred.getOrderName(), String
   protected void commandCollector(String[] tokens) {
          .valueOf(DEFAULT_PRIORITY) });
     if (tokens.length > 2) {
     } else if (!ownStuff.isEmpty()) {
       addNewError("zu viele Argumente");
       int supply = 0;
    }
       for (Item w : ownStuff) {
    int modulo = Integer.MAX_VALUE;
         // reserve all matching weapons, except first one
     if (tokens.length > 1) {
        if (supply > 0) {
       try {
          commandNeed(new String[] { "Benoetige", Integer.toString(Math.min(getCurrentUnit()
        modulo = Integer.parseInt(tokens[1]);
              .getPersons() - supply, w.getAmount())), w.getOrderName(), String.valueOf(
       } catch (NumberFormatException e) {
                  DEFAULT_PRIORITY) });
         addNewError("ungültige Zahl " + tokens[1]);
         }
         return;
        supply += w.getAmount();
       }
       }
    }


      // now reserve the first matching weapon with min=w.getAmount(), max=w.getPersons()-other
    if (currentRegion.getRegionType().isOcean()) {
       // weapons
       addNewError("Sammeln nicht möglich!");
       Item w = ownStuff.get(0);
       return;
      String max = Integer.toString(Math.max(0, Math.min(getCurrentUnit().getPersons() - supply + w
    }
          .getAmount(), getCurrentUnit().getPersons())));
    removeOrdersLike(MAKEOrder + " " + "[^T].*", true);
      String min = warn ? max : Integer.toString(Math.min(getCurrentUnit().getPersons(), w
    removeOrdersLike(getResearchOrder() + ".*", true);
          .getAmount()));
    Date dateBefore = world.getDate().clone();
      commandNeed(new String[] { "Benoetige", min, max, w.getOrderName(), String.valueOf(
    dateBefore.setDate(world.getDate().getDate() - 1);
          DEFAULT_PRIORITY) });
    if ((dateBefore.getSeason() == Date.WINTER && world.getDate().getSeason() == Date.WINTER)) {
     } else
      modulo = 2;
       return false;
    }
     return true;
    if ((modulo != Integer.MAX_VALUE && world.getDate().getDate() % modulo == 0)
        || currentRegion.getHerbAmount() == null
        || (!currentRegion.getHerbAmount().equals("viele") && !currentRegion.getHerbAmount().equals("sehr viele"))) {
      addNewOrder(getResearchOrder(), true);
     } else {
       addNewOrder(MAKEOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER"), true);
     }
    commandSupply(new String[] { "Versorge", KRAUTOrder, "100" });
   }
   }


   /**
   /**
   * Tries to init the constants according to the current faction's locale
   * <code>KrautKontrolle [[[direction...] PAUSE]...]</code> move until the next PAUSE, if pause is
  * reached, research herbs.
   */
   */
   protected void initLocales() {
   protected void commandControl(String[] tokens) {
     if (getCurrentUnit() == null) {
     for (Order o : currentUnit.getOrders2()) {
      findSomeUnit(currentFactions);
      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;
      }
     }
     }
     if (getCurrentUnit() == null)
     // no route order -- create new one
      throw new NullPointerException();
     removeOrdersLike(getResearchOrder() + ".*", true);
 
     StringBuilder newOrder = new StringBuilder();
     GIVEOrder = getLocalizedOrder(EresseaConstants.OC_GIVE, GIVEOrder).toString();
     newOrder.append(createScriptCommand()).append(tokens[0]);
    RESERVEOrder = getLocalizedOrder(EresseaConstants.OC_RESERVE, RESERVEOrder).toString();
     StringBuilder moveOrder = new StringBuilder(ROUTEOrder);
     EACHOrder = getLocalizedOrder(EresseaConstants.OC_EACH, EACHOrder).toString();
     boolean pause = false;
     ALLOrder = getLocalizedOrder(EresseaConstants.OC_ALL, ALLOrder).toString();
     for (int i = 1; i < tokens.length; ++i) {
    PCOMMENTOrder = "//";
      if (!pause) {
    LEARNOrder = getLocalizedOrder(EresseaConstants.OC_LEARN, LEARNOrder).toString();
        // add move order until first PAUSE
     TEACHOrder = getLocalizedOrder(EresseaConstants.OC_TEACH, TEACHOrder).toString();
        moveOrder.append(" ").append(tokens[i]);
     ENTERTAINOrder = getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder).toString();
      } else {
     TAXOrder = getLocalizedOrder(EresseaConstants.OC_TAX, TAXOrder).toString();
        // add to $cript order after first PAUSE
    WORKOrder = getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder).toString();
        newOrder.append(" ").append(tokens[i]);
    BUYOrder = getLocalizedOrder(EresseaConstants.OC_BUY, BUYOrder).toString();
      }
    SELLOrder = getLocalizedOrder(EresseaConstants.OC_SELL, SELLOrder).toString();
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
    MAKEOrder = getLocalizedOrder(EresseaConstants.OC_MAKE, MAKEOrder).toString();
        pause = true;
     MOVEOrder = getLocalizedOrder(EresseaConstants.OC_MOVE, MOVEOrder).toString();
      }
     ROUTEOrder = getLocalizedOrder(EresseaConstants.OC_ROUTE, ROUTEOrder).toString();
     }
     PAUSEOrder = getLocalizedOrder(EresseaConstants.OC_PAUSE, PAUSEOrder).toString();
     moveOrder.append(" ").append(PAUSEOrder);
     RESEARCHOrder = getLocalizedOrder(EresseaConstants.OC_RESEARCH, RESEARCHOrder).toString();
     pause = false;
    RECRUITOrder = getLocalizedOrder(EresseaConstants.OC_RECRUIT, RECRUITOrder).toString();
     for (int i = 1; i < tokens.length; ++i) {
 
      if (!pause) {
    if (currentFactions.keySet().iterator().next().getLocale().getLanguage() != "de") {
        // append movement until first PAUSE to back of $cript order
      // warning constants
        newOrder.append(" ").append(tokens[i]);
      W_NEVER = "never";
      }
      W_SKILL = "skill";
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
      W_WEAPON = "weapon";
        pause = true;
       W_SHIELD = "shield";
       }
      W_ARMOR = "armor";
      BEST = "best";
      NULL = "null";
      LUXUSOrder = "LUXURY";
      KRAUTOrder = "HERBS";
     }
     }
    addNewOrder(newOrder.toString(), true);
    addNewOrder(moveOrder.toString(), true);
   }
   }


   protected String getLocalizedOrder(StringID orderKey, String fallback) {
  /**
     try {
  * <code>// $cript RekrutiereMax [min [max]] [race]</code>: recruit as much as possible; warn if
       return world.getGameSpecificStuff().getOrderChanger().getOrderO(orderKey, getCurrentUnit()
  * less than min are possible
           .getLocale()).getText();
  */
    } catch (RulesException e) {
   protected void commandRecruit(String[] tokens) {
       return fallback;
     if (tokens.length > 4) {
       addNewError("zu viele Argumente");
    }
    int min = 1;
    String race = null;
    int max = Integer.MAX_VALUE;
    if (tokens.length > 1) {
      try {
        min = Integer.parseInt(tokens[1]);
        if (tokens.length > 2) {
           try {
            max = Integer.parseInt(tokens[2]);
            if (tokens.length > 3) {
              race = tokens[3];
            }
          } catch (NumberFormatException e) {
            max = Integer.MAX_VALUE;
            race = tokens[2];
            if (tokens.length > 3) {
              addNewError("zu viele Argumente");
            }
          }
        }
      } catch (NumberFormatException e) {
        min = 1;
        race = tokens[1];
        if (tokens.length > 2) {
          addNewError("zu viele Argumente");
        }
       }
     }
     }
  }


  /**
    Race effRace = race == null ? currentUnit.getRace() : helper.getRace(race);
  * Tries to translate the given order to the current locale.
    if (effRace == null) {
  */
      addNewError("Unbekannte Rasse");
  protected String getLocalizedOrder(StringID orderKey, Object[] args) {
      return;
     return world.getGameSpecificStuff().getOrderChanger().getOrderO(getCurrentUnit().getLocale(),
    }
         orderKey, args).getText();
 
    if (currentUnit.getPersons() != 0 && !effRace.equals(currentUnit.getRace())) {
      addNewWarning("race != unit race");
    }
 
    int amount = world.getGameSpecificRules().getRecruitmentLimit(currentUnit, effRace);
 
    if (currentUnit.getPersons() + amount >= max) {
      addNewWarning("Rekrutierung fertig");
      amount = Math.min(max - currentUnit.getPersons(), amount);
    }
 
    if (amount < min) {
      addNewError("Nicht genug Rekruten");
     }
 
    if (amount > 0) {
      if (effRace.getRecruitmentCosts() > 0) {
        int costs = amount * effRace.getRecruitmentCosts();
        int maxCosts =
            max == Integer.MAX_VALUE ? costs : (max - currentUnit.getPersons())
                * effRace.getRecruitmentCosts();
        addNeed("Silber", currentUnit, costs, maxCosts, DEFAULT_PRIORITY);
      } else {
         addNewWarning("Rekrutierungskosten unbekannt");
      }
      getRecruitOrder(amount, effRace);
      addNewOrder(getRecruitOrder(amount, effRace != currentUnit.getRace() ? effRace : null), true);
    }
   }
   }


   /**
   /**
   * Tries to translate the given order to the current locale.
   * <code>// $cript Kommentar text</code>: add text after a semicolon
   */
   */
   protected String getLocalizedOrder(String orderKey, String fallBack) {
   protected void commandComment(String[] tokens) {
     String translation = Resources.getOrderTranslation(orderKey, getCurrentUnit().getLocale());
     String rest = currentOrder.substring(currentOrder.indexOf(tokens[0]) + tokens[0].length());
     if (translation == orderKey)
     addNewOrder(";" + rest, true);
      return fallBack;
 
    else
      return translation;
   }
   }


   /**
   // ///////////////////////////////////////////////////////
  * Adss an order to the current unit's new orders.
  // HELPER functions
  *
  // ///////////////////////////////////////////////////////
  * @param order The new order
 
  * @param changed Set to true if this is a change (not merely a copy of an old order)
   protected boolean hasEntertain() {
  */
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
   protected void addNewOrder(String order, boolean changed) {
         getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder));
     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());
  protected boolean hasTax() {
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
        getLocalizedOrder(EresseaConstants.OC_TAX, TAXOrder));
   }
   }


  /**
   protected boolean hasWork() {
  * Registers a pattern. All lines matching this regular expression (case sensitive!) will be
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
  * removed from here on. If retroActively, also orders that are already in {@link #newOrders}.
         getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder));
  *
  * @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);
   }
   }


   /**
   protected void initSupply() {
  * Adds an error line.
    if (needQueue == null) {
  *
      needQueue = new ArrayList<Need>();
  * @param line The current order line
    } else {
  * @param hint
      needQueue.clear();
  */
    }
  protected void addNewError(String hint) {
 
     error = line;
    if (supplyMapp == null) {
     // errMsg = hint;
      supplyMapp = new SupplyMap(this);
     addNewOrder(COMMENTOrder + " TODO: " + hint + " (Fehler in Zeile " + error + ")", true);
     } else {
     setConfirm(getCurrentUnit(), false);
      supplyMapp.clear();
  }
     }
    if (capacities == null) {
      capacities = new HashMap<Unit, Integer>();
     } else {
      capacities.clear();
    }
    if (transfersMap == null) {
      transfersMap = new HashMap<Unit, Map<String, Integer>>();
      transferList = new ArrayList<Transfer>();
     } else {
      transfersMap.clear();
      transferList.clear();
    }
    if (dummyUnits == null) {
      dummyUnits = new HashMap<String, Unit>();
    } else {
      dummyUnits.clear();
    }


  /**
    for (Unit u : currentRegion.units()) {
  * Adds a warning message (with a to do tag) to the new orders.
      if (currentFactions.containsKey(u.getFaction())) {
  *
        for (Item item : u.getItems()) {
  * @param text
          putSupply(item.getOrderName(), u, item.getAmount());
  */
        }
  protected void addNewMessage(String text) {
    addNewOrder(COMMENTOrder + " ----- " + text + " -----", true);
  }


  /**
        // TODO take RESERVE or GIVE orders into account?
  * 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) {
        // // subtract reserved items from supply amount of unit
    error = line;
        // for (ReserveRelation relation : u.getRelations(ReserveRelation.class)) {
    // errMsg = hint;
        // Supply supply = getSupply(relation.itemType.getName(), u);
    addNewOrder(COMMENTOrder + " TODO: " + hint + (addLine ? " (Warnung in Zeile " + error + ")"
        // if (supply != null && relation.source == u) {
         : ""), true);
        // supply.setAmount(supply.getAmount() - relation.amount);
    setConfirm(getCurrentUnit(), false);
        // }
        // }
        // // 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);
        // }
        // }
      }
    }
   }
   }
  // ///////////////////////////////////////////////////////
  // HELPER functions
  // ///////////////////////////////////////////////////////


   /**
   /**
   * Adds a warning message (with a to do tag) directly to the unit's orders.
   * Adds a supply to the supplyMap
   *
   *
  * @param item
   * @param unit
   * @param unit
   * @param text
   * @param amount
   */
   */
   public static void addWarning(Unit unit, String text) {
   protected Supply putSupply(String item, Unit unit, int amount) {
     // helper.addOrder(unit, "; -------------------------------------");
     return supplyMapp.put(item, unit, amount, ++supplySerial);
    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
   * Adds a supply to the supplyMap
  * if it has not been marked as unconfirmable before (unconfirm always overrides confirm).
   *
   *
   * @param u
   * @param item
   * @param confirm
   * @param unit
  * @param amount
   */
   */
   public static void setConfirm(Unit u, boolean confirm) {
   protected Supply putDummySupply(String item, Unit unit, int amount) {
     String tag = getProperty(u, "confirm");
     return supplyMapp.put(item, unit, amount, Long.MAX_VALUE);
    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.
   * Tries to satisfy all needs in the current needMap by adding GIVE orders to suppliers.
  *
  * @param u
  * @param tagName
  * @param value
  * @see #getProperty(Unit, String)
   */
   */
   public static void setProperty(Unit u, String tagName, String value) {
   protected void satisfyNeeds() {
     u.putTag(scriptMarker + "." + tagName, value);
     supplyMapp.sortByPriority();
     // u.addOrder("; $cript " + tagName + ":" + value, false, 0);
 
    Need[] needs = needQueue.toArray(new Need[] {});
    sort(needs);
    adjustAlreadyTransferred(needs);
    Reserves reserves = new Reserves();
 
    pack(needs, reserves, false);
     enforceCapacities();
    pack(needs, reserves, true);
 
    executeReserves(reserves);
    executeTransferOrders();
    warnNeeds();
    warnCapacities();
   }
   }


   /**
   private void pack(Need[] needs, Reserves reserves, boolean enforce) {
  * Returns a property value (from a tag) from the unit.
    for (int n = 0, prioStart = 0, state = 0; n < needs.length; ++n) {
  *
      Need need = needs[n];
  * @param u
      if (need.getPriority() > needs[prioStart].getPriority())
  * @param tagName
        throw new RuntimeException("needs not sorted");
  * @return The value of property <code>tagName</code> or "" if the property has not been set.
 
  */
      switch (state) {
  public static String getProperty(Unit u, String tagName) {
      case 0:
    String tag = u.getTag(scriptMarker + "." + tagName);
        reserveNeed(need, true, reserves);
    return tag == null ? "" : tag;
        break;
  }


  /**
      case 1:
  * Returns the ItemType in the rules matching <code>name</code>.
        giveNeed(need, true);
  *
        break;
  * @param name A item type name, like "Silber"
      case 2:
  * @return The ItemType corresponding to name or <code>null</code> if this ItemType does not
        if (need.getAmount() != Integer.MAX_VALUE) {
  *         exist.
          reserveNeed(need, false, reserves);
  */
        }
  public static ItemType getItemType(String name) {
        break;
    return world.getRules().getItemType(name);
      case 3:
        if (need.getAmount() != Integer.MAX_VALUE) {
          giveNeed(need, false);
        }
        break;
      case 4:
         if (need.getAmount() == Integer.MAX_VALUE) {
          // now, finally, satisfy infinite needs
          reserveNeed(need, false, reserves);
        }
        break;
      case 5:
        if (need.getAmount() == Integer.MAX_VALUE) {
          giveNeed(need, false);
        }
        break;
      }
 
      if (n == needs.length - 1 || needs[n + 1].getPriority() < needs[prioStart].getPriority()) {
        if (state < 5) {
          n = prioStart - 1;
          ++state;
        } else {
          state = 0;
          prioStart = n + 1;
          if (enforce) {
            enforceCapacities();
          }
        }
      }
    }
   }
   }


   /**
   private void warnNeeds() {
  * Returns the SkillType in the rules matching <code>name</code>.
    for (Need need : needQueue) {
  *
      if (need.getMinAmount() > 0 && need.getWarning().contains(Warning.C_AMOUNT)) {
  * @param name A skill name, like "Ausdauer"
        addWarning(need.getUnit(), "braucht " + need.getMinAmount()
  * @return The SkillType corresponding to name or <code>null</code> if this SkillType does not
            + (need.getMaxAmount() != need.getMinAmount() ? ("/" + need.getMaxAmount()) : "")
  *        exist.
            + " mehr " + need.getItem() + ", " + need.getMessage());
  */
      }
  public static SkillType getSkillType(String name) {
 
    return world.getRules().getSkillType(name);
      // add messages for unsatisfied needs
      if (need.getMinAmount() <= 0 && need.getMaxAmount() > 0
          && need.getMaxAmount() != Integer.MAX_VALUE) {
        addOrder(need.getUnit(), "; braucht " + need.getMaxAmount() + " mehr " + need.getItem()
            + ", "
            + need.getMessage(),
            false);
      }
    }
   }
   }


   /**
   private void warnCapacities() {
  * Returns a skill with the given name and level.
    for (Entry<Unit, Integer> cap : capacities.entrySet()) {
  *
      if (cap.getValue() < 0) {
  * @param name
        addWarning(cap.getKey(), "Kapazität überschritten um " + (-cap.getValue()));
  * @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;
   }
   }


   /**
   private void sort(Need[] sorted) {
  * Notifies the interface that the unit should be updated.
    for (int j = 1; j < sorted.length; ++j) {
  *
      for (int i = 0; i < j; ++i) {
  * @param u This unit is updated in the UI
        if (sorted[j].compareTo(sorted[i]) < 0) {
  * @deprecated I don't think this is needed any more.
          Need temp = sorted[i];
  */
          sorted[i] = sorted[j];
//  @Deprecated
          sorted[j] = temp;
  public static void notifyMagellan(Unit u) {
        }
    helper.updateUnit(u);
      }
    }
   }
   }


   /**
   private void adjustAlreadyTransferred(Need[] needs) {
  * Adds a GIVE order to the unit's orders, like <code>GIVE receiver [EACH] amount item</code>.
     for (Need need : needs) {
  *
      adjustForTransfer(need);
  * @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));
   }
   }


   /**
   private void enforceCapacities() {
  * Returns a line like <code>GIVE receiver [EACH] amount item</code>.
    for (int i = transferList.size() - 1; i >= 0; --i) {
  *
      Transfer transfer = transferList.get(i);
  * @param unit
      Integer cap;
  * @param receiver
      if ((cap = capacities.get(transfer.getTarget())) != null && cap < 0) {
  * @param item
        if (transfer.getUnit() != transfer.getTarget()) { // && transfer.isMin()
  * @param amount If <code>amount == Integer.MAX_VALUE</code>, amount is replaced by ALL
          int weight = getWeight(transfer.getItem());
  * @param each
          if (weight > 0) {
  * @return a line like <code>GIVE receiver [EACH] amount item</code>.
            int delta = cap / weight;
  */
            if (delta * weight > cap) {
  public static String getGiveOrder(Unit unit, String receiver, String item, int amount,
              --delta;
      boolean each) {
            }
     return helper.getGiveOrder(unit, receiver, item, amount, each);
            delta = Math.max(-transfer.getAmount(), delta);
            if (delta < 0) {
              undoTransfer(i, delta);
              i = transferList.size();
            }
          }
        }
      }
    }
  }
 
  private void undoTransfer(int index, int delta) {
    Transfer transfer = transferList.get(index);
    if (-delta < transfer.getAmount()) {
      transfer.reduceAmount(-delta);
    } else {
      transferList.remove(index);
    }
    if (-delta > getMulti(transfersMap, transfer.getTarget(), transfer.getItem())) {
      addNewError("ungültiger Transfer " + transfer);
    }
    increaseMulti(transfersMap, transfer.getTarget(), transfer.getItem(), delta);
     changeCapacity(transfer.getUnit(), transfer.getTarget(), transfer.getItem(), delta);
 
    getSupply(transfer.getItem(), transfer.getUnit()).reduceAmount(delta);
    if (transfer.isMin()) {
      transfer.getNeed().reduceMinAmount(delta);
    }
    transfer.getNeed().reduceMaxAmount(delta);
 
   }
   }


   /**
   private void addTransfer(Unit giver, Unit receiver, String item, int amount, boolean min,
  * Adds a RESERVE order to the unit's orders, like <code>RESERVE [EACH] amount item</code>.
      boolean all,
  *
      Need need) {
  * @param unit The order is added to this unit's orders
     transferList.add(new Transfer(giver, receiver, item, amount, min, all, need, ++supplySerial));
  * @param item
    increaseMulti(transfersMap, receiver, item, amount);
  * @param amount
  * @param each
  */
  public static void addReserveOrder(Unit unit, String item, int amount, boolean each) {
     helper.addOrder(unit, getReserveOrder(unit, item, amount, each));
   }
   }


   /**
   private void executeTransferOrders() {
  * Returns a line like <code>RESERVE [EACH] amount item</code>.
    for (Transfer t : transferList) {
  *
      if (t.getUnit() != t.getTarget()) {
  * @param unit
        addOrder(t.getUnit(),
  * @param item
            getGiveOrder(t.getUnit(), t.getTarget().getID().toString(), t.getItem(),
  * @param amount
                (t.isAll() ? Integer.MAX_VALUE : t.getAmount()), false)
  * @param each
                + COMMENTOrder + t.getMessage(), false);
  * @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);
 
   private void transfer(Unit unit, Need need, int amount) {
    Supply supply = getSupply(need.getItem(), unit);
    if (supply.getAmount() < amount) {
      if (currentUnit == null) {
        addWarning(supply.getUnit(), "not enough " + need.getItem());
      } else {
        addNewWarning("not enough " + need.getItem() + " for " + supply.getUnit());
      }
     }
 
    need.reduceMaxAmount(amount);
    need.reduceMinAmount(amount);
    supply.reduceAmount(amount);
    if (unit != need.getUnit()) {
      changeCapacity(supply.getUnit(), need.getUnit(), need.getItem(), amount);
    }
   }
   }


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


   /**
   private void giveTransfer(String targetId, String item, int amount, boolean all) {
  * Returns a <code>RECRUIT amount race</code> order.
     Unit targetUnit = findUnit(targetId);
  */
    Need dummyNeed = new Need(targetUnit, item, amount, amount, GIVE_IF_PRIORITY, new Warning(true),
  protected String getRecruitOrder(int amount, Race race) {
        ++supplySerial);
     if (race != null)
     Supply supply = getSupply(item, currentUnit);
      return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount, race });
     all = all || (supply != null && supply.getAmount() == amount);
     else
    addTransfer(currentUnit, targetUnit, item, amount, true, all, dummyNeed);
      return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount });
    transfer(currentUnit, dummyNeed, amount);
     // return RECRUITOrder + " " + amount
    // + (race != null ? (" " + getLocalizedOrder("race." + race.getID(), race.getName())) : "");
   }
   }


   /**
   public static class MyReserveVisitor implements ReserveVisitor {
  * Return the amount of item that a unit has.
 
  *
    public void execute(Unit u, String item, int amount) {
  * @param unit
      if (amount > 0) {
  * @param item
        if (amount == u.getPersons()) {
  * @return The amount of item in the unit's items
          addOrder(u, getReserveOrder(u, item // + COMMENTOrder + need.toString()
  */
              , 1, true), false);
  public static int getItemCount(Unit unit, String item) {
        } else {
    return helper.getItemCount(unit, item);
          addOrder(u, getReserveOrder(u, item, amount, false), false);
  }
        }
      }
    }


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


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


   /**
   private void changeCapacity(Unit source, Unit target, String item, int amount) {
  * Returns true if the item is usable with weaponSkill, false otherwise.
    if (!isHorse(item)) {
  */
      int weight = amount * getWeight(item);
  protected static boolean isUsable(Item item, SkillType weaponSkill) {
      changeCapacity(source, weight);
    return isUsable(item.getItemType(), weaponSkill);
      changeCapacity(target, -weight);
    }
   }
   }


   /**
   private Integer changeCapacity(Unit unit, int delta) {
  * Returns true if skill is a weapon skill.
     Integer c = capacities.get(unit);
  */
    if (c != null) {
  protected static boolean isWeaponSkill(Skill skill) {
      capacities.put(unit, c = c + delta);
     return skill.getSkillType().getID().equals(EresseaConstants.S_HIEBWAFFEN) || skill
    }
        .getSkillType().getID().equals(EresseaConstants.S_STANGENWAFFEN) || skill.getSkillType()
    return c;
            .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.
   * Tries to satisfy (minimum) need by a RESERVE order
  *
  * @param need
  * @param min
  * @param reserves
   */
   */
   public static boolean isSoldier(Unit unit) {
   protected void reserveNeed(Need need, boolean min, Reserves reserves) {
//     Collection<Item> items = unit.getItems();
     int amount = getNeedAmount(need, min);
     Collection items = unit.getItems();
     // amount = adjustForTransfer(need, amount);
     ItemCategory weapons = world.getRules().getItemCategory(StringID.create("weapons"));
 
     for (Item item : items) {
     Supply supply = getSupply(need.getItem(), need.getUnit());
       if (weapons.isInstance(item.getItemType())) {
     if (supply == null)
        // ah, a weapon...
       return;
         Skill useSkill = item.getItemType().getUseSkill();
 
        if (useSkill != null) {
    // only suppliers with positive priority serve maximum needs
          // okay, has the unit the skill?
    amount = Math.min(amount, supply.getAmount() - 0);
          for (Skill skill : unit.getSkills()) {
    if (amount > 0) {
            if (useSkill.getSkillType().equals(skill.getSkillType()))
      if (min) {
              return true;
         addTransfer(need.getUnit(), need.getUnit(), need.getItem(), amount, min, false, need);
          }
      }
        }
      transfer(need.getUnit(), need, amount);
      if (min) {
        reserves.add(need.getItem(), need.getUnit(), amount);
       }
       }
     }
     }
    return false;
   }
   }


   /**
   /**
   * Parses all units in the region for orders like "; $xyz$sl" and adds a tag with name
   * Tries to satisfy (minimum) need by a give order from suppliers.
  * "ejcTaggableComparator5" and value "xyz" for them.
   *
   *
   * @param r
   * @param need
  * @param min
   */
   */
   public static void parseShipLoaderTag(magellan.library.Region r) {
   protected void giveNeed(Need need, boolean min) {
     for (Unit u : r.units()) {
     int amount = getNeedAmount(need, min);
       String name = null;
    if (amount > 0) {
       for (Order order : u.getOrders2()) {
       // adjustForTransfer(need, amount);
         String line = order.getText();
       for (Supply supply : supplyMapp.get(need.getItem())) {
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]sl.*");
         if (supply.getUnit() != need.getUnit() && (min || supply.getPriority() > 0) && supply
        java.util.regex.Matcher m = p.matcher(line);
            .hasPriority()) {
        if (m.matches()) {
          int giveAmount = Math.min(amount, supply.getAmount());
           name = m.group(1);
          if (giveAmount > 0) {
            addTransfer(supply.getUnit(), need.getUnit(), need.getItem(), giveAmount, min, false,
                need);
            transfer(supply.getUnit(), need, giveAmount);
            amount -= giveAmount;
           }
        }
        if (amount <= 0) {
          break;
         }
         }
      }
      if (name != null) {
        u.putTag("ejcTaggableComparator5", name);
      } else {
        u.removeTag("ejcTaggableComparator5");
       }
       }
     }
     }
   }
   }


   /**
   private int getNeedAmount(Need need, boolean min) {
  * Parses all units in the region for orders like "; $xyz$verlassen" and adds a tag with name
     int amount = min ? need.getMinAmount() : need.getAmount();
  * "ejcTaggableComparator5" and value "xyz" for them.
    return amount;
  *
  }
  * @param r
 
  */
  private int adjustForTransfer(Need need) {
  public static void parseShipLoaderTag2(magellan.library.Region r) {
    int amount = need.getMaxAmount();
     for (Unit u : r.units()) {
    Integer transferred = getMulti(transfersMap, need.getUnit(), need.getItem());
      String name = null;
    if (transferred != null) {
      for (Order line : u.getOrders2()) {
      if (transferred > amount) {
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]verlassen.*");
        increaseMulti(transfersMap, need.getUnit(), need.getItem(), -amount);
        java.util.regex.Matcher m = p.matcher(line.getText());
         need.setMinAmount(0);
        if (m.matches()) {
         need.setMaxAmount(0);
          name = m.group(1);
        amount = 0;
         }
      }
      if (name != null && name.equals("633")) {
         u.putTag("ejcTaggableComparator5", name);
       } else {
       } else {
         u.removeTag("ejcTaggableComparator5");
         removeMulti(transfersMap, need.getUnit(), need.getItem());
        need.reduceMinAmount(transferred);
        need.reduceMaxAmount(transferred);
        amount -= transferred;
       }
       }
     }
     }
    return amount;
  }
  protected int getWeight(String item) {
    return Math.round(world.getRules().getItemType(item).getWeight() * 100);
   }
   }


   /**
   /**
   * Converts some Vorlage commands to $cript commands. Call from the script of your faction with
   * Returns the total supply for an item.
   * <br />
   *
   * <code>(new E3CommandParser(world, helper)).convertVorlage((Faction) container, null);</code>
   * @param item Order name of the supplied item
   * (all regions) or from a region with<br />
  * @return The supply or 0, if none has been registered.
  * <code>(new E3CommandParser(world, helper)).convertVorlage(helper.getFaction("1wpy"),
   */
   * (Region) container);</code>.
  protected int getSupply(String item) {
    return supplyMapp.getSupply(item);
  }
 
  /**
   * Returns a supply of a unit for an item.
   *
   *
   * @param faction
   * @param item Order name of the supplied item
   * @param region
   * @param unit
  * @return The supply or null, if none has been registered.
   */
   */
   public void convertVorlage(Faction faction, Region region) {
   protected Supply getSupply(String item, Unit unit) {
     if (faction == null)
     return supplyMapp.get(item, unit);
      throw new NullPointerException();
  }
    currentFactions = Collections.singletonMap(faction, 1);


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


    if (region == null) {
  /**
      // comment out the following line if you don't have the newest nightly build of Magellan
  * Adds the specified amounts to the need of a unit for an item to the needMap.
       helper.getUI().setMaximum(world.getRegions().size());
  *
 
  * @param item Order name of the required item
      // call self for all regions
  * @param unit
      for (Region r : world.getRegions()) {
  * @param minAmount
        // comment out the following line if you don't have the newest nightly build of Magellan
  * @param maxAmount
        helper.getUI().setProgress(r.toString(), ++progress);
  */
  protected Need addNeed(String item, Unit unit, int minAmount, int maxAmount, int priority,
       Warning w) {
    Need result;
    if (world.getRules().getItemType(StringID.create(item)) == null && w.contains(~0)) {
      addNewWarning("unknown item " + item);
    }


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


     // loop for all units of faction
     return result;
    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>();
  * Marks the unit as soldier. Learns its best weapon skill. Reserves suitable weapon, armor, and
        newOrders = new ArrayList();
  * shield if available.
//         removedOrderPatterns = new ArrayList<String>();
  *
        removedOrderPatterns = new ArrayList();
  * @param u The unit in question
        changedOrders = false;
  * @param sWeaponSkill The desired skill. If <code>null</code>, the unit's best weapon skill is
        error = -1;
  *          used. If the unit knows no weapon skill, a warning is issued.
        StringBuilder lerneOrder = null;
  * @param sWeapon The desired weapon that is reserved. If this is <code>null</code>, a weapon that
  *          matches the weaponSkill is reserved.
  * @param sArmor The desired armor. If <code>null</code>, a suitable armor is reserved. If the
  *          unit has no armor at all, <em>no</em> warning is issued.
  * @param sShield The desired shield. If <code>null</code>, a suitable shield is reserved. If the
  *          unit has no shield at all, <em>no</em> warning is issued.
  * @param warning Warnings for missing equipment are only issued if this is <code>true</code>.
  */
  protected void soldier(Unit u, String sWeaponSkill, String sWeapon, String sShield,
      String sArmor, String warning) {


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


          if (currentOrder.startsWith("// #")) {
    SkillType weaponSkill =
            String[] tokens = detectVorlageCommand(currentOrder);
        sWeaponSkill == null ? null : rules.getSkillType(StringID.create(sWeaponSkill));
            if (tokens != null) {
    ItemType weapon = sWeapon == null ? null : rules.getItemType(StringID.create(sWeapon));
              String command = tokens[0];
    ItemType armor = sArmor == null ? null : rules.getItemType(StringID.create(sArmor));
    ItemType shield = sShield == null ? null : rules.getItemType(StringID.create(sShield));


              // add vorlage as comment
    if (weaponSkill == null || BEST.equals(sWeaponSkill)) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
      int max = 0;
      for (Skill skill : u.getSkills()) {
        if (isWeaponSkill(skill) && skill.getLevel() > max) {
          max = skill.getLevel();
          weaponSkill = skill.getSkillType();
        }
      }
      if (weaponSkill == null && !Warning.W_NEVER.equals(warning)) {
        addNewWarning("kein Kampftalent");
        return;
      }
    }


              // detect commands
    ArrayList<Item> weapons = new ArrayList<Item>();
              if (command.equals("Ausruestung")) {
    if (weapon == null) {
                for (int i = 1; i < tokens.length; ++i) {
      for (Item item : u.getItems()) {
                  if (tokens[i].equals("Ausruestung")) {
        if (isUsable(item, weaponSkill)) {
                    continue;
          weapons.add(item);
                  }
        }
                  try {
      }
                    int number = Integer.parseInt(tokens[i]);
      if (weapons.isEmpty()) {
                    addNewOrder(scriptStart + "Benoetige " + number + " " + tokens[++i], true);
        for (ItemType type : rules.getItemTypes()) {
                  } catch (Exception e) {
          if (isUsable(type, weaponSkill)) {
                    addNewOrder(scriptStart + "Benoetige JE 1 " + tokens[i], true);
            weapons.add(new Item(type, 0));
                  }
            break;
                }
          }
              } else if (command.equals("AutoBestaetigen")) {
        }
                addNewOrder(scriptStart + "auto " + (tokens[1].equals("an") ? "" : NOT), true);
      }
              } else if (command.equals("Benoetige")) {
      if (weapons.isEmpty()) {
                if (tokens.length > 3 && tokens[3].equals("aus")) {
        addNewError("keine passenden Waffen bekannt für " + weaponSkill);
                  addNewOrder(scriptStart + "Benoetige 0 " + tokens[1] + " " + tokens[2], true);
      }
                } else {
    } else {
                  addNewOrder(scriptStart + "Benoetige " + tokens[1] + " " + tokens[2], true);
      if (u.getItem(weapon) != null) {
                }
        weapons.add(u.getItem(weapon));
              } else if (command.startsWith("BerufBotschafter")) { // also BerufBotschafterSTM
      } else {
                addNewOrder(scriptStart + " BerufBotschafter " + (tokens.length > 1 ? tokens[1]
        addNewError("keine " + weapon);
                    : ""), true);
      }
              } else if (command.equals("BerufDepotVerwalter")) {
    }
                int number = Integer.MIN_VALUE;
 
                if (tokens.length > 1) {
    // note that "shield" is a subcategory of "armour"
                  try {
    ArrayList<Item> shields = findItems(shield, u, "shield", true);
                    number = Integer.parseInt(tokens[1]);
    if (shields.isEmpty()) {
                  } catch (Exception e) {
      addNewError("keine Schilde bekannt");
                    number = Integer.MIN_VALUE;
    }
                  }
 
                }
    ArrayList<Item> armors = findItems(armor, u, "armour", true);
                addNewOrder(scriptStart + "BerufDepotVerwalter " + (number > 0 ? number : ""),
    if (armors.isEmpty()) {
                    true);
      addNewError("keine Rüstungen bekannt");
              } else if (command.equals("BerufWahrnehmer")) {
    }
                addNewOrder(scriptStart + "Lerne " + world.getRules().getSkillType(
 
                    EresseaConstants.S_WAHRNEHMUNG.toString()).getName() + " 10", true);
    Collections.sort(weapons, new ItemSorter(WEAPON_PRIORITIES));
              } else if (command.equals("Depot")) {
    Collections.sort(shields, new ItemSorter(SHIELD_PRIORITIES));
                addNewOrder(scriptStart + "Benoetige " + ALLOrder, true);
    Collections.sort(armors, new ItemSorter(ARMOR_PRIORITIES));
              } else if (command.equals("Versorge")) {
 
                addNewOrder(COMMENTOrder + " Versorge ignored", true);
    if (weaponSkill != null) {
              } else if (command.equals("Erlerne")) {
      List<SkillSpec> targetSkills = new LinkedList<SkillSpec>();
                // prepare Lerne order, but write it later to include several consecutive Erlerne
      targetSkills.add(new SkillSpec(weaponSkill, DEFAULT_LEARNLEVEL, 99));
                // orders
      if (weaponSkill.getName().equals("Hiebwaffen") || weaponSkill.getName().equals("Stangenwaffen")) {
                if (lerneOrder == null) {
        int weeks = (DEFAULT_LEARNLEVEL) * (DEFAULT_LEARNLEVEL + 1) / 2; // weeks for melee
                  lerneOrder = new StringBuilder();
        int weeks2 = (DEFAULT_LEARNLEVEL + 2) * (DEFAULT_LEARNLEVEL + 3) / 2; // weeks for melee + 2
                  lerneOrder.append(scriptStart).append("Lerne");
        int maxskill = (int) (-.5 + Math.sqrt(.25 + 2 * (weeks2 - weeks)));
                }
        targetSkills.add(new SkillSpec(getSkillType(S_ENDURANCE), maxskill, 99));
                lerneOrder.append(" ").append(tokens[1]).append(" ").append(tokens.length > 2
        // getSkillType(S_ENDURANCE), Math.round(ENDURANCERATIO_FRONT * DEFAULT_LEARNLEVEL), 99));
                    ? tokens[2] : "10");
      } else {
                // ignore warning argument "an/aus"
        int weeks = (DEFAULT_LEARNLEVEL) * (DEFAULT_LEARNLEVEL + 1) / 2; // weeks for melee
              } else if (command.equals("Ernaehre")) {
        int weeks2 = (DEFAULT_LEARNLEVEL + 1) * (DEFAULT_LEARNLEVEL + 2) / 2; // weeks for melee + 1
                StringBuilder order = new StringBuilder(scriptStart);
        int maxskill = (int) (-.5 + Math.sqrt(.25 + 2 * (weeks2 - weeks)));
                for (String token : tokens) {
        targetSkills.add(new SkillSpec(getSkillType(S_ENDURANCE), maxskill, 99));
                  if (order.length() > 0) {
        // getSkillType(S_ENDURANCE), Math.round(ENDURANCERATIO_BACK * DEFAULT_LEARNLEVEL), 99));
                    order.append(" ");
      }
                  }
      learn(u, targetSkills);
                  order.append(token);
    }
                }
 
                addNewOrder(order.toString(), true);
    if (!NULL.equals(sWeapon)
              } else if (command.equals("GibKraeuter")) {
        && !reserveEquipment(weapon, weapons, !Warning.W_NEVER.equals(warning) && !Warning.W_SKILL
                addNewOrder(scriptStart + "GibWenn " + tokens[1] + " KRAUT", true);
            .equals(warning))) {
              } else if (command.equals("GibWenn")) {
      addNewError("konnte Waffe nicht reservieren");
                StringBuilder order = new StringBuilder(scriptStart);
    }
                for (int i = 0; i < tokens.length - 1; ++i) {
    if (!NULL.equals(sShield)
                  if (order.length() > 0) {
        && !reserveEquipment(shield, shields, Warning.W_ARMOR.equals(warning) || Warning.W_SHIELD
                    order.append(" ");
            .equals(warning))) {
                  }
      addNewError("konnte Schilde nicht reservieren");
                  order.append(tokens[i]);
    }
                }
    if (!NULL.equals(sArmor) && !reserveEquipment(armor, armors, Warning.W_ARMOR.equals(warning))) {
                if (tokens[tokens.length - 1].equals("aus")) {
      addNewError("konnte Rüstung nicht reservieren");
                  order.append(" Menge");
    }
                }
  }
                if (tokens[tokens.length - 1].equals("nie")) {
 
                  order.append(" nie");
  /**
                }
  * Tries to learn the skills at the ratio reflected in the skills argument.
                addNewOrder(order.toString(), true);
  *
                if (command.equals("Steuermann") && tokens.length != 3) {
  * @param u
                  addNewError("unzulässige Argumente");
  * @param targetSkills
                }
  */
              } else if (command.equals("VersorgeFremd") || command.equals("BenoetigeFremd")) {
  protected void learn(Unit u, Collection<SkillSpec> targetSkills) {
                if (tokens.length > 3 && tokens[3].equals("aus")) {
 
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " 0 " + tokens[2] + " "
    if (hasTeachPlugin()) {
                      + tokens[3], true);
      addToTeachPlugin(currentUnit, targetSkills);
                } else {
      return;
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " " + tokens[2] + " "
    }
                      + tokens[3], true);
 
                }
    if (findLongOrder(u)) {
              } else if (command.equals("Warnung")) {
      addOrder(u, "; langer Befehl gefunden, Einheit wird nicht gelehrt", false);
                StringBuilder order = new StringBuilder(scriptStart);
      return;
                order.append("+0 ");
    }
                for (int i = 1; i < tokens.length; ++i) {
 
                  order.append(tokens[i]);
    // find skill with maximum priority
                }
    double maxWeight = 0;
                addNewOrder(order.toString(), true);
    SkillSpec maxSkill = null;
              } else if (command.equals("Warnung")) {
    StringBuilder comment = new StringBuilder(";");
                // ignore
    for (SkillSpec skill : targetSkills) {
              } else if (command.equals("Erlaube") || command.equals("Verlange") || command.equals(
      double weight = calcSkillWeight(u, skill, targetSkills);
                  "Handel") || command.equals("Mannschaft") || command.equals("Quartiermeister")
      comment.append(" ").append(skill.toString()).append(" ").append(weight);
                  || command.equals("Steuermann") || command.equals("Ueberwache")) {
      if (weight > maxWeight) {
                // simply copy order
        maxWeight = weight;
                StringBuilder order = new StringBuilder(scriptStart);
        maxSkill = skill;
                for (String token : tokens) {
      }
                  if (order.length() > scriptStart.length()) {
    }
                    order.append(" ");
 
                  }
    addNewOrder(comment.toString(), true);
                  order.append(token);
 
                }
    if (maxSkill == null) {
                addNewOrder(order.toString(), true);
      addNewError("kein Kampftalent");
                if (command.equals("Steuermann") && tokens.length != 3) {
    } else {
                  addNewError("unzulässige Argumente");
      removeOrdersLike(LEARNOrder + ".*", true);
                }
      removeOrdersLike(TEACHOrder + ".*", true);
      addNewOrder(LEARNOrder + " " + maxSkill.skill.getName(), true);
    }
  }
 
  private boolean hasTeachPlugin() {
    return TEACH_PREFIX != null;
  }
 
  private void addToTeachPlugin(Unit unit, Collection<SkillSpec> targetSkills) {
    Collection<SkillSpec> oldskills = skills.get(unit);
    if (oldskills != null) {
      Map<SkillType, SkillSpec> map = new OrderedHashtable<SkillType, SkillSpec>();
      for (SkillSpec spec : targetSkills) {
        map.put(spec.skill, spec);
      }
      for (SkillSpec spec : oldskills) {
        map.put(spec.skill, spec);
      }
      for (SkillSpec spec : targetSkills) {
        map.put(spec.skill, spec);
      }
 
      skills.put(unit, map.values());
    } else {
      skills.put(unit, targetSkills);
    }
  }


              } else {
  private void teachUnits() {
                addNewOrder(currentOrder, false);
    for (Unit u : skills.keySet()) {
                addNewError("unbekannter Befehl: " + command);
      if (!findLongOrder(u)) {
              }
        StringBuilder learnOrder = new StringBuilder(String.format("; $%s$L 100.0", TEACH_PREFIX));
            } else if (currentOrder.startsWith("// #forever { ")) {
        for (SkillSpec s : skills.get(u)) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
          learnOrder.append(String.format(" %s %d %d", s.skill.getName(), s.level, s.max));
              addNewOrder("@" + currentOrder.substring(14, currentOrder.lastIndexOf("}") - 1),
        }
                  true);
        addOrder(u, String.format("; $%s$T ALLES 0", TEACH_PREFIX), true);
            } else if (currentOrder.startsWith("// #default")) {
        addOrder(u, learnOrder.toString(), true);
              // ignore
      } else {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
        addOrder(u, "; langer Befehl gefunden, Einheit wird nicht gelehrt", false);
            } else {
      }
              addNewOrder(currentOrder, false);
    }
              addNewError("unbekannter Befehl: " + currentOrder);
  }
              log.fine("unknown order " + currentOrder);
            }
          } else {
            addNewOrder(currentOrder, false);
          }
          currentOrder = null;


        }
  private boolean findLongOrder(Unit u) {
        if (lerneOrder != null) { // unwritten Lerne order
    boolean teach = true;
          addNewOrder(lerneOrder.toString(), true);
    for (Order order : u.getOrders2()) {
          lerneOrder = null;
      if (order.isLong() && !order.getText().startsWith(TEACHOrder) && !order.getText().startsWith(LEARNOrder))
        }
         return true;
 
    }
        if (changedOrders) {
    return false;
          getCurrentUnit().setOrders(newOrders);
        }
        notifyMagellan(getCurrentUnit());
 
         newOrders = null;
      }
   }
   }


   /**
   /**
   * If order is a vorlage command ("// #call Name ..."), returns a List of the tokens. Otherwise
   * Returns a weight ("importance") of a skill according to target values and the unit's current
   * returns <code>null</code>. The first in the list is the first token after the "// #call".
   * skill levels.
   */
   */
   protected static String[] detectVorlageCommand(String order) {
   protected static double calcSkillWeight(Unit u, SkillSpec learningSkill,
     StringTokenizer tokenizer = new StringTokenizer(order, " ");
      Collection<SkillSpec> targetSkills) {
     if (tokenizer.hasMoreTokens()) {
     if (learningSkill == null)
      String part = tokenizer.nextToken();
      return 0;
      if (part.equals(PCOMMENTOrder)) {
     double prio = 0.5;
        if (tokenizer.hasMoreTokens()) {
 
          part = tokenizer.nextToken();
    // calc max mult
          if (part.equals("#call")) {
    SkillSpec learningTarget = null;
//            List<String> result = new ArrayList<String>();
    double maxMult = 0;
            List result = new ArrayList();
 
            while (tokenizer.hasMoreTokens()) {
    for (SkillSpec skill2 : targetSkills) {
              result.add(tokenizer.nextToken());
      if (skill2.skill.equals(learningSkill.skill)) {
            }
        learningTarget = skill2;
            if (result.size() == 0)
      }
              return null;
      int level = Math.max(1, getSkillLevel(u, skill2.skill));
            return result.toArray(new String[] {});
      if (level < skill2.max) {
           }
        double mult = level / (double) skill2.level;
        if (maxMult < mult) {
           maxMult = mult;
         }
         }
       }
       }
     }
     }
    return null;
  }


  /**
    if (learningTarget == null)
  * @param u
      // learned skill is not in target skills
  * @param orderFilter
      return 0.01 * learningSkill.level;
  */
  protected boolean changeOrders(Unit u, OrderFilter orderFilter) {
//     List<String> newOrders2 = new ArrayList<String>();
    List newOrders2 = new ArrayList();
    boolean changedOrders2 = false;


     // loop over orders
     if (maxMult > 0) {
    for (Order command : u.getOrders2()) {
      // calc max normalized learning weeks
      if (orderFilter.changeOrder(command)) {
      double maxWeeks = 0;
        changedOrders2 = true;
      for (SkillSpec skill2 : targetSkills) {
         if (orderFilter.changedOrder() != null) {
        int currentLevel = Math.max(1, getSkillLevel(u, skill2.skill));
           newOrders2.add(orderFilter.changedOrder());
         if (currentLevel < skill2.max) {
           double weeks = skill2.level - currentLevel / maxMult + 2;
          if (maxWeeks < weeks) {
            maxWeeks = weeks;
          }
         }
         }
      }
      int level = Math.max(1, getSkillLevel(u, learningSkill.skill));
      if (level >= learningSkill.max) {
        prio = 0.01d;
       } else {
       } else {
         newOrders2.add(command.getText());
         // prio should be between .4 and 1
        prio = .4 + .6 * (learningTarget.level - level / maxMult + 2) / maxWeeks;
      }
      if (prio < 0) {
        prio = prio * 1.000001;
       }
       }
     }
     }
     if (changedOrders2) {
     return prio;
      u.setOrders(newOrders2);
  }
    }
 
     return changedOrders2;
  protected static int getSkillLevel(Unit u, SkillType skill) {
    Skill uskill = u.getSkill(skill);
     return uskill == null ? 0 : uskill.getLevel();
   }
   }


   /**
   /**
   * Private utility script written to convert some of my faction's orders. Not interesting for the
   * If <code>itemType==null</code> return all the unit's items of the given category. If no item is
   * general public.
   * found, at least one item (with amount 0) is returned.
  *
  * @param faction
  * @param region
   */
   */
   public void cleanShortOrders(Faction faction, Region region) {
   protected static ArrayList<Item> findItems(ItemType itemType, Unit u, String category,
     if (faction == null)
      boolean returnDummy) {
      throw new NullPointerException();
     ArrayList<Item> items = new ArrayList<Item>(1);
     currentFactions = Collections.singletonMap(faction, 1);
     ItemCategory itemCategory = world.getRules().getItemCategory(StringID.create(category));
 
     if (itemType == null) {
    initLocales();
       for (Item item : u.getItems()) {
 
        if (itemCategory.equals(item.getItemType().getCategory())) {
     if (region == null) {
          items.add(item);
       // comment out the following line if you don't have the newest nightly build of Magellan
        }
      helper.getUI().setMaximum(world.getRegions().size());
       }
 
    } else {
       // call self for all regions
       if (u.getItem(itemType) != null) {
       for (Region r : world.getRegions()) {
         items.add(u.getItem(itemType));
         // comment out the following line if you don't have the newest nightly build of Magellan
      }
        helper.getUI().setProgress(r.toString(), ++progress);
    }
 
    if (items.isEmpty() && returnDummy) {
         cleanShortOrders(faction, r);
      for (Object o : itemCategory.getInstances()) {
        ItemType type = (ItemType) o;
         items.add(new Item(type, 0));
        break;
       }
       }
      return;
     }
     }
    return items;
  }


    // loop for all units of faction
  protected boolean reserveEquipment(ItemType preferred, List<Item> ownStuff, boolean warn) {
     for (Unit u : region.units()) {
     if (preferred != null) {
       if (faction.getID().equals(u.getFaction().getID())) {
       // reserve requested weapon
         // comment out the following line if you don't have the newest nightly build of Magellan
      commandNeed(new String[] { "Benoetige", EACHOrder, "1", preferred.getOrderName(),
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
          String.valueOf(DEFAULT_PRIORITY) });
 
    } else if (!ownStuff.isEmpty()) {
         if (changeOrders(u, new ShortOrderFilter(world))) {
      int supply = 0;
           notifyMagellan(u);
      for (Item w : ownStuff) {
         // reserve all matching weapons, except first one
        if (supply > 0) {
          commandNeed(new String[] { "Benoetige",
              Integer.toString(Math.min(currentUnit.getPersons() - supply, w.getAmount())),
              w.getOrderName(), String.valueOf(DEFAULT_PRIORITY) });
        }
        supply += w.getAmount();
         if (supply >= currentUnit.getPersons()) {
           supply = currentUnit.getPersons();
          break;
         }
         }
       }
       }
      // now reserve the first matching weapon with
      // min=w.getAmount(), max=w.getPersons()-other weapons
      Item w = ownStuff.get(0);
      String max =
          Integer.toString(Math.max(0, Math.min(currentUnit.getPersons() - supply + w.getAmount(),
              currentUnit.getPersons())));
      String min = warn ? max : Integer.toString(Math.min(currentUnit.getPersons(), w.getAmount()));
      commandNeed(new String[] { "Benoetige", min, max, w.getOrderName(),
          String.valueOf(DEFAULT_PRIORITY) });
    } else
      return false;
    return true;
  }
  @SuppressWarnings("unused")
  private static Integer putMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2,
      Integer value) {
    Map<String, Integer> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<String, Integer>());
     }
     }
    Integer old = inner.put(key2, value);
    return old;
   }
   }


   /**
   private static void increaseMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2,
  * Private utility script written to convert some of my faction's orders. Not interesting for the
      int value) {
  * general public.
    Map<String, Integer> inner = map.get(key1);
  *
    if (inner == null) {
  * @param faction
      map.put(key1, inner = new HashMap<String, Integer>());
  * @param region
    }
  */
    Integer old = inner.get(key2);
  public void cleanScripts(Faction faction, Region region) {
     if (old == null) {
     if (faction == null)
       inner.put(key2, value);
       throw new NullPointerException();
     } else {
     currentFactions = Collections.singletonMap(faction, 1);
      inner.put(key2, old + value);
    }
  }


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


     if (region == null) {
  @SuppressWarnings("unused")
       // comment out the following line if you don't have the newest nightly build of Magellan
  private static void appendMulti(Map<Object, Map<Object, Collection<Integer>>> map, Object key1,
      helper.getUI().setMaximum(world.getRegions().size());
      Object key2,
      Integer value) {
    Map<Object, Collection<Integer>> inner = map.get(key1);
     if (inner == null) {
       map.put(key1, inner = new HashMap<Object, Collection<Integer>>());
    }
    Collection<Integer> old = inner.get(key2);
    if (old == null) {
      inner.put(key2, old = new ArrayList<Integer>());
    }
    old.add(value);
  }


      // call self for all regions
  private static Integer getMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2) {
      for (Region r : world.getRegions()) {
    Map<String, Integer> inner = map.get(key1);
        // comment out the following line if you don't have the newest nightly build of Magellan
    if (inner != null)
        helper.getUI().setProgress(r.toString(), ++progress);
      return inner.get(key2);
    return null;
  }


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


    // loop for all units of faction
  // ///////////////////////////////////////////////////////
    for (Unit u : region.units()) {
  // HELPER functions
      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>();
  * Adds a warning message (with a to do tag) directly to the unit's orders.
        newOrders = new ArrayList();
  *
//        removedOrderPatterns = new ArrayList<String>();
  * @param unit
        removedOrderPatterns = new ArrayList();
  * @param text
        changedOrders = false;
  */
        error = -1;
  public static void addWarning(Unit unit, String text) {
 
    helper.addOrder(unit, "; TODO: " + text);
        // loop over orders
    setConfirm(unit, false);
        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;
      }
    }


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


   /**
   /**
   * Private utility script written to handle unwanted orders created by the TeachPlugin. Not
   * If confirm is false, mark unit as not confirmable. If confirm is true, mark it as confirmable
   * interesting for the general public.
   * if it has not been marked as unconfirmable before (unconfirm always overrides confirm).
   *
   *
   * @param faction
   * @param u
   * @param region
   * @param confirm
   */
   */
   public void undoTeaching(Faction faction, Region region) {
   public static void setConfirm(Unit u, boolean confirm) {
     if (faction == null)
     String tag = getProperty(u, "confirm");
      throw new NullPointerException();
     if (confirm) {
    currentFactions = Collections.singletonMap(faction, 1);
       if (tag.length() == 0) {
 
         setProperty(u, "confirm", "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;
    } else if (!confirm) {
       setProperty(u, "confirm", "0");
     }
     }
  }


    // loop for all units of faction
  /**
    for (Unit u : region.units()) {
  * Add a property (in form of a tag) to the unit.
      if (faction.getID().equals(u.getFaction().getID())) {
  *
        // comment out the following line if you don't have the newest nightly build of Magellan
  * @param u
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
  * @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;
  }


        setCurrentUnit(u);
  /**
//         newOrders = new ArrayList<String>();
  * Returns the ItemType in the rules matching <code>name</code>.
         newOrders = new ArrayList();
  *
//        removedOrderPatterns = new ArrayList<String>();
  * @param name A item type name, like "Silber"
        removedOrderPatterns = new ArrayList();
  * @return The ItemType corresponding to name or <code>null</code> if this ItemType does not
        changedOrders = false;
  *         exist.
        error = -1;
  */
  public static ItemType getItemType(String name) {
    return world.getRules().getItemType(name);
  }


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


  }
    @Override
 
    public String toString() {
  /**
       return createMessage();
  * Private 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
     private String createMessage() {
    for (Unit u : region.units()) {
       return uip.unit + " needs " + minAmount + "/" + uip.amount + " " + uip.item + " ("
       if (faction.getID().equals(u.getFaction().getID())) {
           + uip.priority + ")";
        // 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;
      }
     }
     }
  }


  /**
     public String getMessage() {
  * Again, a very specialized procedure
       return message;
  */
  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,
   static class Transfer {
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
     private Unit target;
      InvocationTargetException {
     private boolean all;
     ExtendedCommandsHelper.invoke(plugin, "setNamespaces", new Class[] { String.class },
     private Need need;
        new Object[] { namespaces });
     private boolean min;
  }
     private UnitItemPriority uip;
 
  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) {
     public Transfer(Unit unit, Unit target, String item, int amount, boolean min, boolean all,
     log.info("trying to call TeachPlugin method..");
         Need need,
    MagellanPlugIn plugin = helper.getPlugin("magellan.plugin.teacher.TeachPlugin");
         long serial) {
    if (plugin != null) {
       if (item == null)
      try {
         throw new NullPointerException();
        String oldNamespaces = null;
       uip = new UnitItemPriority(unit, item, amount, 0, serial);
        if (namespaces != null) {
       this.target = target;
          oldNamespaces = getNamespaces(plugin);
       this.all = all;
          setNamespaces(plugin, namespaces);
       this.need = need;
        }
       this.min = min;
        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");
     }
     }
  }


  /**
     public void reduceAmount(int change) {
  * Test classes for Beanshell
       uip.amount = UnitItemPriority.reduceAmount(uip.amount, change);
  */
  static class A {
 
    /**
    * @return "A.b"
    */
     public static String b() {
       return "A.b";
     }
     }
  }
  class B {


     public String b() {
     public Unit getUnit() {
       return "B.b";
       return uip.unit;
     }
     }


  }
     public int getAmount() {
 
       return uip.amount;
  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) {
     public void setAmount(int i) {
    if (order.startsWith("// $$L"))
       uip.amount = i;
      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) {
     public String getItem() {
     minAmount = amount;
       return uip.item;
  }
 
  public void reduceMinAmount(int change) {
    if (minAmount == Integer.MAX_VALUE || minAmount == Integer.MIN_VALUE)
       return;
    long newValue = (long) minAmount - (long) change;
    if (newValue > Integer.MAX_VALUE) {
      minAmount = Integer.MAX_VALUE;
    } else if (newValue < Integer.MIN_VALUE) {
      minAmount = Integer.MIN_VALUE;
    } else {
      minAmount = (int) newValue;
     }
     }
  }
  public int getMaxAmount() {
    return getAmount();
  }
  public void setMaxAmount(int amount) {
    setAmount(amount);
  }
  public void reduceMaxAmount(int change) {
    reduceAmount(change);
  }
  /**
  * Returns the value of warning.
  *
  * @return Returns warning.
  */
  public Warning getWarning() {
    return warning;
  }
  /**
  * Sets the value of warning.
  *
  * @param warning The value for warning.
  */
  public void setWarning(Warning warning) {
    this.warning = warning;
  }
//  @Override
  public String toString() {
    return unit + " needs " + minAmount + "/" + amount + " " + item;
  }
}
class Flag {
  boolean positive;
  String name;
  int value;
  public Flag(boolean positive, String name, int value) {
    this.positive = positive;
    this.name = name;
    this.value = value;
  }
//  @Override
  public String toString() {
    return name + "(" + positive + "," + value + ")";
  }
}
class Warning {
//  private List<Flag> flags = new ArrayList<Flag>();
  private List flags = new ArrayList();
  public static final Flag[] ALL_FLAGS = { 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 boolean isMin() {
//  public static Map<String, Flag> NAMES;
       return min;
  public static Map NAMES;
 
  public Warning(boolean all) {
    this(new String[] { E3CommandParser.W_NEVER });
    if (all) {
       setAll();
     }
     }
  }


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


  protected void initStatic() {
     public boolean isAll() {
    initialized = true;
       return all;
//    NAMES = new HashMap<String, Flag>();
    NAMES = new HashMap();
     for (Flag f : ALL_FLAGS) {
       NAMES.put(f.name, f);
     }
     }
  }
  protected void setAll() {
    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;
     public String getMessage() {
    boolean onlyNegative = true;
       return need.getMessage();
    for (; i >= 0; --i)
      if (NAMES.containsKey(tokens[i])) {
        Flag f = NAMES.get(tokens[i]);
        add(f);
        if (f.positive) {
          onlyNegative = false;
        }
       } else if (E3CommandParser.W_ALWAYS.equals(tokens[i])) {
//        flags = new ArrayList<Flag>();
        flags = new ArrayList();
        setAll();
        onlyNegative = false;
      } else if (E3CommandParser.W_NEVER.equals(tokens[i])) {
//        flags = new ArrayList<Flag>();
        flags = new ArrayList();
        onlyNegative = false;
      } else {
        break;
      }
    if (onlyNegative) {
      for (Flag f : ALL_FLAGS) {
        if (f.positive) {
          add(f);
        }
      }
     }
     }


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


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


  public void remove(int f) {
    flags.remove(f);
   }
   }
  public boolean contains(String w) {
    for (Flag f : flags)
      if (f.name.equals(w))
        return true;
    return false;
  }
  public boolean contains(int flag) {
    for (Flag f : flags)
      if (f.value == flag)
        return true;
    return false;
  }
//  @Override
  public String toString() {
    return flags.toString();
  }
}
}
// ---uncommented for BeanShell
void soldier(Unit unit) {
E3CommandParser.soldier(unit);
}
void cleanOcean() {
for (Region r : world.regions().values()) {
if (r.getName() != null && r.getType().toString().contains("Ozean")
&& r.getName().contains("Leere")) {
r.setName(null);
r.setDescription(null);
}
}
}
// ---stop uncomment for BeanShell




</nowiki>
</nowiki>

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

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

Kurzanleitung

Um sie zu nutzen sollte folgende Schritte nachvollziehen:

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

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

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

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


  • Dieses Skript speichern und ausführen

Einheitenbefehle

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

auto

Der einfachste Befehl ist

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

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

 // $cript auto 10

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

 // $cript auto 9

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

 // $cript auto 0

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

// $cript auto 10 10

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

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

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

// $cript auto NICHT

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


Versorge

 // $cript Versorge 100

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

 // $crript Versorge Balsam 95

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


Benoetige

 // $cript Benoetige JE 1 Schwert

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

 // $cript Benoetige 10 100 Silber 120

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

BenoetigeFremd

 // $cript BenoetigeFremd abc 15 Schwert Menge

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

GibWenn

 // $cript GibWenn abc 15 Schwert Menge

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

+X

 // $cript +3 Warnungstext

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


Weitere nützliche Befehle

 // $cript Ueberwache

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

 // $cript Erlaube pnr enr1 enr2 enr3

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

 // $cript Handel x3 ALLES

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

 // $cript Sammler 10

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

// $cript Ernaehre

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

 // $cript Soldat

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

Meta-Befehle

 // $cript 3 LERNEN Wahrnehmung

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

 // $cript 1 LERNEN Wahrnehmung

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

 // $cript 3 LERNEN Wahrnehmung

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

 // $cript 4 $cript Kommando

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

 // $cript 3 $cript Loeschen

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

 // $cript 2 // $cript Kommando

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

 // $cript 5 5 15 NACH no

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

E3CommandParser

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

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

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

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

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

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

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

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

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

  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  static class Reserves {

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

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

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

      }
    }
  }

  static class SkillSpec {

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

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

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

  public static class ItemSorter implements Comparator<Item> {

    private String[] priorities;

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

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

  }

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

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

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

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

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

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

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

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

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

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

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

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

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

  private static final int TEACH_MULTI = 10;

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

  private OrderParser parser;
  private Logger log;

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

  protected OrderParser getParser() {
    return parser;
  }

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

  private Unit someUnit;

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

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

  protected Map<Unit, Integer> capacities;

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

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

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

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

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

  public int getShowStats() {
    return showStats;
  }

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

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

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

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

    executeFrom(faction, region, region);
  }

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

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

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

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

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

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

    findSomeUnit(currentFactions);

    initLocales();

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

    collectStats();

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

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

    relationFactory.restartUpdating();

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

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

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

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

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

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

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

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

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

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

  protected boolean isChangedOrders() {
    return changedOrders;
  }

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

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

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

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

  }

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

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

    return true;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    updateCurrentOrders();

  }

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

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

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

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

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

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

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

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

      setChangedOrders(true);

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

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

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

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

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

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

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

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

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

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

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

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

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

      return;
    }

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

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

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

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

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

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

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

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

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

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

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

    int tokenCount = tokens.length;

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

    tokens = Arrays.copyOf(tokens, tokenCount);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    int volume = currentRegion.maxLuxuries();

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

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

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

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

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

    // Gueterzahl intitialisieren
    int totalVolume = 0;

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

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

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

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

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

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

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

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

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

    }

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

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

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

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

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

    setConfirm(currentUnit, true);
  }

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

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

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

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

    setConfirm(currentUnit, true);
  }

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

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

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

    setConfirm(currentUnit, true);
  }

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

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

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

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

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

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

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

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

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

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

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

  }

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

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

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

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

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

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

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

        // TODO take RESERVE or GIVE orders into account?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  }

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

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

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

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

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

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

  public static class MyReserveVisitor implements ReserveVisitor {

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

  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return result;
  }

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

    Rules rules = world.getRules();

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

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

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

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

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

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

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

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

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

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

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

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

    addNewOrder(comment.toString(), true);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    initLocales();

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

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

        convertVorlage(faction, r);
      }
      return;
    }

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

        setCurrentUnit(u);

        StringBuilder lerneOrder = null;

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

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

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

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

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

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

        updateCurrentOrders();
        setCurrentUnit(null);
      }
  }

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

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

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

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

    initLocales();

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

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

        cleanShortOrders(faction, r);
      }
      return;
    }

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

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

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

    initLocales();

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

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

        cleanScripts(faction, r);
      }
      return;
    }

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

        setCurrentUnit(u);

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

  }

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

    initLocales();

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

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

        undoTeaching(faction, r);
      }
      return;
    }

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

        setCurrentUnit(u);

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

  }

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

    initLocales();

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

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

        fixTwoLongOrders(faction, r);
      }
      return;
    }

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

        setCurrentUnit(u);

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

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

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

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

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

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

    }
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  class B {

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

  }

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

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

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

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

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

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

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

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

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

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

    }

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

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

  static class Supply {

    UnitItemPriority uip;

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

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

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

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

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

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

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

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

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

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

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

  interface OrderFilter {

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

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

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

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

    public String changedOrder() {
      return null;
    }
  }

  static class ShortOrderFilter implements OrderFilter {

    private String result;
    private GameData world;

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

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

    public String changedOrder() {
      return result;
    }
  }

  static class Need {

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

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

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

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

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

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

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

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

    public int getMinAmount() {
      return minAmount;
    }

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

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

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

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

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

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

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

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

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

    public String getMessage() {
      return message;
    }
  }

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

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

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

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

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

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

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

    public boolean isMin() {
      return min;
    }

    public Unit getTarget() {
      return target;
    }

    public boolean isAll() {
      return all;
    }

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

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

    public Need getNeed() {
      return need;
    }

  }
}