public String attempt() throws CMException, PostProcessException 
					{
						String value;
						if((E instanceof MOB) && (stat.equals("ABILITY")))
							value = findOptionalString(E,ignoreStats,defPrefix,"HPMOD",piece,this.defined, debug);
						else
							value = findOptionalString(E,ignoreStats,defPrefix,stat,piece,this.defined, debug);
						if(value != null)
						{
							E.setStat(stat, value);
							if((defPrefix!=null)&&(defPrefix.length()>0))
								addDefinition(defPrefix+stat,value,this.defined);
						}
						return value;
					}
				});
			}
			catch(CMException e)
			{
				Log.errOut(e);
				//should never happen
			}
		}
		return null;
	}

	protected void fillOutStatCodes(Modifiable E, List<String> ignoreStats, String defPrefix, XMLTag piece, Map<String,Object> defined)
	{
		final String[] statCodes = E.getStatCodes();
		for (final String stat : statCodes)
		{
			fillOutStatCode(E,ignoreStats,defPrefix,stat,piece,defined, false);
		}
	}

	protected void fillOutCopyStats(final Modifiable E, final Modifiable E2)
	{
		if((E2 instanceof MOB)&&(!((MOB)E2).isGeneric()))
		{
			for(final GenericBuilder.GenMOBCode stat : GenericBuilder.GenMOBCode.values())
			{
				if(stat != GenericBuilder.GenMOBCode.ABILITY) // because this screws up gen hit points
				{
					E.setStat(stat.name(), CMLib.coffeeMaker().getGenMobStat((MOB)E2,stat.name()));
				}
			}
		}
		else
		if((E2 instanceof Item)&&(!((Item)E2).isGeneric()))
		{
			for(final GenericBuilder.GenItemCode stat : GenericBuilder.GenItemCode.values())
			{
				E.setStat(stat.name(),CMLib.coffeeMaker().getGenItemStat((Item)E2,stat.name()));
			}
		}
		else
		for(final String stat : E2.getStatCodes())
		{
			E.setStat(stat, E2.getStat(stat));
		}
	}

	protected boolean fillOutCopyCodes(final Modifiable E, List<String> ignoreStats, String defPrefix, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		final String copyStatID = findOptionalStringNow(E,ignoreStats,defPrefix,"COPYOF",piece,defined, false);
		if(copyStatID!=null)
		{
			final List<String> V=CMParms.parseCommas(copyStatID,true);
			for(int v=0;v<V.size();v++)
			{
				String s = V.get(v);
				if(s.startsWith("$"))
					s=s.substring(1).trim();
				final XMLTag statPiece =(XMLTag)defined.get(s.toUpperCase().trim());
				if(statPiece == null)
				{
					Object o=CMClass.getMOBPrototype(s);
					if(o==null)
						o=CMClass.getItemPrototype(s);
					if(o==null)
						o=CMClass.getObjectOrPrototype(s);
					if(!(o instanceof Modifiable))
						throw new CMException("Invalid copystat: '"+s+"' on piece '"+piece.tag()+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
					final Modifiable E2=(Modifiable)o;
					fillOutCopyStats(E,E2);
				}
				else
				{
					final XMLTag likePiece =(XMLTag)defined.get(s.toUpperCase().trim());
					if(likePiece == null)
						throw new CMException("Invalid copystat: '"+s+"' on piece '"+piece.tag()+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
					final BuildCallback callBack=new BuildCallback()
					{
						@Override public void willBuild(Environmental E2, XMLTag XMLTag)
						{
							fillOutCopyStats(E,E2);
						}
					};
					findItems(E,likePiece,defined,callBack);
					findMobs(E,likePiece,defined,callBack);
					findAbilities(E,likePiece,defined,callBack);
					findExits(E,likePiece,defined,callBack);
				}
			}
			return V.size()>0;
		}
		return false;
	}

	protected MOB buildMob(final XMLTag piece, final Map<String,Object> defined) throws CMException
	{
		final String classID = findStringNow("class",piece,defined);
		MOB M = null;
		final List<String> ignoreStats=new XVector<String>();
		boolean copyFilled = false;
		if(classID.equalsIgnoreCase("catalog"))
		{
			final String name = findStringNow("NAME",piece,defined);
			if((name == null)||(name.length()==0))
				throw new CMException("Unable to build a catalog mob without a name, Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			M = CMLib.catalog().getCatalogMob(name);
			if(M==null)
				throw new CMException("Unable to find cataloged mob called '"+name+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			M=(MOB)M.copyOf();
			CMLib.catalog().changeCatalogUsage(M,true);
			addDefinition("MOB_CLASS",M.ID(),defined);
			copyFilled=true;
		}
		else
		{
			M = CMClass.getMOB(classID);
			if(M == null)
				throw new CMException("Unable to build mob on classID '"+classID+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			addDefinition("MOB_CLASS",classID,defined);

			if(M.isGeneric())
			{
				copyFilled = fillOutCopyCodes(M,ignoreStats,"MOB_",piece,defined);
				String name = fillOutStatCode(M,ignoreStats,"MOB_","NAME",piece,defined, false);
				if((!copyFilled) && ((name == null)||(name.length()==0)))
					name = fillOutStatCode(M,ignoreStats,"MOB_","NAME",piece,defined, false);
				if((!copyFilled) && ((name == null)||(name.length()==0)))
				{
					name = fillOutStatCode(M,ignoreStats,"MOB_","NAME",piece,defined, true);
					if((!copyFilled) && ((name == null)||(name.length()==0)))
						throw new CMException("Unable to build a mob without a name, Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
				}
				if((name != null)&&(name.length()>0))
					M.setName(name);
			}
		}
		final MOB mob=M;
		addDefinition("MOB_NAME",M.Name(),defined);
		if(!copyFilled)
			M.baseCharStats().setMyRace(CMClass.getRace("StdRace"));

		PostProcessAttempter(defined,new PostProcessAttempt() {
			@Override
			public String attempt() throws CMException, PostProcessException 
			{
				String value = findOptionalString(mob,ignoreStats,"MOB_","LEVEL",piece,this.defined, false);
				if((value != null)&&(value.length()>0))
				{
					mob.setStat("LEVEL",value);
					addDefinition("MOB_LEVEL",value,this.defined);
					CMLib.leveler().fillOutMOB(mob,mob.basePhyStats().level());
					CMLib.leveler().fillOutMOB(mob,mob.basePhyStats().level());
				}
				return value;
			}
		});
		
		PostProcessAttempter(defined,new PostProcessAttempt() {
			@Override
			public String attempt() throws CMException, PostProcessException 
			{
				String value = findOptionalString(mob,ignoreStats,"MOB_","GENDER",piece,this.defined, false);
				if((value != null)&&(value.length()>0))
				{
					mob.baseCharStats().setStat(CharStats.STAT_GENDER,value.charAt(0));
					addDefinition("MOB_GENDER",value,this.defined);
				}
				else
					mob.baseCharStats().setStat(CharStats.STAT_GENDER,CMLib.dice().rollPercentage()>50?'M':'F');
				PostProcessAttempter(this.defined,new PostProcessAttempt() {
					@Override
					public String attempt() throws CMException, PostProcessException 
					{
						String value = findOptionalString(mob,ignoreStats,"MOB_","RACE",piece,this.defined, false);
						if((value != null)&&(value.length()>0))
						{
							mob.setStat("RACE",value);
							addDefinition("MOB_RACE",value,this.defined);
							Race R=CMClass.getRace(value);
							if(R==null)
							{
								final List<Race> races=findRaces(mob,piece, this.defined);
								if(races.size()>0)
									R=races.get(CMLib.dice().roll(1, races.size(), -1));
							}
							if(R!=null)
								R.setHeightWeight(mob.basePhyStats(),(char)mob.baseCharStats().getStat(CharStats.STAT_GENDER));
						}
						return value;
					}
				});
				return value;
			}
		});

		PostProcessAttempter(defined, new PostProcessAttempt()
		{
			@Override
			public String attempt() throws CMException, PostProcessException 
			{
				final List<XMLLibrary.XMLTag> choices = getAllChoices(mob,ignoreStats,"MOB_","FACTION", piece, this.defined, true);
				if((choices!=null)&&(choices.size()>0))
				{
					for(int c=0;c<choices.size();c++)
					{
						final XMLTag valPiece = choices.get(c);
						final String f = valPiece.getParmValue("ID");
						final String v = valPiece.getParmValue("VALUE");
						mob.addFaction(f, Integer.parseInt(v));
					}
				}
				return "";
			}
		});
		ignoreStats.addAll(Arrays.asList(new String[]{"CLASS","NAME","LEVEL","GENDER"}));
		fillOutStatCodes(M,ignoreStats,"MOB_",piece,defined);
		M.recoverCharStats();
		M.recoverPhyStats();
		M.recoverMaxState();

		final List<Item> items = findItems(piece,defined);
		for(int i=0;i<items.size();i++)
		{
			final Item I=items.get(i);
			M.addItem(I);
			I.setSavable(true);
			I.wearIfPossible(M);
		}
		final List<Ability> aV = findAffects(M,piece,defined,null);
		for(int i=0;i<aV.size();i++)
		{
			final Ability A=aV.get(i);
			A.setSavable(true);
			M.addNonUninvokableEffect(A);
		}
		final List<Behavior> bV= findBehaviors(M,piece,defined);
		for(int i=0;i<bV.size();i++)
		{
			final Behavior B=bV.get(i);
			B.setSavable(true);
			M.addBehavior(B);
		}
		final List<Ability> abV = findAbilities(M,piece,defined,null);
		for(int i=0;i<abV.size();i++)
		{
			final Ability A=abV.get(i);
			A.setSavable(true);
			M.addAbility(A);
		}
		final ShopKeeper SK=CMLib.coffeeShops().getShopKeeper(M);
		if(SK!=null)
		{
			final List<Triad<Environmental,Integer,Long>> iV = findShopInventory(M,piece,defined);
			if(iV.size()>0)
				SK.getShop().emptyAllShelves();
			for(int i=0;i<iV.size();i++)
				SK.getShop().addStoreInventory(iV.get(i).first,iV.get(i).second.intValue(),iV.get(i).third.intValue());
		}

		M.recoverCharStats();
		M.recoverPhyStats();
		M.recoverMaxState();
		M.text();
		M.setMiscText(M.text());
		M.recoverCharStats();
		M.recoverPhyStats();
		M.recoverMaxState();
		return M;
	}

	protected List<Exit> findExits(Modifiable M,XMLTag piece, Map<String,Object> defined, BuildCallback callBack) throws CMException
	{
		try
		{
			final List<Exit> V = new Vector<Exit>();
			final String tagName="EXIT";
			final List<XMLLibrary.XMLTag> choices = getAllChoices(null,null,null,tagName, piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag valPiece = choices.get(c);
				if(valPiece.parms().containsKey("VALIDATE") && !testCondition(M,null,null,CMLib.xml().restoreAngleBrackets(valPiece.getParmValue("VALIDATE")),valPiece, defined))
					continue;
				if(CMSecurity.isDebugging(CMSecurity.DbgFlag.MUDPERCOLATOR))
					Log.debugOut("MUDPercolator","Build Exit: "+CMStrings.limit(valPiece.value(),80)+"...");
				defineReward(M,null,null,valPiece.getParmValue("DEFINE"),valPiece,null,defined,true);
				final Set<String> definedSet=getPrevouslyDefined(defined,tagName+"_");
				final Exit E=buildExit(valPiece,defined);
				if(callBack != null)
					callBack.willBuild(E, valPiece);
				V.add(E);
				clearNewlyDefined(defined, definedSet, tagName+"_");
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}

	// remember to check ROOMLINK_DIR for N,S,E,W,U,D,etc..
	protected Exit buildExit(XMLTag piece, Map<String,Object> defined) throws CMException
	{
		final List<String> ignoreStats=new XVector<String>();
		final String classID = findStringNow("class",piece,defined);
		final Exit E = CMClass.getExit(classID);
		if(E == null)
			throw new CMException("Unable to build exit on classID '"+classID+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		addDefinition("EXIT_CLASS",classID,defined);
		ignoreStats.add("CLASS");
		fillOutCopyCodes(E,ignoreStats,"EXIT_",piece,defined);
		fillOutStatCodes(E,ignoreStats,"EXIT_",piece,defined);
		final List<Ability> aV = findAffects(E,piece,defined,null);
		for(int i=0;i<aV.size();i++)
		{
			final Ability A=aV.get(i);
			A.setSavable(true);
			E.addNonUninvokableEffect(A);
		}
		final List<Behavior> bV= findBehaviors(E,piece,defined);
		for(int i=0;i<bV.size();i++)
		{
			final Behavior B=bV.get(i);
			B.setSavable(true);
			E.addBehavior(B);
		}
		E.text();
		E.setMiscText(E.text());
		E.recoverPhyStats();
		return E;
	}

	protected List<Triad<Environmental,Integer,Long>> findShopInventory(final Modifiable E,final XMLTag piece, final Map<String,Object> defined) throws CMException
	{
		try
		{
			final List<Triad<Environmental,Integer,Long>> V = new Vector<Triad<Environmental,Integer,Long>>();
			final List<XMLLibrary.XMLTag> choices = getAllChoices(E,null,null,"SHOPINVENTORY", piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag shopPiece = choices.get(c);
				if(shopPiece.parms().containsKey("VALIDATE") && !testCondition(E,null,null,CMLib.xml().restoreAngleBrackets(shopPiece.getParmValue("VALIDATE")),shopPiece, defined))
					continue;
				final String baseNumber[] = { "1" };
				final String basePrice[] = { "-1" };
				try
				{
					final String baseStr=shopPiece.getParmValue("NUMBER");
					if(baseStr != null)
						baseNumber[0]=baseStr;
				}
				catch(final Exception e){ }
				try
				{
					final String baseStr=shopPiece.getParmValue("PRICE");
					if(baseStr != null)
						basePrice[0]=baseStr;
				}
				catch(final Exception e){ }
				final BuildCallback callBack=new BuildCallback()
				{
					@Override 
					public void willBuild(Environmental E, XMLTag XMLTag)
					{
						String numbStr=XMLTag.getParmValue("NUMBER");
						if(numbStr == null)
							numbStr=baseNumber[0];
						int number;
						try{ number=CMath.parseIntExpression(strFilter(E,null,null,numbStr.trim(),piece, defined)); } catch(final Exception e){ number=1; }
						numbStr=XMLTag.getParmValue("PRICE");
						if(numbStr == null)
							numbStr=basePrice[0];
						long price;
						try{ price=CMath.parseLongExpression(strFilter(E,null,null,numbStr.trim(),piece, defined)); } catch(final Exception e){ price=-1; }
						V.add(new Triad<Environmental,Integer,Long>(E,Integer.valueOf(number),Long.valueOf(price)));
					}
				};
				findItems(E,shopPiece,defined,callBack);
				findMobs(E,shopPiece,defined,callBack);
				findAbilities(E,shopPiece,defined,callBack);
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}

	protected Set<String> getPrevouslyDefined(Map<String,Object> defined, String prefix)
	{
		final Set<String> prevSet=new HashSet<String>();
		for(final String key : defined.keySet())
		{
			if(key.toUpperCase().startsWith(prefix.toUpperCase()))
				prevSet.add(key.toUpperCase());
		}
		return prevSet;
	}

	protected void clearNewlyDefined(Map<String,Object> defined, Set<String> exceptSet, String prefix)
	{
		final Set<String> clearSet=new HashSet<String>();
		for(final String key : defined.keySet())
			if(key.toUpperCase().startsWith(prefix.toUpperCase())
			&& (!exceptSet.contains(key.toUpperCase()))
			&& (!key.startsWith("_")))
				clearSet.add(key);
		for(final String key : clearSet)
			defined.remove(key);
	}

	@Override
	public List<Item> findItems(XMLTag piece, Map<String,Object> defined) throws CMException
	{
		return findItems(null,piece, defined, null);
	}

	protected List<Item> findItems(Modifiable E,XMLTag piece, Map<String,Object> defined, BuildCallback callBack) throws CMException
	{
		try
		{
			final List<Item> V = new Vector<Item>();
			final String tagName="ITEM";
			final List<XMLLibrary.XMLTag> choices = getAllChoices(E,null,null,tagName, piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag valPiece = choices.get(c);
				if(valPiece.parms().containsKey("VALIDATE") && !testCondition(E,null,null,CMLib.xml().restoreAngleBrackets(valPiece.getParmValue("VALIDATE")),valPiece, defined))
					continue;
				defineReward(E,null,null,valPiece.getParmValue("DEFINE"),valPiece,null,defined,true);
				if(CMSecurity.isDebugging(CMSecurity.DbgFlag.MUDPERCOLATOR))
					Log.debugOut("MUDPercolator","Build Item: "+CMStrings.limit(valPiece.value(),80)+"...");
				final Set<String> definedSet=getPrevouslyDefined(defined,tagName+"_");
				try
				{
					for(final Item I : buildItem(valPiece,defined))
					{
						if(callBack != null)
							callBack.willBuild(I, valPiece);
						V.add(I);
					}
				}
				catch(final CMException e)
				{
					throw e;
				}
				clearNewlyDefined(defined, definedSet, tagName+"_");
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}

	protected List<Item> findContents(XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			final List<Item> V = new Vector<Item>();
			final String tagName="CONTENT";
			final List<XMLLibrary.XMLTag> choices = getAllChoices(null,null,null,tagName, piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag valPiece = choices.get(c);
				if(CMSecurity.isDebugging(CMSecurity.DbgFlag.MUDPERCOLATOR))
					Log.debugOut("MUDPercolator","Found Content: "+valPiece.value());
				V.addAll(findItems(valPiece,defined));
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}

	protected String getMetacraftFilter(String recipe, XMLTag piece, Map<String,Object> defined, Triad<Integer,Integer,Class<?>[]> filter) throws CMException
	{
		int levelLimit=-1;
		int levelFloor=-1;
		Class<?>[] deriveClasses=new Class[0];
		final Map.Entry<Character, String>[] otherParms=CMStrings.splitMulti(recipe, splitters);
		recipe=otherParms[0].getValue();
		if(otherParms.length==1)
			return recipe;
		for(int i=1;i<otherParms.length;i++)
		{
			if(otherParms[i].getKey().charValue()=='<')
			{
				final String lvlStr=strFilterNow(null,null,null,otherParms[i].getValue().trim(),piece, defined);
				if(CMath.isMathExpression(lvlStr))
				{
					levelLimit=CMath.parseIntExpression(lvlStr);
					if((levelLimit==0)||(levelLimit<levelFloor))
						levelLimit=-1;
				}
			}
			else
			if(otherParms[i].getKey().charValue()=='>')
			{
				final String lvlStr=strFilterNow(null,null,null,otherParms[i].getValue().trim(),piece, defined);
				if(CMath.isMathExpression(lvlStr))
				{
					levelFloor=CMath.parseIntExpression(lvlStr);
					if((levelFloor==0)||((levelFloor>levelLimit)&&(levelLimit>0)))
						levelFloor=-1;
				}
			}
			else
			if(otherParms[i].getKey().charValue()=='=')
			{
				final String classStr=strFilterNow(null,null,null,otherParms[i].getValue().trim(),piece, defined);
				final Object O=CMClass.getItemPrototype(classStr);
				if(O!=null)
				{
					deriveClasses=Arrays.copyOf(deriveClasses, deriveClasses.length+1);
					deriveClasses[deriveClasses.length-1]=O.getClass();
				}
				else
					throw new CMException("Unknown metacraft class= "+classStr);
			}
		}
		if(levelLimit>0)
			filter.first=Integer.valueOf(levelLimit);
		if(levelFloor>0)
			filter.second=Integer.valueOf(levelFloor);
		if(deriveClasses.length>0)
			filter.third=deriveClasses;
		return recipe;
	}

	@SuppressWarnings("unchecked")
	protected List<ItemCraftor.ItemKeyPair> craftAllOfThisRecipe(ItemCraftor skill, int material, Map<String,Object> defined)
	{
		List<ItemCraftor.ItemKeyPair> skillContents=(List<ItemCraftor.ItemKeyPair>)defined.get("____COFFEEMUD_"+skill.ID()+"_"+material+"_true");
		if(skillContents==null)
		{
			if(material>=0)
				skillContents=skill.craftAllItemSets(material, true);
			else
				skillContents=skill.craftAllItemSets(true);
			if(skillContents==null)
				return null;
			defined.put("____COFFEEMUD_"+skill.ID()+"_"+material+"_true",skillContents);
		}
		final List<ItemCraftor.ItemKeyPair> skillContentsCopy=new Vector<ItemCraftor.ItemKeyPair>(skillContents.size());
		skillContentsCopy.addAll(skillContents);
		return skillContentsCopy;
	}

	protected boolean checkMetacraftItem(Item I, Triad<Integer,Integer,Class<?>[]> filter)
	{
		final int levelLimit=filter.first.intValue();
		final int levelFloor=filter.second.intValue();
		final Class<?>[] deriveClasses=filter.third;
		if(((levelLimit>0) && (I.basePhyStats().level() > levelLimit))
		||((levelFloor>0) && (I.basePhyStats().level() < levelFloor)))
			return false;
		if(deriveClasses.length==0)
			return true;
		for(final Class<?> C : deriveClasses)
		{
			if(C.isAssignableFrom(I.getClass()))
				return true;
		}
		return false;
	}

	protected List<Item> buildItem(XMLTag piece, Map<String,Object> defined) throws CMException
	{
		final String classID = findStringNow("class",piece,defined);
		final List<Item> contents = new Vector<Item>();
		final List<String> ignoreStats=new XVector<String>();
		if(classID.toLowerCase().startsWith("metacraft"))
		{
			final String classRest=classID.substring(9).toLowerCase().trim();
			final Triad<Integer,Integer,Class<?>[]> filter = new Triad<Integer,Integer,Class<?>[]>(Integer.valueOf(-1),Integer.valueOf(-1),new Class<?>[0]);
			String recipe="anything";
			if(classRest.startsWith(":"))
			{
				recipe=getMetacraftFilter(classRest.substring(1).trim(), piece, defined, filter);
			}
			else
			{
				recipe = findStringNow("NAME",piece,defined);
				if((recipe == null)||(recipe.length()==0))
					throw new CMException("Unable to metacraft with malformed class Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			}

			final String materialStr = findOptionalStringNow(null,null,null,"material",piece,defined, false);
			int material=-1;
			if(materialStr!=null)
				 material = RawMaterial.CODES.FIND_IgnoreCase(materialStr);
			final List<ItemCraftor> craftors=new Vector<ItemCraftor>();
			for(final Enumeration<Ability> e=CMClass.abilities();e.hasMoreElements();)
			{
				final Ability A=e.nextElement();
				if(A instanceof ItemCraftor)
					craftors.add((ItemCraftor)A);
			}
			if(recipe.equalsIgnoreCase("anything"))
			{
				final long startTime=System.currentTimeMillis();
				while((contents.size()==0)&&((System.currentTimeMillis()-startTime)<1000))
				{
					final ItemCraftor skill=craftors.get(CMLib.dice().roll(1,craftors.size(),-1));
					if(skill.fetchRecipes().size()>0)
					{
						final List<ItemCraftor.ItemKeyPair> skillContents=craftAllOfThisRecipe(skill,material,defined);
						if(skillContents.size()==0) // preliminary error messaging, just for the craft skills themselves
							Log.errOut("MUDPercolator","Tried metacrafting anything, got "+Integer.toString(skillContents.size())+" from "+skill.ID());
						else
						for(int i=skillContents.size()-1;i>=0;i--)
						{
							final Item I=skillContents.get(i).item;
							if(!checkMetacraftItem(I, filter))
								skillContents.remove(i);
						}
						if(skillContents.size()>0)
							contents.add((Item)skillContents.get(CMLib.dice().roll(1,skillContents.size(),-1)).item.copyOf());
					}
				}
			}
			else
			if(recipe.toLowerCase().startsWith("any-"))
			{
				List<ItemCraftor.ItemKeyPair> skillContents=null;
				recipe=recipe.substring(4).trim();
				for(final ItemCraftor skill : craftors)
				{
					if(skill.ID().equalsIgnoreCase(recipe))
					{
						skillContents=craftAllOfThisRecipe(skill,material,defined);
						if((skillContents==null)||(skillContents.size()==0)) // this is just for checking the skills themselves
							Log.errOut("MUDPercolator","Tried metacrafting any-"+recipe+", got "+Integer.toString(contents.size())+" from "+skill.ID());
						else
						for(int i=skillContents.size()-1;i>=0;i--)
						{
							final Item I=skillContents.get(i).item;
							if(!checkMetacraftItem(I, filter))
								skillContents.remove(i);
						}
						break;
					}
				}
				if((skillContents!=null)&&(skillContents.size()>0))
					contents.add((Item)skillContents.get(CMLib.dice().roll(1,skillContents.size(),-1)).item.copyOf());
			}
			else
			if(recipe.toLowerCase().startsWith("all"))
			{
				List<ItemCraftor.ItemKeyPair> skillContents=null;
				recipe=recipe.substring(3).startsWith("-")?recipe.substring(4).trim():"";
				for(final ItemCraftor skill : craftors)
				{
					if(skill.ID().equalsIgnoreCase(recipe)||(recipe.length()==0))
					{
						skillContents=craftAllOfThisRecipe(skill,material,defined);
						if((skillContents==null)||(skillContents.size()==0)) // this is just for checking the skills themselves
							Log.errOut("MUDPercolator","Tried metacrafting any-"+recipe+", got "+Integer.toString(contents.size())+" from "+skill.ID());
						else
						for(int i=skillContents.size()-1;i>=0;i--)
						{
							final Item I=skillContents.get(i).item;
							if(!checkMetacraftItem(I, filter))
								skillContents.remove(i);
						}
						while((skillContents!=null)&&(skillContents.size()>0))
							contents.add((Item)skillContents.remove(0).item.copyOf());
						if(recipe.length()>0)
							break;
					}
				}
			}
			else
			{
				for(final ItemCraftor skill : craftors)
				{
					final List<List<String>> V=skill.matchingRecipeNames(recipe,false);
					if((V!=null)&&(V.size()>0))
					{
						ItemCraftor.ItemKeyPair pair;
						if(material>=0)
							pair=skill.craftItem(recipe,material,true);
						else
							pair=skill.craftItem(recipe,-1,true);
						if(pair!=null)
						{
							contents.add(pair.item);
							break;
						}
					}
				}
				for(int i=contents.size()-1;i>=0;i--)
				{
					final Item I=contents.get(i);
					if(!checkMetacraftItem(I, filter))
						contents.remove(i);
				}
				if(contents.size()==0)
					for(final ItemCraftor skill : craftors)
					{
						final List<List<String>> V=skill.matchingRecipeNames(recipe,true);
						if((V!=null)&&(V.size()>0))
						{
							ItemCraftor.ItemKeyPair pair;
							if(material>=0)
								pair=skill.craftItem(recipe,material,true);
							else
								pair=skill.craftItem(recipe,0,true);
							if(pair!=null)
							{
								contents.add(pair.item);
								break;
							}
						}
					}
				for(int i=contents.size()-1;i>=0;i--)
				{
					final Item I=contents.get(i);
					if(!checkMetacraftItem(I, filter))
						contents.remove(i);
				}
			}
			if(contents.size()==0)
			{
				if(filter.equals(emptyMetacraftFilter))
					throw new CMException("Unable to metacraft an item called '"+recipe+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
				else
					return new ArrayList<Item>(0);
			}
			for(final Item I : contents)
			{
				addDefinition("ITEM_CLASS",I.ID(),defined);
				addDefinition("ITEM_NAME",I.Name(),defined); // define so we can mess with it
				addDefinition("ITEM_LEVEL",""+I.basePhyStats().level(),defined); // define so we can mess with it
				fillOutStatCode(I,ignoreStats,"ITEM_","NAME",piece,defined, false);
				fillOutStatCode(I,ignoreStats,"ITEM_","LEVEL",piece,defined, false);
			}
			ignoreStats.addAll(Arrays.asList(new String[]{"CLASS","MATERIAL","NAME","LEVEL"}));
		}
		else
		if(classID.equalsIgnoreCase("catalog"))
		{
			final String name = findStringNow("NAME",piece,defined);
			if((name == null)||(name.length()==0))
				throw new CMException("Unable to build a catalog item without a name, Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			Item I = CMLib.catalog().getCatalogItem(name);
			if(I==null)
				throw new CMException("Unable to find cataloged item called '"+name+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			I=(Item)I.copyOf();
			CMLib.catalog().changeCatalogUsage(I,true);
			contents.add(I);
			addDefinition("ITEM_CLASS",I.ID(),defined);
			addDefinition("ITEM_NAME",I.Name(),defined); // define so we can mess with it
			ignoreStats.addAll(Arrays.asList(new String[]{"CLASS","NAME"}));
		}
		else
		{
			final Item I = CMClass.getItem(classID);
			if(I == null)
				throw new CMException("Unable to build item on classID '"+classID+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			contents.add(I);
			addDefinition("ITEM_CLASS",classID,defined);

			if(I.isGeneric())
			{
				final boolean filledOut = fillOutCopyCodes(I,ignoreStats,"ITEM_",piece,defined);
				final String name = fillOutStatCode(I,ignoreStats,"ITEM_","NAME",piece,defined, false);
				if((!filledOut) && ((name == null)||(name.length()==0)))
					throw new CMException("Unable to build an item without a name, Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
				if((name != null)&&(name.length()>0))
					I.setName(name);
			}
			ignoreStats.addAll(Arrays.asList(new String[]{"CLASS","NAME"}));
			addDefinition("ITEM_NAME",I.Name(),defined); // define so we can mess with it
		}

		final int contentSize=contents.size();
		for(int it=0;it<contentSize;it++) // no iterator, please!!
		{
			final Item I=contents.get(it);
			fillOutStatCodes(I,ignoreStats,"ITEM_",piece,defined);
			I.recoverPhyStats();
			CMLib.itemBuilder().balanceItemByLevel(I);
			I.recoverPhyStats();
			fillOutStatCodes(I,ignoreStats,"ITEM_",piece,defined);
			I.recoverPhyStats();

			if(I instanceof Container)
			{
				final List<Item> V= findContents(piece,defined);
				for(int i=0;i<V.size();i++)
				{
					final Item I2=V.get(i);
					I2.setContainer((Container)I);
					contents.add(I2);
				}
			}
			{
				final List<Ability> V= findAffects(I,piece,defined,null);
				for(int i=0;i<V.size();i++)
				{
					final Ability A=V.get(i);
					A.setSavable(true);
					I.addNonUninvokableEffect(A);
				}
			}
			final List<Behavior> V = findBehaviors(I,piece,defined);
			for(int i=0;i<V.size();i++)
			{
				final Behavior B=V.get(i);
				B.setSavable(true);
				I.addBehavior(B);
			}
			I.recoverPhyStats();
			I.text();
			I.setMiscText(I.text());
			I.recoverPhyStats();
		}
		return contents;
	}

	protected List<Ability> findAffects(final Modifiable E, XMLTag piece, Map<String,Object> defined, BuildCallback callBack) throws CMException
	{
		return findAbilities(E,"AFFECT",piece,defined,callBack);
	}

	protected List<Ability> findAbilities(final Modifiable E, XMLTag piece, Map<String,Object> defined, BuildCallback callBack) throws CMException
	{
		return findAbilities(E,"ABILITY",piece,defined,callBack);
	}

	protected List<Ability> findAbilities(final Modifiable E, String tagName, XMLTag piece, Map<String,Object> defined, BuildCallback callBack) throws CMException
	{
		try
		{
			final List<Ability> V = new Vector<Ability>();
			final List<XMLLibrary.XMLTag> choices = getAllChoices(E,null,null,tagName, piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag valPiece = choices.get(c);
				if(valPiece.parms().containsKey("VALIDATE") && !testCondition(E,null,null,CMLib.xml().restoreAngleBrackets(valPiece.getParmValue("VALIDATE")),valPiece, defined))
					continue;
				defineReward(E,null,null,valPiece.getParmValue("DEFINE"),valPiece,null,defined,true);
				final Set<String> definedSet=getPrevouslyDefined(defined,tagName+"_");
				final Ability A=buildAbility(E,valPiece,defined);
				if(callBack != null)
					callBack.willBuild(A, valPiece);
				V.add(A);
				clearNewlyDefined(defined, definedSet, tagName+"_");
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}

	protected List<Behavior> findBehaviors(final Modifiable E,XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			final List<Behavior> V = new Vector<Behavior>();
			final String tagName="BEHAVIOR";
			final List<XMLLibrary.XMLTag> choices = getAllChoices(E,null,null,tagName, piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag valPiece = choices.get(c);
				if(valPiece.parms().containsKey("VALIDATE") && !testCondition(E,null,null,CMLib.xml().restoreAngleBrackets(valPiece.getParmValue("VALIDATE")),valPiece, defined))
					continue;
				defineReward(E,null,null,valPiece.getParmValue("DEFINE"),valPiece,null,defined,true);
				final Set<String> definedSet=getPrevouslyDefined(defined,tagName+"_");
				final Behavior B=buildBehavior(E,valPiece,defined);
				V.add(B);
				clearNewlyDefined(defined, definedSet, tagName+"_");
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}

	protected List<Race> findRaces(final Modifiable E, XMLTag piece, Map<String,Object> defined) throws CMException, PostProcessException
	{
		final List<Race> V = new Vector<Race>();
		final String tagName="RACE";
		final List<XMLLibrary.XMLTag> choices = getAllChoices(E,null,null,tagName, piece, defined,true);
		if((choices==null)||(choices.size()==0))
			return V;
		for(int c=0;c<choices.size();c++)
		{
			final XMLTag valPiece = choices.get(c);
			if(valPiece.parms().containsKey("VALIDATE") && !testCondition(E,null,null,CMLib.xml().restoreAngleBrackets(valPiece.getParmValue("VALIDATE")),valPiece, defined))
				continue;
			defineReward(E,null,null,valPiece.getParmValue("DEFINE"),valPiece,null,defined,true);
			final Set<String> definedSet=getPrevouslyDefined(defined,tagName+"_");
			final Race R=buildGenRace(E,valPiece,defined);
			V.add(R);
			clearNewlyDefined(defined, definedSet, tagName+"_");
		}
		return V;
	}

	protected Ability buildAbility(final Modifiable E, final XMLTag piece, final Map<String,Object> defined) throws CMException
	{
		final String classID = findStringNow("class",piece,defined);
		Ability A=CMClass.getAbility(classID);
		if(A == null)
			A=CMClass.findAbility(classID);
		if(A == null)
			throw new CMException("Unable to build ability on classID '"+classID+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		final Ability aA=A;
		PostProcessAttempter(defined,new PostProcessAttempt() {
			@Override
			public String attempt() throws CMException, PostProcessException 
			{
				final String value = findOptionalString(E,null,null,"PARMS",piece,this.defined, false);
				if(value != null)
					aA.setMiscText(value);
				return value;
			}
		});
		return A;
	}

	protected Behavior buildBehavior(final Modifiable E, final XMLTag piece, final Map<String,Object> defined) throws CMException
	{
		final String classID = findStringNow("class",piece,defined);
		Behavior B=CMClass.getBehavior(classID);
		if(B == null)
			B=CMClass.findBehavior(classID);
		if(B == null)
			throw new CMException("Unable to build behavior on classID '"+classID+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		final Behavior bB=B;
		PostProcessAttempter(defined,new PostProcessAttempt() {
			@Override
			public String attempt() throws CMException, PostProcessException 
			{
				final String value = findOptionalString(E,null,null,"PARMS",piece,this.defined, false);
				if(value != null)
					bB.setParms(value);
				return value;
			}
		});
		return B;
	}

	protected List<AbilityMapping> findRaceAbles(final Modifiable E, String tagName, final String prefix, final XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			final List<AbilityMapping> V = new Vector<AbilityMapping>();
			final List<XMLLibrary.XMLTag> choices = getAllChoices(E,null,prefix,tagName, piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag valPiece = choices.get(c);
				if(valPiece.parms().containsKey("VALIDATE") && !testCondition(E,null,prefix,CMLib.xml().restoreAngleBrackets(valPiece.getParmValue("VALIDATE")),valPiece, defined))
					continue;
				defineReward(E,null,prefix,valPiece.getParmValue("DEFINE"),valPiece,null,defined,true);
				final Set<String> definedSet=getPrevouslyDefined(defined,tagName+"_");
				final String classID = findStringNow("CLASS",valPiece,defined);
				Ability A=CMClass.getAbility(classID);
				if(A == null)
					A=CMClass.findAbility(classID);
				if(A == null)
					throw new CMException("Unable to build ability on classID '"+classID+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
				defined.put(prefix+"CLASS", classID);
				final AbilityMapping mapA=CMLib.ableMapper().newAbilityMapping().ID(classID);
				String value;
				value=findOptionalStringNow(E, null, prefix, "PARMS", valPiece, defined, false);
				if(value != null)
				{
					mapA.defaultParm(value);
					defined.put(prefix+"PARMS", value);
				}
				value=findOptionalStringNow(E, null, prefix, "PROFF", valPiece, defined, false);
				if(value != null)
				{
					mapA.defaultProficiency(CMath.parseIntExpression(value));
					defined.put(prefix+"PROFF", Integer.valueOf(mapA.defaultProficiency()));
				}
				value=findOptionalStringNow(E, null, prefix, "LEVEL", valPiece, defined, false);
				if(value != null)
				{
					mapA.qualLevel(CMath.parseIntExpression(value));
					defined.put(prefix+"LEVEL", Integer.valueOf(mapA.qualLevel()));
				}
				value=findOptionalStringNow(E, null, prefix, "QUALIFY", valPiece, defined, false);
				if(value != null)
				{
					mapA.autoGain(!CMath.s_bool(value));
					defined.put(prefix+"QUALIFY", value);
				}
				V.add(mapA);
				clearNewlyDefined(defined, definedSet, tagName+"_");
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}

	protected List<Item> getRaceItems(final Modifiable E, String tagName, final String prefix, final XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			final List<Item> V = new Vector<Item>();
			final List<XMLLibrary.XMLTag> choices = getAllChoices(E,null,prefix,tagName, piece, defined,true);
			if((choices==null)||(choices.size()==0))
				return V;
			for(int c=0;c<choices.size();c++)
			{
				final XMLTag valPiece = choices.get(c);
				if(CMSecurity.isDebugging(CMSecurity.DbgFlag.MUDPERCOLATOR))
					Log.debugOut("MUDPercolator","Found Race Item: "+valPiece.value());
				V.addAll(findItems(valPiece,defined));
			}
			return V;
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object type: "+pe.getMessage(),pe);
		}
	}
	
	protected Race buildGenRace(Modifiable E, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		final String classID = findStringNow("class",piece,defined);
		Race R=CMClass.getRace(classID);
		if( R != null)
			return R;
		R=(Race)CMClass.getRace("GenRace").copyOf();
		int numStatsFound=0;
		for(String stat : R.getStatCodes())
			try
			{
				if(findOptionalString(null, null, null, stat, piece, defined, false)!=null)
					numStatsFound++;
			}
			catch(PostProcessException pe)
			{
				numStatsFound++;
			}
		if(numStatsFound<5)
			throw new CMException("Too few fields to build race on classID '"+classID+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		R.setRacialParms("<RACE><ID>"+CMStrings.capitalizeAndLower(classID)+"</ID><NAME>"+CMStrings.capitalizeAndLower(classID)+"</NAME></RACE>");
		CMClass.addRace(R);
		addDefinition("RACE_CLASS",R.ID(),defined); // define so we can mess with it
		R.setStat("NAME", findStringNow("name",piece,defined));
		addDefinition("RACE_NAME",R.name(),defined); // define so we can mess with it
		final List<String> ignoreStats=new XVector<String>(new String[]{"CLASS","NAME"});
		
		final List<Item> raceWeapons=getRaceItems(E,"WEAPON","RACE_WEAPON_",piece,defined);
		if(raceWeapons.size()>0)
		{
			Item I=raceWeapons.get(CMLib.dice().roll(1, raceWeapons.size(), -1));
			R.setStat("WEAPONCLASS", I.ID());
			R.setStat("WEAPONXML", I.text());
		}
		ignoreStats.addAll(Arrays.asList(new String[]{"WEAPONCLASS","WEAPONXML"}));
		
		final List<Item> raceResources=getRaceItems(E,"RESOURCES","RACE_RESOURCE_",piece,defined);
		R.setStat("NUMRSC", ""+raceResources.size());
		for(int i=0;i<raceResources.size();i++)
		{
			final Item I=raceResources.get(i);
			R.setStat("GETRSCID"+i, I.ID());
			R.setStat("GETRSCPARM"+i, ""+I.text());
		}
		ignoreStats.addAll(Arrays.asList(new String[]{"NUMRSC","GETRSCID","GETRSCPARM"}));
		final List<Item> raceOutfit=getRaceItems(E,"OUTFIT","RACE_RESOURCE_",piece,defined);
		R.setStat("NUMOFT", ""+raceOutfit.size());
		for(int i=0;i<raceOutfit.size();i++)
		{
			final Item I=raceOutfit.get(i);
			R.setStat("GETOFTID"+i, I.ID());
			R.setStat("GETOFTPARM"+i, ""+I.text());
		}
		ignoreStats.addAll(Arrays.asList(new String[]{"NUMOFT","GETOFTID","GETOFTPARM"}));
		final List<AbilityMapping> rables = findRaceAbles(E,"ABILITY","RACE_ABLE_",piece,defined);
		R.setStat("NUMRABLE", ""+rables.size());
		for(int i=0;i<rables.size();i++)
		{
			final AbilityMapping ableMap=rables.get(i);
			R.setStat("GETRABLE"+i, ableMap.abilityID());
			R.setStat("GETRABLEPROF"+i, ""+ableMap.defaultProficiency());
			R.setStat("GETRABLEQUAL"+i, ""+(!ableMap.autoGain()));
			R.setStat("GETRABLELVL"+i, ""+ableMap.qualLevel());
			R.setStat("GETRABLEPARM"+i, ableMap.defaultParm());
		}
		ignoreStats.addAll(Arrays.asList(new String[]{"NUMRABLE","GETRABLE","GETRABLEPROF","GETRABLEQUAL","GETRABLELVL","GETRABLEPARM"}));
		final List<AbilityMapping> cables = findRaceAbles(E,"CULTUREABILITY","RACE_CULT_ABLE_",piece,defined);
		R.setStat("NUMCABLE", ""+cables.size());
		for(int i=0;i<cables.size();i++)
		{
			final AbilityMapping ableMap=cables.get(i);
			R.setStat("GETCABLE"+i, ableMap.abilityID());
			R.setStat("GETCABLEPROF"+i, ""+ableMap.defaultProficiency());
		}
		ignoreStats.addAll(Arrays.asList(new String[]{"NUMCABLE","GETCABLE","GETCABLEPROF"}));
		final List<AbilityMapping> reffs = findRaceAbles(E,"AFFECT","RACE_EFFECT_",piece,defined);
		R.setStat("NUMREFF", ""+reffs.size());
		for(int i=0;i<reffs.size();i++)
		{
			final AbilityMapping ableMap=reffs.get(i);
			R.setStat("GETREFF"+i, ableMap.abilityID());
			R.setStat("GETREFFPARM"+i, ""+ableMap.defaultParm());
			R.setStat("GETREFFLVL"+i, ""+ableMap.qualLevel());
		}
		ignoreStats.addAll(Arrays.asList(new String[]{"NUMREFF","GETREFF","GETREFFPARM","GETREFFLVL"}));
		
		final List<AbilityMapping> iables = findRaceAbles(E,"IMMUNITY","RACE_IMMUNITY_",piece,defined);
		R.setStat("NUMIABLE", ""+reffs.size());
		for(int i=0;i<iables.size();i++)
		{
			final AbilityMapping ableMap=reffs.get(i);
			R.setStat("GETIABLE"+i, ableMap.abilityID());
		}
		ignoreStats.addAll(Arrays.asList(new String[]{"NUMIABLE","GETIABLE"}));
		
		fillOutStatCodes(R,ignoreStats,"RACE_",piece,defined);
		CMLib.database().DBCreateRace(R.ID(),R.racialParms());
		return R;
	}

	protected void addDefinition(String definition, String value, Map<String,Object> defined)
	{
		definition=definition.toUpperCase().trim();
		defined.put(definition, value);
		if(definition.toUpperCase().endsWith("S"))
			definition+="ES";
		else
			definition+="S";
		final String def = (String)defined.get(definition);
		if(def==null)
			defined.put(definition, value);
		else defined.put(definition, def+","+value);
	}

	protected String findOptionalStringNow(Modifiable E, List<String> ignoreStats, String defPrefix, String tagName, XMLTag piece, Map<String,Object> defined, boolean debug)
	{
		try
		{
			return findOptionalString(E,ignoreStats,defPrefix,tagName, piece, defined, false);
		}
		catch(final PostProcessException x)
		{
			if(debug)
				Log.errOut(x);
			return null;
		}
	}

	protected String findOptionalString(Modifiable E, List<String> ignoreStats, String defPrefix, String tagName, XMLTag piece, Map<String,Object> defined, boolean debug) throws PostProcessException
	{
		try
		{
			return findString(E,ignoreStats,defPrefix,tagName, piece, defined);
		}
		catch(final CMException x)
		{
			if(debug)
				Log.errOut(x);
			return null;
		}
	}

	@Override
	public void defineReward(XMLTag piece, Map<String,Object> defined) throws CMException
	{
		defineReward(null, null, null,piece,piece.value(),defined);
	}

	protected void defineReward(Modifiable E, List<String> ignoreStats, String defPrefix, XMLTag piece, String value, Map<String,Object> defined) throws CMException
	{
		try
		{
			defineReward(E,ignoreStats,defPrefix,piece.getParmValue("DEFINE"),piece,value,defined,true);
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Post-processing not permitted: "+pe.getMessage(),pe);
		}
	}

	@Override
	public void preDefineReward(XMLTag piece, Map<String,Object> defined) throws CMException
	{
		preDefineReward(null,null,null,piece,defined);
	}
	
	protected void preDefineReward(Modifiable E, List<String> ignoreStats, String defPrefix, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			defineReward(E,ignoreStats,defPrefix,piece.getParmValue("PREDEFINE"),piece,piece.value(),defined,false);
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Post-processing not permitted: "+pe.getMessage(),pe);
		}
	}

	protected void defineReward(Modifiable E, List<String> ignoreStats, String defPrefix, String defineString, XMLTag piece, String value, Map<String,Object> defined, boolean recurseAllowed) throws CMException, PostProcessException
	{
		if((defineString!=null)&&(defineString.trim().length()>0))
		{
			final List<String> V=CMParms.parseCommas(defineString,true);
			for (String defVar : V)
			{
				String definition=value;
				final int x=defVar.indexOf('=');
				if(x==0)
					continue;
				if(x>0)
				{
					definition=defVar.substring(x+1).trim();
					defVar=defVar.substring(0,x).toUpperCase().trim();
					switch(defVar.charAt(defVar.length()-1))
					{
						case '+': case '-': case '*': case '/':
						{
							final char plusMinus=defVar.charAt(defVar.length()-1);
							defVar=defVar.substring(0,defVar.length()-1).trim();
							String oldVal=(String)defined.get(defVar.toUpperCase().trim());
							if((oldVal==null)||(oldVal.trim().length()==0))
								oldVal="0";
							definition=oldVal+plusMinus+definition;
							break;
						}
					}
				}
				if(definition==null)
					definition="!";
				definition=strFilter(E,ignoreStats,defPrefix,definition,piece, defined);
				if(CMath.isMathExpression(definition))
					definition=Integer.toString(CMath.s_parseIntExpression(definition));
				if(defVar.trim().length()>0)
					defined.put(defVar.toUpperCase().trim(), definition);
				if(CMSecurity.isDebugging(CMSecurity.DbgFlag.MUDPERCOLATOR))
					Log.debugOut("MudPercolator","DEFINE:"+defVar.toUpperCase().trim()+"="+definition);
			}
		}
		final XMLTag parentPiece = piece.parent();
		if((parentPiece!=null)&&(parentPiece.tag().equalsIgnoreCase(piece.tag()))&&(recurseAllowed))
			defineReward(E,ignoreStats,defPrefix,parentPiece.getParmValue("DEFINE"),parentPiece,value,defined,recurseAllowed);
	}

	protected String findStringNow(Modifiable E, List<String> ignoreStats, String defPrefix, String tagName, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			return findString(E,ignoreStats,defPrefix,tagName,piece,defined);
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Post processing not permitted",pe);
		}
	}
	
	protected String findString(Modifiable E, List<String> ignoreStats, String defPrefix, String tagName, XMLTag piece, Map<String,Object> defined) throws CMException,PostProcessException
	{
		tagName=tagName.toUpperCase().trim();
		
		if(tagName.startsWith("SYSTEM_RANDOM_NAME:"))
		{
			String[] split=tagName.substring(19).split("-");
			if((split.length==2)&&(CMath.isInteger(split[0]))&&(CMath.isInteger(split[1])))
				return CMLib.login().generateRandomName(CMath.s_int(split[0]), CMath.s_int(split[1]));
			throw new CMException("Bad random name range in '"+tagName+"' on piece '"+piece.tag()+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		}
		else
		if(tagName.equals("ROOM_AREAGATE"))
		{
			if(E instanceof Environmental)
			{
				final Room R=CMLib.map().roomLocation((Environmental)E);
				if(R!=null)
				{
					boolean foundOne=false;
					for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
					{
						final Room R2=R.getRoomInDir(d);
						foundOne=(R2!=null) || foundOne;
						if((R2!=null) && (R2.roomID().length()>0) && (R.getArea()!=R2.getArea()))
							return CMLib.directions().getDirectionName(d);
					}
					if(!foundOne)
						throw new PostProcessException("No exits at all on on object "+R.roomID()+" in variable '"+tagName+"'");
				}
			}
			return "";
		}
		
		final String asParm = piece.getParmValue(tagName);
		if(asParm != null)
			return strFilter(E,ignoreStats,defPrefix,asParm,piece, defined);

		final Object asDefined = defined.get(tagName);
		if(asDefined instanceof String)
			return (String)asDefined;
		XMLTag processDefined=null;
		if(asDefined instanceof XMLTag)
		{
			piece=(XMLTag)asDefined;
			processDefined=piece;
			tagName=piece.tag();
		}
		List<XMLLibrary.XMLTag> choices = getAllChoices(E,ignoreStats,defPrefix,tagName, piece, defined,true);
		if((choices==null)||(choices.size()==0))
			throw new CMException("Unable to find tag '"+tagName+"' on piece '"+piece.tag()+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		StringBuffer finalValue = new StringBuffer("");

		for(int c=0;c<choices.size();c++)
		{
			final XMLTag valPiece = choices.get(c);
			if(valPiece.parms().containsKey("VALIDATE") && !testCondition(E,null,null,CMLib.xml().restoreAngleBrackets(valPiece.getParmValue("VALIDATE")),valPiece, defined))
				continue;

			final String value=strFilter(E,ignoreStats,defPrefix,valPiece.value(),valPiece, defined);
			if(processDefined!=valPiece)
				defineReward(E,ignoreStats,defPrefix,valPiece.getParmValue("DEFINE"),valPiece,value,defined,true);

			String action = valPiece.getParmValue("ACTION");
			if(action==null) 
				finalValue.append(" ").append(value);
			else
			{
				action=action.toUpperCase().trim();
				if((action.length()==0)||(action.equals("APPEND")))
					finalValue.append(" ").append(value);
				else
				if(action.equals("REPLACE"))
					finalValue = new StringBuffer(value);
				else
				if(action.equals("PREPEND"))
					finalValue.insert(0,' ').insert(0,value);
				else
					throw new CMException("Unknown action '"+action+" on subPiece "+valPiece.tag()+" on piece '"+piece.tag()+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			}
		}
		final String finalFinalValue=finalValue.toString().trim();
		if(processDefined!=null)
			defineReward(E,ignoreStats,defPrefix,piece.getParmValue("DEFINE"),processDefined,finalFinalValue,defined,true);
		return finalFinalValue;
	}

	protected XMLTag processLikeParm(String tagName, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		final String like = piece.getParmValue("LIKE");
		if(like!=null)
		{
			final List<String> V=CMParms.parseCommas(like,true);
			final XMLTag origPiece = piece;
			piece=piece.copyOf();
			for(int v=0;v<V.size();v++)
			{
				String s = V.get(v);
				if(s.startsWith("$"))
					s=s.substring(1).trim();
				final XMLTag likePiece =(XMLTag)defined.get(s.toUpperCase().trim());
				if((likePiece == null)||(!likePiece.tag().equalsIgnoreCase(tagName)))
					throw new CMException("Invalid like: '"+s+"' on piece '"+piece.tag()+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
				piece.contents().addAll(likePiece.contents());
				piece.parms().putAll(likePiece.parms());
				piece.parms().putAll(origPiece.parms());
			}
		}
		return piece;
	}
	
	@Override
	public List<XMLTag> getAllChoices(String tagName, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			return getAllChoices(null,null,null,tagName,piece,defined,true);
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Unable to post process this object: "+pe.getMessage(),pe);
		}
	}
	
	protected List<XMLTag> getAllChoices(Modifiable E, List<String> ignoreStats, String defPrefix, String tagName, XMLTag piece, Map<String,Object> defined, boolean skipTest) throws CMException, PostProcessException
	{
		if((!skipTest)&&(!testCondition(E,ignoreStats,defPrefix,CMLib.xml().restoreAngleBrackets(piece.getParmValue("CONDITION")),piece,defined)))
			return new Vector<XMLTag>(1);

		defineReward(E,ignoreStats,defPrefix,piece.getParmValue("PREDEFINE"),piece,piece.value(),defined,false); // does pre-define

		final List<XMLTag> choices = new Vector<XMLTag>();
		final String inserter = piece.getParmValue("INSERT");
		if(inserter != null)
		{
			final List<String> V=CMParms.parseCommas(inserter,true);
			for(int v=0;v<V.size();v++)
			{
				String s = V.get(v);
				if(s.startsWith("$"))
					s=s.substring(1).trim();
				final XMLTag insertPiece =(XMLTag)defined.get(s.toUpperCase().trim());
				if(insertPiece == null)
					throw new CMException("Undefined insert: '"+s+"' on piece '"+piece.tag()+"', Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
				if(insertPiece.tag().equalsIgnoreCase(tagName))
					choices.addAll(getAllChoices(E,ignoreStats,defPrefix,tagName,insertPiece,defined,false));
			}
		}
		else
		if(piece.tag().equalsIgnoreCase(tagName))
		{
			if((piece.parms().containsKey("LAYOUT")) && (piece.tag().equalsIgnoreCase("ROOM")) && (!defined.containsKey("ROOM_LAYOUT")))
				return new XVector<XMLTag>(processLikeParm(tagName,piece,defined));

			boolean container=false;
			for(int p=0;p<piece.contents().size();p++)
			{
				final XMLTag ppiece=piece.contents().get(p);
				if(ppiece.tag().equalsIgnoreCase(tagName))
				{
					container=true;
					break;
				}
			}
			if(!container)
				return new XVector<XMLTag>(processLikeParm(tagName,piece,defined));
		}

		for(int p=0;p<piece.contents().size();p++)
		{
			final XMLTag subPiece = piece.contents().get(p);
			if(subPiece.tag().equalsIgnoreCase(tagName))
				choices.addAll(getAllChoices(E,ignoreStats,defPrefix,tagName,subPiece,defined,false));
		}
		return selectChoices(E,tagName,ignoreStats,defPrefix,choices,piece,defined);
	}

	protected boolean testCondition(Modifiable E, List<String> ignoreStats, String defPrefix, String condition, XMLTag piece, Map<String,Object> defined) throws PostProcessException
	{
		final Map<String,Object> fixed=new HashMap<String,Object>();
		try
		{
			if(condition == null) 
				return true;
			fixed.putAll(defined);
			final List<Varidentifier> ids=parseVariables(condition);
			for(final Varidentifier id : ids)
			{
				try
				{
					if((defPrefix!=null)&&(defined!=null)&&(id.var.toUpperCase().startsWith(defPrefix))&&(!defined.containsKey(defPrefix)))
					{
						XMLTag newPiece=piece;
						while((newPiece.parent()!=null)&&(newPiece.tag().equals(piece.tag())))
							newPiece=newPiece.parent();
						fillOutStatCode(E, ignoreStats, defPrefix, id.var.substring(defPrefix.length()), newPiece, defined, false);
					}
					String value=findString(E,ignoreStats,defPrefix,id.var, piece, defined);
					if(CMath.isMathExpression(value))
					{
						final double val=CMath.parseMathExpression(value);
						if(Math.round(val)==val)
							value=""+Math.round(val);
						else
							value=""+val;
					}
					fixed.put(id.var.toUpperCase(),value);
				}
				catch(final CMException e)
				{
				}
			}
			final boolean test= CMStrings.parseStringExpression(condition.toUpperCase(),fixed, true);
			if(CMSecurity.isDebugging(CMSecurity.DbgFlag.MUDPERCOLATOR))
				Log.debugOut("MudPercolator","TEST "+piece.tag()+": "+condition+"="+test);
			return test;
		}
		catch(final Exception e)
		{
			if(e instanceof PostProcessException)
				throw (PostProcessException)e;
			Log.errOut("Generate",e.getMessage()+": "+condition);
			try {
				CMStrings.parseStringExpression(condition,fixed, true);
			}
			catch(final Exception e1)
			{
			}
			return false;
		}
	}
	
	protected String getRequirementsDescription(String values)
	{
		if(values==null)
			return "";
		if(values.equalsIgnoreCase("integer")||values.equalsIgnoreCase("int"))
			return " as an integer or integer expression";
		else
		if(values.equalsIgnoreCase("double")||values.equalsIgnoreCase("#")||values.equalsIgnoreCase("number"))
			return " as a number or numeric expression";
		else
		if(values.equalsIgnoreCase("string")||values.equalsIgnoreCase("$"))
			return " as an open string";
		else
		if(values.trim().length()>0)
			return " as one of the following values: "+values;
		return "";
	}

	protected boolean checkRequirementsValue(String validValue, String value)
	{
		if(validValue==null)
			return value != null;
		if(validValue.equalsIgnoreCase("integer")||validValue.equalsIgnoreCase("int"))
			return CMath.isMathExpression(value);
		else
		if(validValue.equalsIgnoreCase("double")||validValue.equalsIgnoreCase("#")||validValue.equalsIgnoreCase("number"))
			return CMath.isMathExpression(value);
		else
		if(validValue.equalsIgnoreCase("string")||validValue.equalsIgnoreCase("$"))
			return value.length()>0;
		else
		if(validValue.trim().length()>0)
			return CMParms.containsIgnoreCase(CMParms.toStringArray(CMParms.parseSemicolons(validValue,true)),value);
		return value.length()==0;
	}

	protected String cleanRequirementsValue(String values, String value)
	{
		if(values==null)
			return value;
		if(values.equalsIgnoreCase("integer")||values.equalsIgnoreCase("int"))
			return Integer.toString(CMath.s_parseIntExpression(value));
		else
		if(values.equalsIgnoreCase("double")||values.equalsIgnoreCase("#")||values.equalsIgnoreCase("number"))
			return Double.toString(CMath.s_parseMathExpression(value));
		else
		if(values.equalsIgnoreCase("string")||values.equalsIgnoreCase("$"))
			return value;
		else
		if(values.trim().length()>0)
		{
			final String[] arrayStr=CMParms.toStringArray(CMParms.parseSemicolons(values,true));
			int x=CMParms.indexOfIgnoreCase(arrayStr,value);
			if(x<0)
				x=0;
			return arrayStr[x];
		}
		return value;
	}

	@Override
	public Map<String,String> getUnfilledRequirements(Map<String,Object> defined, XMLTag piece)
	{
		String requirements = piece.getParmValue("REQUIRES");
		final Map<String,String> set=new Hashtable<String,String>();
		if(requirements==null)
			return set;
		requirements = requirements.trim();
		final List<String> reqs = CMParms.parseCommas(requirements,true);
		for(int r=0;r<reqs.size();r++)
		{
			String reqVariable=reqs.get(r);
			if(reqVariable.startsWith("$"))
				reqVariable=reqVariable.substring(1).trim();
			String validValues=null;
			final int x=reqVariable.indexOf('=');
			if(x>=0)
			{
				validValues=reqVariable.substring(x+1).trim();
				reqVariable=reqVariable.substring(0,x).trim();
			}
			if((!defined.containsKey(reqVariable.toUpperCase()))
			||(!checkRequirementsValue(validValues, defined.get(reqVariable.toUpperCase()).toString())))
			{
				if(validValues==null)
					set.put(reqVariable.toUpperCase(), "any");
				else
				if(validValues.equalsIgnoreCase("integer")||validValues.equalsIgnoreCase("int"))
					set.put(reqVariable.toUpperCase(), "int");
				else
				if(validValues.equalsIgnoreCase("double")||validValues.equalsIgnoreCase("#")||validValues.equalsIgnoreCase("number"))
					set.put(reqVariable.toUpperCase(), "double");
				else
				if(validValues.equalsIgnoreCase("string")||validValues.equalsIgnoreCase("$"))
					set.put(reqVariable.toUpperCase(), "string");
				else
				if(validValues.trim().length()>0)
					set.put(reqVariable.toUpperCase(), CMParms.toListString(CMParms.parseSemicolons(validValues,true)));
			}
		}
		return set;
	}

	protected void checkRequirements(Map<String,Object> defined, String requirements) throws CMException
	{
		if(requirements==null)
			return;
		requirements = requirements.trim();
		final List<String> reqs = CMParms.parseCommas(requirements,true);
		for(int r=0;r<reqs.size();r++)
		{
			String reqVariable=reqs.get(r);
			if(reqVariable.startsWith("$"))
				reqVariable=reqVariable.substring(1).trim();
			String validValues=null;
			final int x=reqVariable.indexOf('=');
			if(x>=0)
			{
				validValues=reqVariable.substring(x+1).trim();
				reqVariable=reqVariable.substring(0,x).trim();
			}
			if(!defined.containsKey(reqVariable.toUpperCase()))
				throw new CMException("Required variable not defined: '"+reqVariable+"'.  Please define this variable"+getRequirementsDescription(validValues)+".");
			if(!checkRequirementsValue(validValues, defined.get(reqVariable.toUpperCase()).toString()))
				throw new CMException("The required variable '"+reqVariable+"' is not properly defined.  Please define this variable"+getRequirementsDescription(validValues)+".");
		}
	}

	@Override
	public void checkRequirements(XMLTag piece, Map<String,Object> defined) throws CMException
	{
		checkRequirements(defined,piece.getParmValue("REQUIRES"));
	}

	protected List<XMLTag> selectChoices(Modifiable E, String tagName, List<String> ignoreStats, String defPrefix, List<XMLTag> choices, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		String selection = piece.getParmValue("SELECT");
		if(selection == null)
			return choices;
		selection=selection.toUpperCase().trim();
		List<XMLLibrary.XMLTag> selectedChoicesV=null;
		if(selection.equals("NONE"))
			selectedChoicesV= new Vector<XMLTag>();
		else
		if(selection.equals("ALL"))
			selectedChoicesV=choices;
		else
		if((choices.size()==0)
		&&(!selection.startsWith("ANY-0"))
		&&(!selection.startsWith("FIRST-0"))
		&&(!selection.startsWith("LAST-0"))
		&&(!selection.startsWith("PICK-0"))
		&&(!selection.startsWith("LIMIT-")))
		{
			throw new CMException("Can't make selection among NONE: on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		}
		else
		if(selection.equals("FIRST"))
			selectedChoicesV= new XVector<XMLTag>(choices.get(0));
		else
		if(selection.startsWith("FIRST-"))
		{
			final int num=CMath.parseIntExpression(strFilterNow(E,ignoreStats,defPrefix,selection.substring(selection.indexOf('-')+1),piece, defined));
			if((num<0)||(num>choices.size()))
				throw new CMException("Can't pick first "+num+" of "+choices.size()+" on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			selectedChoicesV=new Vector<XMLTag>();
			for(int v=0;v<num;v++)
				selectedChoicesV.add(choices.get(v));
		}
		else
		if(selection.startsWith("LIMIT-"))
		{
			final int num=CMath.parseIntExpression(strFilterNow(E,ignoreStats,defPrefix,selection.substring(selection.indexOf('-')+1),piece, defined));
			if(num<0)
				throw new CMException("Can't pick limit "+num+" of "+choices.size()+" on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			selectedChoicesV=new Vector<XMLTag>();
			if(choices.size()<=num)
				selectedChoicesV.addAll(choices);
			else
			while(selectedChoicesV.size()<num)
				selectedChoicesV.add(choices.remove(CMLib.dice().roll(1, choices.size(), -1)));
		}
		else
		if(selection.equals("LAST"))
			selectedChoicesV=new XVector<XMLTag>(choices.get(choices.size()-1));
		else
		if(selection.startsWith("LAST-"))
		{
			final int num=CMath.parseIntExpression(strFilterNow(E,ignoreStats,defPrefix,selection.substring(selection.indexOf('-')+1),piece, defined));
			if((num<0)||(num>choices.size()))
				throw new CMException("Can't pick last "+num+" of "+choices.size()+" on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			selectedChoicesV=new Vector<XMLTag>();
			for(int v=choices.size()-num;v<choices.size();v++)
				selectedChoicesV.add(choices.get(v));
		}
		else
		if(selection.startsWith("PICK-"))
		{
			final int num=CMath.parseIntExpression(strFilterNow(E,ignoreStats,defPrefix,selection.substring(selection.indexOf('-')+1),piece, defined));
			if((num<0)||(num>choices.size()))
				throw new CMException("Can't pick "+num+" of "+choices.size()+" on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			selectedChoicesV=new Vector<XMLTag>();
			final List<XMLLibrary.XMLTag> cV=new XVector<XMLLibrary.XMLTag>(choices);
			for(int v=0;v<num;v++)
			{
				final int[] weights=new int[cV.size()];
				int total=0;
				for(int c=0;c<cV.size();c++)
				{
					final XMLTag lilP=cV.get(c);
					int weight=CMath.s_parseIntExpression(lilP.getParmValue("PICKWEIGHT"));
					if(weight<1)
						weight=1;
					weights[c]=weight;
					total+=weight;
				}

				int choice=CMLib.dice().roll(1,total,0);
				int c=-1;
				while(choice>0)
				{
					c++;
					choice-=weights[c];
				}
				selectedChoicesV.add(cV.get(c));
				cV.remove(c);
			}
		}
		else
		if(selection.equals("ANY"))
			selectedChoicesV=new XVector<XMLTag>(choices.get(CMLib.dice().roll(1,choices.size(),-1)));
		else
		if(selection.startsWith("ANY-"))
		{
			final int num=CMath.parseIntExpression(strFilterNow(E,ignoreStats,defPrefix,selection.substring(selection.indexOf('-')+1),piece, defined));
			if((num<0)||(num>choices.size()))
				throw new CMException("Can't pick last "+num+" of "+choices.size()+" on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			selectedChoicesV=new Vector<XMLTag>();
			final List<XMLLibrary.XMLTag> cV=new XVector<XMLLibrary.XMLTag>(choices);
			for(int v=0;v<num;v++)
			{
				final int x=CMLib.dice().roll(1,cV.size(),-1);
				selectedChoicesV.add(cV.get(x));
				cV.remove(x);
			}
		}
		else
		if(selection.startsWith("REPEAT-"))
		{
			final int num=CMath.parseIntExpression(strFilterNow(E,ignoreStats,defPrefix,selection.substring(selection.indexOf('-')+1),piece, defined));
			if(num<0)
				throw new CMException("Can't pick last "+num+" of "+choices.size()+" on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			selectedChoicesV=new Vector<XMLTag>();
			final List<XMLLibrary.XMLTag> cV=new XVector<XMLLibrary.XMLTag>(choices);
			for(int v=0;v<num;v++)
			{
				final int x=CMLib.dice().roll(1,cV.size(),-1);
				selectedChoicesV.add(cV.get(x));
			}
		}
		else
		if((selection.trim().length()>0)&&CMath.isMathExpression(selection))
		{
			final int num=CMath.parseIntExpression(strFilterNow(E,ignoreStats,defPrefix,selection.substring(selection.indexOf('-')+1),piece, defined));
			if((num<0)||(num>choices.size()))
				throw new CMException("Can't pick any "+num+" of "+choices.size()+" on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
			selectedChoicesV=new Vector<XMLTag>();
			final List<XMLLibrary.XMLTag> cV=new XVector<XMLLibrary.XMLTag>(choices);
			for(int v=0;v<num;v++)
			{
				final int x=CMLib.dice().roll(1,cV.size(),-1);
				selectedChoicesV.add(cV.get(x));
				cV.remove(x);
			}
		}
		else
			throw new CMException("Illegal select type '"+selection+"' on piece '"+piece.tag()+"', Tag: "+tagName+", Data: "+CMParms.toKeyValueSlashListString(piece.parms())+":"+CMStrings.limit(piece.value(),100));
		return selectedChoicesV;
	}

	protected String strFilterNow(Modifiable E, List<String> ignoreStats, String defPrefix, String str, XMLTag piece, Map<String,Object> defined) throws CMException
	{
		try
		{
			return strFilter(E,ignoreStats,defPrefix,str,piece,defined);
		}
		catch(PostProcessException pe)
		{
			throw new CMException("Post processing not permitted",pe);
		}
		
	}

	protected String strFilter(Modifiable E, List<String> ignoreStats, String defPrefix, String str, XMLTag piece, Map<String,Object> defined) throws CMException,PostProcessException
	{
		List<Varidentifier> vars=parseVariables(str);
		final boolean killArticles=str.toLowerCase().startsWith("(a(n))");
		while(vars.size()>0)
		{
			final Varidentifier V=vars.remove(vars.size()-1);
			Object val;
			if(V.isMathExpression)
			{
				final String expression=strFilter(E,ignoreStats,defPrefix,V.var,piece, defined);
				if(CMath.isMathExpression(expression))
				{
					if(expression.indexOf('.')>0)
						val=""+CMath.parseMathExpression(expression);
					else
						val=""+CMath.parseLongExpression(expression);
				}
				else
					throw new CMException("Invalid math expression '$"+expression+"' in str '"+str+"'");
			}
			else
			if(V.var.toUpperCase().startsWith("STAT:") && (E!=null))
			{
				final String[] parts=V.var.toUpperCase().split(":");
				if(E instanceof Environmental)
				{
					Environmental E2=(Environmental)E;
					for(int p=1;p<parts.length-1;p++)
					{
						final Room R=CMLib.map().roomLocation(E2);
						Environmental E3;
						if(parts[p].equals("ROOM"))
							E3=CMLib.map().roomLocation(E2);
						else
						if(parts[p].equals("AREA"))
							E3=CMLib.map().areaLocation(E2);
						else
						if(CMLib.directions().getDirectionCode(parts[p])>=0)
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							final int dir=CMLib.directions().getDirectionCode(parts[p]);
							E3=R.getRoomInDir(dir);
						}
						else
						if(parts[p].equals("ANYROOM"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							final List<Room> dirs=new ArrayList<Room>();
							for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
							{
								if((R.getRoomInDir(d)!=null)&&(R.getRoomInDir(d).roomID().length()>0))
									dirs.add(R.getRoomInDir(d));
							}
							if(dirs.size()==0)
								throw new PostProcessException("No anyrooms on object "+E2.ID()+" in variable '"+V.var+"'");
							E3=dirs.get(CMLib.dice().roll(1, dirs.size(), -1));
						}
						else
						if(parts[p].equals("MOB"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							if((R.numInhabitants()==0)||((E2 instanceof MOB)&&(R.numInhabitants()==1)))
								throw new PostProcessException("No mobs in room for "+E2.ID()+" in variable '"+V.var+"'");
							E3=R.fetchInhabitant(CMLib.dice().roll(1, R.numInhabitants(), -1));
						}
						else
						if(parts[p].equals("ITEM"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							if((R.numItems()==0)||((E2 instanceof Item)&&(R.numItems()==1)))
								throw new PostProcessException("No items in room for "+E2.ID()+" in variable '"+V.var+"'");
							E3=R.getItem(CMLib.dice().roll(1, R.numItems(), -1));
						}
						else
						if(parts[p].equals("AREAGATE"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							final List<Room> dirs=new ArrayList<Room>();
							for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
							{
								final Room R2=R.getRoomInDir(d);
								if((R2!=null) && (R2.roomID().length()>0) && (R.getArea()!=R2.getArea()))
									dirs.add(R.getRoomInDir(d));
							}
							if(dirs.size()==0)
							{
								if(defined.get("ROOMTAG_GATEEXITROOM") instanceof Room)
									dirs.add((Room)defined.get("ROOMTAG_GATEEXITROOM"));
								if(dirs.size()==0)
									throw new PostProcessException("No areagates on object "+E2.ID()+" in variable '"+V.var+"'");
							}
							E3=dirs.get(CMLib.dice().roll(1, dirs.size(), -1));
						}
						else
							throw new PostProcessException("Unknown stat code '"+parts[p]+"' on object "+E2.ID()+" in variable '"+V.var+"'");
						if(E3==null)
							throw new PostProcessException("Unknown '"+parts[p]+"' on object "+E2.ID()+" in variable '"+V.var+"'");
						else
							E2=E3;
					}
					E=E2;
				}
				if (E.isStat(parts[parts.length-1]))
					val=E.getStat(parts[parts.length-1]);
				else
					throw new CMException("Unknown stat code '"+parts[parts.length-1]+"' on object "+E+" in variable '"+V.var+"'");
			}
			else
				val = defined.get(V.var.toUpperCase().trim());
			if(val instanceof XMLTag)
			{
				val = findString(E,ignoreStats,defPrefix,"STRING",(XMLTag)val,defined);
			}
			if((val == null)&&(defPrefix!=null)&&(defPrefix.length()>0)&&(E!=null))
			{
				String preValue=V.var;
				if(preValue.toUpperCase().startsWith(defPrefix.toUpperCase()))
				{
					preValue=preValue.toUpperCase().substring(defPrefix.length());
					if((E.isStat(preValue))
					&&((ignoreStats==null)||(!ignoreStats.contains(preValue.toUpperCase()))))
					{
						val=fillOutStatCode(E,ignoreStats,defPrefix,preValue,piece,defined, false);
						XMLTag statPiece=piece;
						while((val == null)
						&&(statPiece.parent()!=null)
						&&(!(defPrefix.startsWith(statPiece.tag())&&(!defPrefix.startsWith(statPiece.parent().tag())))))
						{
							statPiece=statPiece.parent();
							val=fillOutStatCode(E,ignoreStats,defPrefix,preValue,statPiece,defined, false);
						}
						if((ignoreStats!=null)&&(val!=null))
							ignoreStats.add(preValue.toUpperCase());
					}
				}
			}
			if(val == null)
				throw new CMException("Unknown variable '$"+V.var+"' in str '"+str+"'",new CMException("$"+V.var));
			if(V.toUpperCase)
				val=val.toString().toUpperCase();
			if(V.toLowerCase)
				val=val.toString().toLowerCase();
			if(V.toPlural)
				val=CMLib.english().makePlural(val.toString());
			if(V.toCapitalized)
				val=CMStrings.capitalizeAndLower(val.toString());
			if(killArticles)
				val=CMLib.english().cleanArticles(val.toString());
			str=str.substring(0,V.outerStart)+val.toString()+str.substring(V.outerEnd);
			if(vars.size()==0)
				vars=parseVariables(str);
		}
		int x=str.toLowerCase().indexOf("(a(n))");
		while((x>=0)&&(x<str.length()-8))
		{
			if((Character.isWhitespace(str.charAt(x+6)))
			&&(Character.isLetter(str.charAt(x+7))))
			{
				if(CMStrings.isVowel(str.charAt(x+7)))
					str=str.substring(0,x)+"an"+str.substring(x+6);
				else
					str=str.substring(0,x)+"a"+str.substring(x+6);
			}