@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(!(I instanceof Container))
						return "";
					final Container C=(Container)I;
					if(C.hasALock())
						return "LOCK";
					if(C.hasADoor())
						return "LID";
					final StringBuilder str=new StringBuilder("");
					for(int i=1;i<Container.CONTAIN_DESCS.length;i++)
						if(CMath.isSet(C.containTypes(), i-1))
						{
							if(str.length()>0)
								str.append("|");
							str.append(Container.CONTAIN_DESCS[i]);
						}
					return str.toString();
				}
			},
			new AbilityParmEditorImpl("CODED_SPELL_LIST","Spell Affects",ParmType.SPECIAL)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public boolean confirmValue(String oldVal)
				{
					if(oldVal.length()==0)
						return true;
					if(oldVal.charAt(0)=='*')
						oldVal = oldVal.substring(1);
					final int x=oldVal.indexOf('(');
					int y=oldVal.indexOf(';');
					if((x<y)&&(x>0))
						y=x;
					if(y<0)
						return CMClass.getAbility(oldVal)!=null;
					return CMClass.getAbility(oldVal.substring(0,y))!=null;
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return CMLib.ableParms().encodeCodedSpells(I);
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				public String rebuild(List<Ability> spells) throws CMException
				{
					final StringBuffer newVal = new StringBuffer("");
					if(spells.size()==1)
						newVal.append("*" + spells.get(0).ID() + ";" + spells.get(0).text());
					else
					{
						if(spells.size()>1)
						{
							for(int s=0;s<spells.size();s++)
							{
								final String txt = spells.get(s).text().trim();
								if((txt.indexOf(';')>=0)||(CMClass.getAbility(txt)!=null))
									throw new CMException("You may not have more than one spell when one of the spells parameters is a spell id or a ; character.");
								newVal.append(spells.get(s).ID());
								if(txt.length()>0)
									newVal.append(";" + spells.get(s).text());
								if(s<(spells.size()-1))
									newVal.append(";");
							}
						}
					}
					return newVal.toString();
				}

				@Override
				public String[] fakeUserInput(String oldVal)
				{
					final Vector<String> V = new Vector<String>();
					final Vector<String> V2 = new Vector<String>();
					final List<Ability> spells=CMLib.ableParms().getCodedSpells(oldVal);
					for(int s=0;s<spells.size();s++)
					{
						V.addElement(spells.get(s).ID());
						V2.addElement(spells.get(s).ID());
						V2.addElement(spells.get(s).text());
					}
					V.addAll(V2);
					V.addElement("");
					return CMParms.toStringArray(V);
				}

				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					List<Ability> spells=null;
					if(httpReq.isUrlParameter(fieldName+"_AFFECT1"))
					{
						spells = new Vector<Ability>();
						int num=1;
						String behav=httpReq.getUrlParameter(fieldName+"_AFFECT"+num);
						String theparm=httpReq.getUrlParameter(fieldName+"_ADATA"+num);
						while((behav!=null)&&(theparm!=null))
						{
							if(behav.length()>0)
							{
								final Ability A=CMClass.getAbility(behav);
								if(theparm.trim().length()>0)
									A.setMiscText(theparm);
								spells.add(A);
							}
							num++;
							behav=httpReq.getUrlParameter(fieldName+"_AFFECT"+num);
							theparm=httpReq.getUrlParameter(fieldName+"_ADATA"+num);
						}
					}
					else
						spells = CMLib.ableParms().getCodedSpells(oldVal);
					try
					{
						return rebuild(spells);
					}
					catch(final Exception e)
					{
						return oldVal;
					}
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final List<Ability> spells=CMLib.ableParms().getCodedSpells(webValue(httpReq,parms,oldVal,fieldName));
					final StringBuffer str = new StringBuffer("");
					str.append("<TABLE WIDTH=100% BORDER=\"1\" CELLSPACING=0 CELLPADDING=0>");
					for(int i=0;i<spells.size();i++)
					{
						final Ability A=spells.get(i);
						str.append("<TR><TD WIDTH=50%>");
						str.append("\n\r<SELECT ONCHANGE=\"EditAffect(this);\" NAME="+fieldName+"_AFFECT"+(i+1)+">");
						str.append("<OPTION VALUE=\"\">Delete!");
						str.append("<OPTION VALUE=\""+A.ID()+"\" SELECTED>"+A.ID());
						str.append("</SELECT>");
						str.append("</TD><TD WIDTH=50%>");
						final String theparm=CMStrings.replaceAll(A.text(),"\"","&quot;");
						str.append("\n\r<INPUT TYPE=TEXT SIZE=30 NAME="+fieldName+"_ADATA"+(i+1)+" VALUE=\""+theparm+"\">");
						str.append("</TD></TR>");
					}
					str.append("<TR><TD WIDTH=50%>");
					str.append("\n\r<SELECT ONCHANGE=\"AddAffect(this);\" NAME="+fieldName+"_AFFECT"+(spells.size()+1)+">");
					str.append("<OPTION SELECTED VALUE=\"\">Select an Effect");
					for(final Enumeration<Ability> a=CMClass.abilities();a.hasMoreElements();)
					{
						final Ability A=a.nextElement();
						if((A.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_ARCHON)
							continue;
						final String cnam=A.ID();
						str.append("<OPTION VALUE=\""+cnam+"\">"+cnam);
					}
					str.append("</SELECT>");
					str.append("</TD><TD WIDTH=50%>");
					str.append("\n\r<INPUT TYPE=TEXT SIZE=30 NAME="+fieldName+"_ADATA"+(spells.size()+1)+" VALUE=\"\">");
					str.append("</TD></TR>");
					str.append("</TABLE>");
					return str.toString();
				}

				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					final List<Ability> spells=CMLib.ableParms().getCodedSpells(oldVal);
					final StringBuffer rawCheck = new StringBuffer("");
					for(int s=0;s<spells.size();s++)
						rawCheck.append(spells.get(s).ID()).append(";").append(spells.get(s).text()).append(";");
					boolean okToProceed = true;
					++showNumber[0];
					String newVal = null;
					while(okToProceed)
					{
						okToProceed = false;
						CMLib.genEd().spells(mob,spells,showNumber[0],showFlag,true);
						final StringBuffer sameCheck = new StringBuffer("");
						for(int s=0;s<spells.size();s++)
							sameCheck.append(spells.get(s).ID()).append(';').append(spells.get(s).text()).append(';');
						if(sameCheck.toString().equals(rawCheck.toString()))
							return oldVal;
						try
						{
							newVal = rebuild(spells);
						}
						catch(final CMException e)
						{
							mob.tell(e.getMessage());
							okToProceed = true;
							break;
						}
					}
					return (newVal==null)?oldVal:newVal.toString();
				}
			},
			new AbilityParmEditorImpl("BUILDING_FLAGS","Flags",ParmType.SPECIAL)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public boolean confirmValue(String oldVal)
				{
					if(oldVal.trim().length()==0)
						return true;
					Pair<String[],String[]> codesFlags = CMAbleParms.getBuildingCodesNFlags();
					String[] names = CMParms.parseSpaces(oldVal, true).toArray(new String[0]);
					for(String name : names)
					{
						if(!CMParms.containsIgnoreCase(codesFlags.second, name))
							return false;
					}
					return true;
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String[] fakeUserInput(String oldVal)
				{
					return CMParms.parseSpaces(oldVal, true).toArray(new String[0]);
				}
				
				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final String webValue = httpReq.getUrlParameter(fieldName);
					if(webValue == null)
						return oldVal;
					StringBuilder s=new StringBuilder("");
					String id="";
					int index=0;
					Pair<String[],String[]> codesFlags = CMAbleParms.getBuildingCodesNFlags();
					for(;httpReq.isUrlParameter(fieldName+id);id=""+(++index))
					{
						final String newVal = httpReq.getUrlParameter(fieldName+id);
						if(CMParms.containsIgnoreCase(codesFlags.second, newVal.toUpperCase().trim()))
							s.append(" ").append(newVal.toUpperCase().trim());
					}
					return s.toString().trim();
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final StringBuffer str = new StringBuffer("");
					final String webValue = webValue(httpReq,parms,oldVal,fieldName);
					String onChange = null;
					onChange = " MULTIPLE ";
					if(!parms.containsKey("NOSELECT"))
						onChange+= "ONCHANGE=\"MultiSelect(this);\"";
					Pair<String[],String[]> codesFlags = CMAbleParms.getBuildingCodesNFlags();
					String[] fakeValues = this.fakeUserInput(webValue);
					str.append("\n\r<SELECT NAME="+fieldName+onChange+">");
					for(int i=0;i<codesFlags.second.length;i++)
					{
						final String option = (codesFlags.second[i]);
						str.append("<OPTION VALUE=\""+option+"\" ");
						if(CMParms.containsIgnoreCase(fakeValues, option))
							str.append("SELECTED");
						str.append(">"+option);
					}
					return str.toString()+"</SELECT>";
				}

				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					Pair<String[],String[]> codesFlags = CMAbleParms.getBuildingCodesNFlags();
					String help=CMParms.combineWith(Arrays.asList(codesFlags.second), ',');
					String newVal = CMLib.genEd().prompt(mob, oldVal, ++showNumber[0], showFlag, L("Flags"), true, help);
					String[] newVals;
					if(newVal.indexOf(',')>0)
						newVals = CMParms.parseCommas(newVal.toUpperCase().trim(), true).toArray(new String[0]);
					else
					if(newVal.indexOf(';')>0)
						newVals = CMParms.parseSemicolons(newVal.toUpperCase().trim(), true).toArray(new String[0]);
					else
						newVals = CMParms.parse(newVal.toUpperCase().trim()).toArray(new String[0]);
					StringBuilder finalVal = new StringBuilder("");
					for(int i=0;i<newVals.length;i++)
					{
						if(CMParms.containsIgnoreCase(codesFlags.second, newVals[i]))
							finalVal.append(" ").append(newVals[i]);
					}
					return finalVal.toString().toUpperCase().trim();
				}
			},
			new AbilityParmEditorImpl("EXIT_NAMES","Exit Words",ParmType.SPECIAL)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					if(o instanceof String)
					{
						String chk=((String)o).toUpperCase();
						if(chk.equalsIgnoreCase("DOOR"))
							return 1;
					}
					return -1;
				}
				
				@Override
				public boolean confirmValue(String oldVal)
				{
					if(oldVal.trim().length()==0)
						return true;
					String[] names = CMParms.parseAny(oldVal.trim(), '|', true).toArray(new String[0]);
					if(names.length > 5)
						return false;
					return true;
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}
	
				@Override
				public String defaultValue()
				{
					return "door|open|close|A closed door.|An open doorway.";
				}
	
				@Override
				public String[] fakeUserInput(String oldVal)
				{
					
					final Vector<String> V = new Vector<String>();
					V.addAll(CMParms.parseAny(oldVal.trim(), '|', true));
					while(V.size()<5)
						V.add("");
					return CMParms.toStringArray(V);
				}
				
				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					if(httpReq.isUrlParameter(fieldName+"_W1"))
					{
						StringBuilder str=new StringBuilder("");
						str.append(httpReq.getUrlParameter(fieldName+"_W1")).append("|");
						str.append(httpReq.getUrlParameter(fieldName+"_W2")).append("|");
						str.append(httpReq.getUrlParameter(fieldName+"_W3")).append("|");
						str.append(httpReq.getUrlParameter(fieldName+"_W4")).append("|");
						str.append(httpReq.getUrlParameter(fieldName+"_W5"));
						String s=str.toString();
						while(s.endsWith("|"))
							s=s.substring(0,s.length()-1);
						return s;
					}
					else
					{
						return oldVal;
					}
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final StringBuffer str = new StringBuffer("");
					str.append("<TABLE WIDTH=100% BORDER=\"1\" CELLSPACING=0 CELLPADDING=0>");
					String[] vals = this.fakeUserInput(oldVal);
					String[] keys = new String[]{"Noun","Open","Close","Closed Display","Open Display"};
					for(int i=0;i<keys.length;i++)
					{
						str.append("<TR><TD WIDTH=30%><FONT COLOR=WHITE>"+L(keys[i])+"</FONT></TD>");
						str.append("<TD><INPUT TYPE=TEXT SIZE=30 NAME="+fieldName+"_W"+(i+1)+" VALUE=\""+vals[i]+"\">");
						str.append("</TD></TR>");
					}
					str.append("</TABLE>");
					return str.toString();
				}

				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					String[] vals = this.fakeUserInput(oldVal);
					StringBuilder newVal = new StringBuilder("");
					newVal.append(CMLib.genEd().prompt(mob, vals[0], ++showNumber[0], showFlag, L("Exit Noun"), true)).append("|");
					newVal.append(CMLib.genEd().prompt(mob, vals[1], ++showNumber[0], showFlag, L("Open Verb"), true)).append("|");
					newVal.append(CMLib.genEd().prompt(mob, vals[2], ++showNumber[0], showFlag, L("Close Verb"), true)).append("|");
					newVal.append(CMLib.genEd().prompt(mob, vals[3], ++showNumber[0], showFlag, L("Opened Text"), true)).append("|");
					newVal.append(CMLib.genEd().prompt(mob, vals[4], ++showNumber[0], showFlag, L("Closed Text"), true));
					String s=newVal.toString();
					while(s.endsWith("|"))
						s=s.substring(0,s.length()-1);
					return s;
				}
			},
			new AbilityParmEditorImpl("PCODED_SPELL_LIST","Spell Affects",ParmType.SPECIAL)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					if(o instanceof String)
					{
						String chk=((String)o).toUpperCase();
						if(chk.equalsIgnoreCase("WALL")
						||chk.equalsIgnoreCase("DEMOLISH")
						||chk.equalsIgnoreCase("TITLE")
						||chk.equalsIgnoreCase("DESC"))
							return -1;
						final Pair<String[],String[]> codeFlags = CMAbleParms.getBuildingCodesNFlags();
						if(CMParms.contains(codeFlags.first, chk))
							return 1;
					}
					return -1;
				}
				
				@Override
				public boolean confirmValue(String oldVal)
				{
					if(oldVal.trim().length()==0)
						return true;
					String[] spells = CMParms.parseAny(oldVal.trim(), ')', true).toArray(new String[0]);
					for(String spell : spells)
					{
						final int x=spell.indexOf('(');
						if(x>0)
							spell=spell.substring(0,x);
						if(spell.trim().length()==0)
							continue;
						if((CMClass.getAbility(spell)==null)&&(CMClass.getBehavior(spell)==null))
							return false;
					}
					return true;
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				public String rebuild(List<CMObject> spells) throws CMException
				{
					final StringBuffer newVal = new StringBuffer("");
					for(int s=0;s<spells.size();s++)
					{
						final String txt;
						if(spells.get(s) instanceof Ability)
							txt = ((Ability)spells.get(s)).text().trim();
						else
						if(spells.get(s) instanceof Behavior)
							txt = ((Behavior)spells.get(s)).getParms().trim();
						else
							continue;
						newVal.append(spells.get(s).ID()).append("(").append(txt).append(")");
					}
					return newVal.toString();
				}

				@Override
				public String[] fakeUserInput(String oldVal)
				{
					final Vector<String> V = new Vector<String>();
					String[] spells = CMParms.parseAny(oldVal.trim(), ')', true).toArray(new String[0]);
					for(String spell : spells)
					{
						final int x=spell.indexOf('(');
						String parms="";
						if(x>0)
						{
							parms=spell.substring(x+1).trim();
							spell=spell.substring(0,x);
						}
						if(spell.trim().length()==0)
							continue;
						if((CMClass.getAbility(spell)!=null)
						||(CMClass.getBehavior(spell)!=null))
						{
							V.add(spell);
							V.add(parms);
						}
					}
					return CMParms.toStringArray(V);
				}
				
				public List<CMObject> getCodedSpells(String oldVal)
				{
					String[] spellStrs = this.fakeUserInput(oldVal);
					List<CMObject> spells=new ArrayList<CMObject>(spellStrs.length/2);
					for(int s=0;s<spellStrs.length;s+=2)
					{
						Ability A=CMClass.getAbility(spellStrs[s]);
						if(A!=null)
						{
							if(spellStrs[s+1].length()>0)
								A.setMiscText(spellStrs[s+1]);
							spells.add(A);
						}
						else
						{
							Behavior B=CMClass.getBehavior(spellStrs[s]);
							if(spellStrs[s+1].length()>0)
								B.setParms(spellStrs[s+1]);
							spells.add(B);
						}
					}
					return spells;
				}

				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					List<CMObject> spells=null;
					if(httpReq.isUrlParameter(fieldName+"_AFFECT1"))
					{
						spells = new Vector<CMObject>();
						int num=1;
						String behav=httpReq.getUrlParameter(fieldName+"_AFFECT"+num);
						String theparm=httpReq.getUrlParameter(fieldName+"_ADATA"+num);
						while((behav!=null)&&(theparm!=null))
						{
							if(behav.length()>0)
							{
								final Ability A=CMClass.getAbility(behav);
								if(A!=null)
								{
									if(theparm.trim().length()>0)
										A.setMiscText(theparm);
									spells.add(A);
								}
								else
								{
									final Behavior B=CMClass.getBehavior(behav);
									if(theparm.trim().length()>0)
										B.setParms(theparm);
									spells.add(B);
								}
							}
							num++;
							behav=httpReq.getUrlParameter(fieldName+"_AFFECT"+num);
							theparm=httpReq.getUrlParameter(fieldName+"_ADATA"+num);
						}
					}
					else
					{
						spells = getCodedSpells(oldVal);
					}
					try
					{
						return rebuild(spells);
					}
					catch(final Exception e)
					{
						return oldVal;
					}
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final List<CMObject> spells=getCodedSpells(webValue(httpReq,parms,oldVal,fieldName));
					final StringBuffer str = new StringBuffer("");
					str.append("<TABLE WIDTH=100% BORDER=\"1\" CELLSPACING=0 CELLPADDING=0>");
					for(int i=0;i<spells.size();i++)
					{
						final CMObject A=spells.get(i);
						str.append("<TR><TD WIDTH=50%>");
						str.append("\n\r<SELECT ONCHANGE=\"EditAffect(this);\" NAME="+fieldName+"_AFFECT"+(i+1)+">");
						str.append("<OPTION VALUE=\"\">Delete!");
						str.append("<OPTION VALUE=\""+A.ID()+"\" SELECTED>"+A.ID());
						str.append("</SELECT>");
						str.append("</TD><TD WIDTH=50%>");
						final String theparm;
						if(A instanceof Ability)
							theparm=CMStrings.replaceAll(((Ability)A).text(),"\"","&quot;");
						else
						if(A instanceof Behavior)
							theparm=CMStrings.replaceAll(((Behavior)A).getParms(),"\"","&quot;");
						else
							continue;
						str.append("\n\r<INPUT TYPE=TEXT SIZE=30 NAME="+fieldName+"_ADATA"+(i+1)+" VALUE=\""+theparm+"\">");
						str.append("</TD></TR>");
					}
					str.append("<TR><TD WIDTH=50%>");
					str.append("\n\r<SELECT ONCHANGE=\"AddAffect(this);\" NAME="+fieldName+"_AFFECT"+(spells.size()+1)+">");
					str.append("<OPTION SELECTED VALUE=\"\">Select an Effect");
					for(final Enumeration<Ability> a=CMClass.abilities();a.hasMoreElements();)
					{
						final Ability A=a.nextElement();
						if((A.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_ARCHON)
							continue;
						final String cnam=A.ID();
						str.append("<OPTION VALUE=\""+cnam+"\">"+cnam);
					}
					for(final Enumeration<Behavior> a=CMClass.behaviors();a.hasMoreElements();)
					{
						final Behavior A=a.nextElement();
						final String cnam=A.ID();
						str.append("<OPTION VALUE=\""+cnam+"\">"+cnam);
					}
					str.append("</SELECT>");
					str.append("</TD><TD WIDTH=50%>");
					str.append("\n\r<INPUT TYPE=TEXT SIZE=30 NAME="+fieldName+"_ADATA"+(spells.size()+1)+" VALUE=\"\">");
					str.append("</TD></TR>");
					str.append("</TABLE>");
					return str.toString();
				}

				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					final List<CMObject> spells=getCodedSpells(oldVal);
					final StringBuffer rawCheck = new StringBuffer("");
					for(int s=0;s<spells.size();s++)
					{
						rawCheck.append(spells.get(s).ID()).append(";");
						if(spells.get(s) instanceof Ability)
							rawCheck.append(((Ability)spells.get(s)).text()).append(";");
						else
						if(spells.get(s) instanceof Behavior)
							rawCheck.append(((Behavior)spells.get(s)).getParms()).append(";");
						else
							rawCheck.append(";");
					}
					boolean okToProceed = true;
					++showNumber[0];
					String newVal = null;
					while(okToProceed)
					{
						okToProceed = false;
						CMLib.genEd().spellsOrBehaviors(mob,spells,showNumber[0],showFlag,true);
						final StringBuffer sameCheck = new StringBuffer("");
						for(int s=0;s<spells.size();s++)
						{
							if(spells.get(s) instanceof Ability)
								rawCheck.append(((Ability)spells.get(s)).text()).append(";");
							else
							if(spells.get(s) instanceof Behavior)
								rawCheck.append(((Behavior)spells.get(s)).getParms()).append(";");
							else
								rawCheck.append(";");
						}
						if(sameCheck.toString().equals(rawCheck.toString()))
							return oldVal;
						try
						{
							newVal = rebuild(spells);
						}
						catch(final CMException e)
						{
							mob.tell(e.getMessage());
							okToProceed = true;
							break;
						}
					}
					return (newVal==null)?oldVal:newVal.toString();
				}
			},
			new AbilityParmEditorImpl("BASE_DAMAGE","Dmg.",ParmType.NUMBER)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Weapon) ? 2 : -1;
				}
	
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "1";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Weapon)
						return ""+((Weapon)I).basePhyStats().damage();
					return "0";
				}
			},
			new AbilityParmEditorImpl("LID_LOCK","Lid.",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Container) ? 1 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "", "LID", "LOCK" });
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(!(I instanceof Container))
						return "";
					final Container C=(Container)I;
					if(C.hasALock())
						return "LOCK";
					if(C.hasADoor())
						return "LID";
					return "";
				}
			},
			new AbilityParmEditorImpl("BUILDING_CODE","Code",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return 1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(getBuildingCodesNFlags().first);
				}
	
				@Override
				public String defaultValue()
				{
					return "TITLE";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "TITLE";
				}
			},
			new AbilityParmEditorImpl("STATUE","Statue",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return ((!(o instanceof Armor)) && (!(o instanceof Container)) && (!(o instanceof Drink))) ? 1 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "", "STATUE" });
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Weapon)
						return "";
					if(I instanceof Armor)
						return "";
					if(I instanceof Ammunition)
						return "";
					final int x=I.Name().lastIndexOf(" of ");
					if(x<0)
						return "";
					final String ender=I.Name();
					if(!I.displayText().endsWith(ender+" is here"))
						return "";
					if(!I.description().startsWith(ender+". "))
						return "";
					return "STATUE";
				}
			},
			new AbilityParmEditorImpl("RIDE_BASIS","Ride",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Rideable) ? 3 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "", "CHAIR", "TABLE", "LADDER", "ENTER", "BED" });
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(!(I instanceof Rideable))
						return "";
					switch(((Rideable)I).rideBasis())
					{
					case Rideable.RIDEABLE_SIT: return "SIT";
					case Rideable.RIDEABLE_TABLE:  return "TABLE";
					case Rideable.RIDEABLE_LADDER: return "LADDER";
					case Rideable.RIDEABLE_ENTERIN: return "ENTER";
					case Rideable.RIDEABLE_SLEEP: return "BED";
					default: return "";
					}
				}
			},
			new AbilityParmEditorImpl("LIQUID_CAPACITY","Liq.",ParmType.NUMBER)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Drink) ? 4 : -1;
				}
	
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "25";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(!(I instanceof Drink))
						return "";
					return ""+((Drink)I).liquidHeld();
				}
			},
			new AbilityParmEditorImpl("WEAPON_CLASS","WClas",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Weapon) ? 2 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(Weapon.CLASS_DESCS);
				}
	
				@Override
				public String defaultValue()
				{
					return "BLUNT";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Weapon)
						return Weapon.CLASS_DESCS[((Weapon)I).weaponClassification()];
					return "0";
				}
			},
			new AbilityParmEditorImpl("SMOKE_FLAG","Smoke",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Light) ? 5 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "", "SMOKE" });
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(!(I instanceof Light))
						return "";
					if((I instanceof Container)
					&&(((Light)I).getDuration() > 199)
					&&(((Container)I).capacity()==0))
						return "SMOKE";
					return "";
				}
			},
			new AbilityParmEditorImpl("WEAPON_HANDS_REQUIRED","Hand",ParmType.NUMBER)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Weapon) ? 2 : -1;
				}
	
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "1";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Weapon)
						return ((Weapon)I).rawLogicalAnd()?"2":"1";
					return "";
				}
			},
			new AbilityParmEditorImpl("LIGHT_DURATION","Dur.",ParmType.NUMBER)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Light) ? 5 : -1;
				}
	
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "10";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Light)
						return ""+((Light)I).getDuration();
					return "";
				}
			},
			new AbilityParmEditorImpl("CLAN_ITEM_CODENUMBER","Typ.",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof ClanItem) ? 10 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createNumberedChoices(ClanItem.ClanItemType.ALL);
				}
	
				@Override
				public String defaultValue()
				{
					return "1";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof ClanItem)
						return ""+((ClanItem)I).getClanItemType().ordinal();
					return "";
				}
			},
			new AbilityParmEditorImpl("CLAN_EXPERIENCE_COST_AMOUNT","Exp",ParmType.NUMBER)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "100";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(!(I instanceof ClanItem))
						return "100";
					if(I.getClass().getName().toString().indexOf("Flag")>0)
						return "2500";
					if(I.getClass().getName().toString().indexOf("ClanItem")>0)
						return "1000";
					if(I.getClass().getName().toString().indexOf("GenClanSpecialItem")>0)
						return "500";
					return "100";
				}
			},
			new AbilityParmEditorImpl("CLAN_AREA_FLAG","Area",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return o.getClass().getName().toString().indexOf("LawBook") > 0 ? 5 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "", "AREA" });
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return (I.getClass().getName().toString().indexOf("LawBook")>0)?"AREA":"";
				}
			},
			new AbilityParmEditorImpl("READABLE_TEXT","Read",ParmType.STRINGORNULL)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(CMLib.flags().isReadable(I))
						return I.readableText();
					return "";
				}
			},
			new AbilityParmEditorImpl("REQUIRED_COMMON_SKILL_ID","Common Skill",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof ClanItem) ? 5 : -1;
				}
	
				@Override
				public void createChoices()
				{
					final Vector<Object> V  = new Vector<Object>();
					Ability A = null;
					for(final Enumeration<Ability> e=CMClass.abilities();e.hasMoreElements();)
					{
						A=e.nextElement();
						if((A.classificationCode() & Ability.ALL_ACODES) == Ability.ACODE_COMMON_SKILL)
							V.addElement(A);
					}
					V.addElement("");
					createChoices(V);
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I.getClass().getName().toString().indexOf("LawBook")>0)
						return "";
					if(I instanceof ClanItem)
						return ((ClanItem)I).readableText();
					return "";
				}
			},
			new AbilityParmEditorImpl("FOOD_DRINK","ETyp",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "", "FOOD", "DRINK", "SOAP", "GenPerfume" });
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					final String str=(I.name()+" "+I.displayText()+" "+I.description()).toUpperCase();
					if(str.startsWith("SOAP ") || str.endsWith(" SOAP") || (str.indexOf("SOAP")>0))
						return "SOAP";
					if(I instanceof Perfume)
						return "PERFUME";
					if(I instanceof Food)
						return "FOOD";
					if(I instanceof Drink)
						return "DRINK";
					return "";
				}
			},
			new AbilityParmEditorImpl("SMELL_LIST","Smells",ParmType.STRING)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Perfume) ? 5 : -1;
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Perfume)
						return ((Perfume)I).getSmellList();
					return "";
				}
			},
			new AbilityParmEditorImpl("RESOURCE_OR_KEYWORD","Resc/Itm",ParmType.SPECIAL)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public boolean confirmValue(String oldVal)
				{
					return true;
				}
	
				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					if(httpReq.isUrlParameter(fieldName+"_WHICH"))
					{
						final String which=httpReq.getUrlParameter(fieldName+"_WHICH");
						if(which.trim().length()>0)
							return httpReq.getUrlParameter(fieldName+"_RESOURCE");
						return httpReq.getUrlParameter(fieldName+"_WORD");
					}
					return oldVal;
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					String value=webValue(httpReq,parms,oldVal,fieldName);
					if(value.endsWith("$"))
						value = value.substring(0,oldVal.length()-1);
					value = value.trim();
					final StringBuffer str = new StringBuffer("");
					str.append("\n\r<INPUT TYPE=RADIO NAME="+fieldName+"_WHICH ");
					final boolean rsc=(value.trim().length()==0)||(RawMaterial.CODES.FIND_IgnoreCase(value)>=0);
					if(rsc)
						str.append("CHECKED ");
					str.append("VALUE=\"RESOURCE\">");
					str.append("\n\r<SELECT NAME="+fieldName+"_RESOURCE>");
					final String[] Ss=RawMaterial.CODES.NAMES().clone();
					Arrays.sort(Ss);
					for(final String S : Ss)
					{
						final String VALUE = S.equals("NOTHING")?"":S;
						str.append("<OPTION VALUE=\""+VALUE+"\"");
						if(rsc&&(value.equalsIgnoreCase(VALUE)))
							str.append(" SELECTED");
						str.append(">"+CMStrings.capitalizeAndLower(S));
					}
					str.append("</SELECT>");
					str.append("<BR>");
					str.append("\n\r<INPUT TYPE=RADIO NAME="+fieldName+"_WHICH ");
					if(!rsc)
						str.append("CHECKED ");
					str.append("VALUE=\"\">");
					str.append("\n\r<INPUT TYPE=TEXT NAME="+fieldName+"_WORD VALUE=\""+(rsc?"":value)+"\">");
					return str.toString();
				}
	
				@Override
				public String[] fakeUserInput(String oldVal)
				{
					return new String[] { oldVal };
				}
	
				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					++showNumber[0];
					boolean proceed = true;
					String str = oldVal;
					while(proceed&&(!mob.session().isStopped()))
					{
						proceed = false;
						str=CMLib.genEd().prompt(mob,oldVal,showNumber[0],showFlag,prompt(),true,CMParms.toListString(RawMaterial.CODES.NAMES())).trim();
						if(str.equals(oldVal))
							return oldVal;
						final int r=RawMaterial.CODES.FIND_IgnoreCase(str);
						if(r==0)
							str="";
						else if(r>0) str=RawMaterial.CODES.NAME(r);
						if(str.equals(oldVal))
							return oldVal;
						if(str.length()==0)
							return "";
						final boolean isResource = CMParms.contains(RawMaterial.CODES.NAMES(),str);
						if((!isResource)&&(mob.session()!=null)&&(!mob.session().isStopped()))
							if(!mob.session().confirm(L("You`ve entered a non-resource item keyword '@x1', ok (Y/n)?",str),"Y"))
								proceed = true;
					}
					return str;
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
			},
			new AbilityParmEditorImpl("RESOURCE_NAME_OR_HERB_NAME","Resrc/Herb",ParmType.SPECIAL)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public boolean confirmValue(String oldVal)
				{
					if(oldVal.trim().length()==0)
						return true;
					if(!oldVal.endsWith("$"))
					{
						return CMParms.contains(RawMaterial.CODES.NAMES(),oldVal);
					}
					return true;
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}

				@Override
				public String[] fakeUserInput(String oldVal)
				{
					if(oldVal.endsWith("$"))
						return new String[]{oldVal.substring(0,oldVal.length()-1)};
					return new String[]{oldVal};
				}

				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final AbilityParmEditor A = CMLib.ableParms().getEditors().get("RESOURCE_OR_KEYWORD");
					if(oldVal.endsWith("$"))
						oldVal = oldVal.substring(0,oldVal.length()-1);
					final String value = A.webValue(httpReq,parms,oldVal,fieldName);
					final int r=RawMaterial.CODES.FIND_IgnoreCase(value);
					if(r>=0)
						return RawMaterial.CODES.NAME(r);
					return (value.trim().length()==0)?"":(value+"$");
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final AbilityParmEditor A = CMLib.ableParms().getEditors().get("RESOURCE_OR_KEYWORD");
					return A.webField(httpReq,parms,oldVal,fieldName);
				}

				@Override
				public String webTableField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal)
				{
					if(oldVal.endsWith("$"))
						return oldVal.substring(0,oldVal.length()-1);
					return oldVal;
				}

				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					++showNumber[0];
					boolean proceed = true;
					String str = oldVal;
					final String orig = oldVal;
					while(proceed&&(!mob.session().isStopped()))
					{
						proceed = false;
						if(oldVal.trim().endsWith("$"))
							oldVal=oldVal.trim().substring(0,oldVal.trim().length()-1);
						str=CMLib.genEd().prompt(mob,oldVal,showNumber[0],showFlag,prompt(),true,CMParms.toListString(RawMaterial.CODES.NAMES())).trim();
						if(str.equals(orig))
							return orig;
						final int r=RawMaterial.CODES.FIND_IgnoreCase(str);
						if(r==0)
							str="";
						else if(r>0) str=RawMaterial.CODES.NAME(r);
						if(str.equals(orig))
							return orig;
						if(str.length()==0)
							return "";
						final boolean isResource = CMParms.contains(RawMaterial.CODES.NAMES(),str);
						if((!isResource)&&(mob.session()!=null)&&(!mob.session().isStopped()))
						{
							if(!mob.session().confirm(L("You`ve entered a non-resource item keyword '@x1', ok (Y/n)?",str),"Y"))
								proceed = true;
							else
								str=str+"$";
						}
					}
					return str;
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
			},
			new AbilityParmEditorImpl("AMMO_TYPE","Ammo",ParmType.STRING)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return ((o instanceof Weapon) || (o instanceof Ammunition)) ? 2 : -1;
				}
	
				@Override
				public String defaultValue()
				{
					return "arrows";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Ammunition)
						return ""+((Ammunition)I).ammunitionType();
					return "";
				}
			},
			new AbilityParmEditorImpl("AMMO_CAPACITY","Ammo#",ParmType.NUMBER)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return ((o instanceof Weapon) || (o instanceof Ammunition)) ? 2 : -1;
				}
	
				@Override
				public String defaultValue()
				{
					return "1";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Ammunition)
						return ""+((Ammunition)I).ammunitionRemaining();
					if((I instanceof AmmunitionWeapon)&&(((AmmunitionWeapon)I).requiresAmmunition()))
						return ""+((AmmunitionWeapon)I).ammunitionCapacity();
					return "";
				}
			},
			new AbilityParmEditorImpl("MAXIMUM_RANGE","Max",ParmType.NUMBER)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return ((o instanceof Weapon) && (!(o instanceof Ammunition))) ? 2 : -1;
				}
	
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "5";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if((I instanceof Ammunition)||(I instanceof Weapon))
						return ""+I.maxRange();
					return "";
				}
			},
			new AbilityParmEditorImpl("RESOURCE_OR_MATERIAL","Rsc/Mat",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					final XVector<String> V=new XVector<String>(RawMaterial.CODES.NAMES());
					Collections.sort(V);
					final XVector<String> V2=new XVector<String>(RawMaterial.Material.names());
					Collections.sort(V2);
					V.addAll(V2);
					createChoices(V);
				}
				
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(CMStrings.containsWordIgnoreCase(I.Name(),"rice"))
						return "RICE";
					if(I.material() == RawMaterial.RESOURCE_PAPER)
						return "WOOD";
					return RawMaterial.CODES.NAME(I.material());
				}
				
				@Override
				public String defaultValue()
				{
					return "IRON";
				}
			},
			new AbilityParmEditorImpl("OPTIONAL_RESOURCE_OR_MATERIAL","Rsc/Mat",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					final XVector<String> V=new XVector<String>(RawMaterial.CODES.NAMES());
					Collections.sort(V);
					final XVector<String> V2=new XVector<String>(RawMaterial.Material.names());
					Collections.sort(V2);
					V.addAll(V2);
					V.addElement("");
					createChoices(V);
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
			},
			new AbilityParmEditorImpl("OPTIONAL_BUILDING_RESOURCE_OR_MATERIAL","Rsc/Mat",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					final XVector<String> V=new XVector<String>(RawMaterial.CODES.NAMES());
					Collections.sort(V);
					final XVector<String> V2=new XVector<String>(RawMaterial.Material.names());
					Collections.sort(V2);
					V.addAll(V2);
					V.addElement("VALUE");
					V.addElement("MONEY");
					V.addElement("");
					createChoices(V);
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
			},
			new AbilityParmEditorImpl("HERB_NAME","Herb Final Name",ParmType.STRING)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "Herb Name";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I.material()==RawMaterial.RESOURCE_HERBS)
						return CMStrings.lastWordIn(I.Name());
					return "";
				}
			},
			new AbilityParmEditorImpl("RIDE_CAPACITY","Ridrs",ParmType.NUMBER)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Rideable) ? 3 : -1;
				}
	
				@Override
				public String defaultValue()
				{
					return "2";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof Rideable)
						return ""+((Rideable)I).riderCapacity();
					return "0";
				}
			},
			new AbilityParmEditorImpl("METAL_OR_WOOD","Metal",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "METAL", "WOOD" });
				}
	
				@Override
				public String defaultValue()
				{
					return "METAL";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					switch(I.material()&RawMaterial.MATERIAL_MASK)
					{
					case RawMaterial.MATERIAL_METAL:
					case RawMaterial.MATERIAL_MITHRIL:
						return "METAL";
					case RawMaterial.MATERIAL_WOODEN:
						return "WOOD";
					}
					return ""; // absolutely no way to determine
				}
			},
			new AbilityParmEditorImpl("OPTIONAL_RACE_ID","Race",ParmType.SPECIAL)
			{
				@Override
				public void createChoices() 
				{
					createChoices(CMClass.races());
					choices().add("","");
					for(int x=0;x<choices().size();x++)
						choices().get(x).first = choices().get(x).first.toUpperCase();
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return ""; // absolutely no way to determine
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public boolean confirmValue(String oldVal)
				{
					if(oldVal.trim().length()==0)
						return true;
					final Vector<String> parsedVals = CMParms.parse(oldVal.toUpperCase());
					for(int v=0;v<parsedVals.size();v++)
					{
						if(CMClass.getRace(parsedVals.elementAt(v))==null)
							return false;
					}
					return true;
				}

				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					Vector<String> raceIDs=null;
					if(httpReq.isUrlParameter(fieldName+"_RACE"))
					{
						String id="";
						raceIDs=new Vector<String>();
						for(int i=0;httpReq.isUrlParameter(fieldName+"_RACE"+id);id=""+(++i))
							raceIDs.addElement(httpReq.getUrlParameter(fieldName+"_RACE"+id).toUpperCase().trim());
					}
					else
						raceIDs = CMParms.parse(oldVal.toUpperCase().trim());
					return CMParms.combine(raceIDs,0);
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final Vector<String> raceIDs=CMParms.parse(webValue(httpReq,parms,oldVal,fieldName).toUpperCase());
					final StringBuffer str = new StringBuffer("");
					str.append("\n\r<SELECT NAME="+fieldName+"_RACE MULTIPLE>");
					str.append("<OPTION VALUE=\"\" "+((raceIDs.size()==0)?"SELECTED":"")+">");
					for(final Enumeration<Race> e=CMClass.races();e.hasMoreElements();)
					{
						final Race R=e.nextElement();
						str.append("<OPTION VALUE=\""+R.ID()+"\" "+((raceIDs.contains(R.ID().toUpperCase()))?"SELECTED":"")+">"+R.name());
					}
					str.append("</SELECT>");
					return str.toString();
				}

				@Override
				public String[] fakeUserInput(String oldVal) 
				{
					final Vector<String> parsedVals = CMParms.parse(oldVal.toUpperCase());
					if(parsedVals.size()==0)
						return new String[]{""};
					final Vector<String> races = new Vector<String>();
					for(int p=0;p<parsedVals.size();p++)
					{
						final Race R=CMClass.getRace(parsedVals.elementAt(p));
						races.addElement(R.name());
					}
					for(int p=0;p<parsedVals.size();p++)
					{
						final Race R=CMClass.getRace(parsedVals.elementAt(p));
						races.addElement(R.name());
					}
					races.addElement("");
					return CMParms.toStringArray(races);
				}

				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					if((showFlag>0)&&(showFlag!=showNumber[0]))
						return oldVal;
					String behave="NO";
					String newVal = oldVal;
					while((mob.session()!=null)&&(!mob.session().isStopped())&&(behave.length()>0))
					{
						mob.tell(showNumber+". "+prompt()+": '"+newVal+"'.");
						if((showFlag!=showNumber[0])&&(showFlag>-999))
							return newVal;
						final Vector<String> parsedVals = CMParms.parse(newVal.toUpperCase());
						behave=mob.session().prompt(L("Enter a race to add/remove (?)\n\r:"),"");
						if(behave.length()>0)
						{
							if(behave.equalsIgnoreCase("?"))
								mob.tell(CMLib.lister().reallyList(mob,CMClass.races(),-1).toString());
							else
							{
								final Race R=CMClass.getRace(behave);
								if(R!=null)
								{
									if(parsedVals.contains(R.ID().toUpperCase()))
									{
										mob.tell(L("'@x1' removed.",behave));
										parsedVals.remove(R.ID().toUpperCase().trim());
										newVal = CMParms.combine(parsedVals,0);
									}
									else
									{
										mob.tell(L("@x1 added.",R.ID()));
										parsedVals.addElement(R.ID().toUpperCase());
										newVal = CMParms.combine(parsedVals,0);
									}
								}
								else
								{
									mob.tell(L("'@x1' is not a recognized race.  Try '?'.",behave));
								}
							}
						}
						else
						{
							if(oldVal.equalsIgnoreCase(newVal))
								mob.tell(L("(no change)"));
						}
					}
					return newVal;
				}
			},
			new AbilityParmEditorImpl("INSTRUMENT_TYPE","Instrmnt",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					createChoices(MusicalInstrument.InstrumentType.valueNames());
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof MusicalInstrument) ? 5 : -1;
				}
	
				@Override
				public String defaultValue()
				{
					return "DRUMS";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I instanceof MusicalInstrument)
						return ((MusicalInstrument)I).getInstrumentTypeName();
					return "0";
				}
			},
			new AbilityParmEditorImpl("STONE_FLAG","Stone",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "", "STONE" });
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(I.material()==RawMaterial.RESOURCE_STONE)
						return "STONE";
					return "";
				}
			},
			new AbilityParmEditorImpl("POSE_NAME","Pose Word",ParmType.ONEWORD)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "New Post";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}
			},
			new AbilityParmEditorImpl("POSE_DESCRIPTION","Pose Description",ParmType.STRING)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public String defaultValue()
				{
					return "<S-NAME> is standing here.";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					if(!(I instanceof DeadBody)) 
						return "";
					String pose=I.displayText();
					pose=CMStrings.replaceAll(pose,I.name(),"<S-NAME>");
					pose=CMStrings.replaceWord(pose,"himself"," <S-HIM-HERSELF>");
					pose=CMStrings.replaceWord(pose,"herself"," <S-HIM-HERSELF>");
					pose=CMStrings.replaceWord(pose,"his"," <S-HIS-HER>");
					pose=CMStrings.replaceWord(pose,"her"," <S-HIS-HER>");
					pose=CMStrings.replaceWord(pose,"him"," <S-HIM-HER>");
					pose=CMStrings.replaceWord(pose,"her"," <S-HIM-HER>");
					return pose;
				}
			},
			new AbilityParmEditorImpl("WOOD_METAL_CLOTH","",ParmType.CHOICES)
			{
				@Override
				public void createChoices()
				{
					createChoices(new String[] { "WOOD", "METAL", "CLOTH" });
				}
	
				@Override
				public String defaultValue()
				{
					return "WOOD";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					switch(I.material()&RawMaterial.MATERIAL_MASK)
					{
					case RawMaterial.MATERIAL_CLOTH: return "CLOTH";
					case RawMaterial.MATERIAL_METAL: return "METAL";
					case RawMaterial.MATERIAL_MITHRIL: return "METAL";
					case RawMaterial.MATERIAL_WOODEN: return "WOOD";
					default: return "";
					}
				}
			},
			new AbilityParmEditorImpl("WEAPON_TYPE","W.Type",ParmType.CHOICES)
			{
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Weapon) ? 2 : -1;
				}
	
				@Override
				public void createChoices()
				{
					createChoices(Weapon.TYPE_DESCS);
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return (I instanceof Weapon) ? Weapon.TYPE_DESCS[((Weapon) I).weaponDamageType()] : "";
				}
	
				@Override
				public String defaultValue()
				{
					return "BASHING";
				}
			},
			new AbilityParmEditorImpl("ATTACK_MODIFICATION","Att.",ParmType.NUMBER)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return (o instanceof Weapon) ? 2 : -1;
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "" + ((I instanceof Weapon) ? ((Weapon) I).basePhyStats().attackAdjustment() : 0);
				}
	
				@Override
				public String defaultValue()
				{
					return "0";
				}
			},
			new AbilityParmEditorImpl("N_A","N/A",ParmType.STRING)
			{
				@Override
				public void createChoices()
				{
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return -1;
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					return "";
				}
	
				@Override
				public boolean confirmValue(String oldVal)
				{
					return oldVal.trim().length() == 0 || oldVal.equals("0") || oldVal.equals("NA") || oldVal.equals("-");
				}
	
				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					return "";
				}
	
				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String, String> parms, String oldVal, String fieldName)
				{
					return "";
				}
			},
			new AbilityParmEditorImpl("RESOURCE_NAME_AMOUNT_MATERIAL_REQUIRED","Resrc/Amt",ParmType.SPECIAL)
			{
				@Override
				public void createChoices() 
				{
					createChoices(RawMaterial.CODES.NAMES());
					choices().add("","");
				}

				@Override
				public String convertFromItem(final ItemCraftor A, final Item I)
				{
					int amt=(int)Math.round(CMath.mul(I.basePhyStats().weight()-1,(A!=null)?A.getItemWeightMultiplier(false):1.0));
					if(amt<1)
						amt=1;
					return RawMaterial.CODES.NAME(I.material())+"/"+amt;
				}
	
				@Override
				public String defaultValue()
				{
					return "";
				}
	
				@Override
				public int appliesToClass(Object o)
				{
					return 0;
				}
	
				@Override
				public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					if(httpReq.isUrlParameter(fieldName+"_RESOURCE"))
					{
						final String rsc=httpReq.getUrlParameter(fieldName+"_RESOURCE");
						final String amt=httpReq.getUrlParameter(fieldName+"_AMOUNT");
						if((rsc.trim().length()==0)||(rsc.equalsIgnoreCase("NOTHING"))||(CMath.s_int(amt)<=0))
							return "";
						return rsc+"/"+amt;
					}
					return oldVal;
				}

				@Override
				public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
				{
					final String value=webValue(httpReq,parms,oldVal,fieldName);
					String rsc = "";
					int amt = 0;
					final int x=value.indexOf('/');
					if(x>0)
					{
						rsc = value.substring(0,x);
						amt = CMath.s_int(value.substring(x+1));
					}
					final StringBuffer str=new StringBuffer("");
					str.append("\n\r<SELECT NAME="+fieldName+"_RESOURCE MULTIPLE>");
					final String[] Ss=RawMaterial.CODES.NAMES().clone();
					Arrays.sort(Ss);
					for(final String S : Ss)
					{
						str.append("<OPTION VALUE=\""+S+"\" "
								+((S.equalsIgnoreCase(rsc))?"SELECTED":"")+">"
								+CMStrings.capitalizeAndLower(S));
					}
					str.append("</SELECT>");
					str.append("&nbsp;&nbsp;Amount: ");
					str.append("<INPUT TYPE=TEXT NAME="+fieldName+"_AMOUNT VALUE="+amt+">");
					return str.toString();
				}

				@Override
				public boolean confirmValue(String oldVal) 
				{
					if(oldVal.trim().length()==0)
						return true;
					oldVal=oldVal.trim();
					final int x=oldVal.indexOf('/');
					if(x<0)
						return false;
					if(!CMStrings.contains(choices().toArrayFirst(new String[0]),oldVal.substring(0,x)))
						return false;
					if(!CMath.isInteger(oldVal.substring(x+1)))
						return false;
					return true;
				}

				@Override
				public String[] fakeUserInput(String oldVal) 
				{
					final int x=oldVal.indexOf('/');
					if(x<=0) return new String[]{""};
					return new String[]{oldVal.substring(0,x),oldVal.substring(x+1)};
				}

				@Override
				public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag) throws java.io.IOException
				{
					oldVal=oldVal.trim();
					final int x=oldVal.indexOf('/');
					String oldRsc = "";
					int oldAmt = 0;
					if(x>0)
					{
						oldRsc = oldVal.substring(0,x);
						oldAmt = CMath.s_int(oldVal.substring(x));
					}
					oldRsc = CMLib.genEd().prompt(mob,oldRsc,++showNumber[0],showFlag,prompt(),choices());
					if(oldRsc.length()>0)
						return oldRsc+"/"+CMLib.genEd().prompt(mob,oldAmt,++showNumber[0],showFlag,prompt());
					return "";
				}
			},
		});
		DEFAULT_EDITORS = new Hashtable<String,AbilityParmEditor>();
		for(int v=0;v<V.size();v++)
		{
			final AbilityParmEditor A = V.elementAt(v);
			DEFAULT_EDITORS.put(A.ID(),A);
		}
		return DEFAULT_EDITORS;
	}

	protected class AbilityRecipeDataImpl implements AbilityRecipeData
	{
		private String recipeFilename;
		private String recipeFormat;
		private Vector<Object> columns;
		private Vector<DVector> dataRows;
		private int numberOfDataColumns;
		public String[] columnHeaders;
		public int[] columnLengths;
		public int classFieldIndex;
		private String parseError = null;
		private boolean wasVFS = false;

		public AbilityRecipeDataImpl(String recipeFilename, String recipeFormat)
		{
			this.recipeFilename = recipeFilename;
			this.recipeFormat = recipeFormat;
			if(recipeFilename.trim().length()==0)
			{
				parseError = "No file";
				return;
			}
			final CMFile F = new CMFile(Resources.buildResourcePath("skills")+recipeFilename,null,CMFile.FLAG_LOGERRORS);
			wasVFS=F.isVFSFile();
			final StringBuffer str=F.text();
			columns = parseRecipeFormatColumns(recipeFormat);
			numberOfDataColumns = 0;
			for(int c = 0; c < columns.size(); c++)
			{
				if(columns.elementAt(c) instanceof List)
					numberOfDataColumns++;
			}
			dataRows = null;
			try
			{
				dataRows = parseDataRows(str,columns,numberOfDataColumns);
				final DVector editRow = new DVector(2);
				for(int c=0;c<columns().size();c++)
				{
					if(columns().elementAt(c) instanceof List)
						editRow.addElement(columns().elementAt(c),"");
				}
				if(editRow.size()==0)
				{
					//classFieldIndex = CMAbleParms.getClassFieldIndex(dataRow);
				}
				else
					classFieldIndex = CMAbleParms.getClassFieldIndex(editRow);
				fixDataColumns(dataRows);
			}
			catch(final CMException e)
			{
				parseError = e.getMessage();
				return;
			}
			columnLengths = new int[numberOfDataColumns];
			columnHeaders = new String[numberOfDataColumns];
			calculateRecipeCols(columnLengths,columnHeaders,dataRows);
		}

		@Override
		public boolean wasVFS()
		{
			return wasVFS;
		}

		@Override
		public DVector newRow(String classFieldData)
		{
			final DVector editRow = blankRow();
			final int keyIndex =classFieldIndex;
			if((keyIndex>=0)&&(classFieldData!=null))
			{
				editRow.setElementAt(keyIndex,2,classFieldData);
			}
			try
			{
				fixDataColumn(editRow,-1);
			}
			catch (final CMException cme)
			{
				return null;
			}
			for(int i=0;i<editRow.size();i++)
			{
				if(i!=keyIndex)
				{
					final AbilityParmEditor A = getEditors().get(editRow.elementAt(i,1));
					editRow.setElementAt(i,2,A.defaultValue());
				}
			}
			return editRow;
		}
		
		@Override
		public DVector blankRow()
		{
			final DVector editRow = new DVector(2);
			for(int c=0;c<columns().size();c++)
			{
				if(columns().elementAt(c) instanceof List)
					editRow.addElement(columns().elementAt(c),"");
			}
			return editRow;
		}

		@Override
		public int getClassFieldIndex()
		{
			return classFieldIndex;
		}

		@Override
		public String recipeFilename()
		{
			return recipeFilename;
		}

		@Override
		public String recipeFormat()
		{
			return recipeFormat;
		}

		@Override
		public Vector<DVector> dataRows()
		{
			return dataRows;
		}

		@Override
		public Vector<? extends Object> columns()
		{
			return columns;
		}

		@Override
		public int[] columnLengths()
		{
			return columnLengths;
		}

		@Override
		public String[] columnHeaders()
		{
			return columnHeaders;
		}

		@Override
		public int numberOfDataColumns()
		{
			return numberOfDataColumns;
		}

		@Override
		public String parseError()
		{
			return parseError;
		}
	}

	protected abstract class AbilityParmEditorImpl implements AbilityParmEditor
	{
		private final String ID;
		private PairList<String,String> choices = null;
		private final ParmType fieldType;
		private String prompt = null;
		private String header = null;

		public AbilityParmEditorImpl(String fieldName, String shortHeader, ParmType type)
		{
			ID=fieldName;
			fieldType = type;
			header = shortHeader;
			prompt = CMStrings.capitalizeAndLower(CMStrings.replaceAll(ID,"_"," "));
			createChoices();
		}

		@Override
		public String ID()
		{
			return ID;
		}

		@Override
		public ParmType parmType()
		{
			return fieldType;
		}

		@Override
		public String prompt()
		{
			return prompt;
		}

		@Override
		public String colHeader()
		{
			return header;
		}

		@Override
		public boolean confirmValue(String oldVal)
		{
			final boolean spaceOK = fieldType != ParmType.ONEWORD;
			boolean emptyOK = false;
			switch(fieldType)
			{
			case STRINGORNULL:
				emptyOK = true;
			//$FALL-THROUGH$
			case ONEWORD:
			case STRING:
			{
				if((!spaceOK) && (oldVal.indexOf(' ') >= 0))
					return false;
				return (emptyOK)||(oldVal.trim().length()>0);
			}
			case NUMBER:
				return CMath.isInteger(oldVal);
			case CHOICES:
				if(!CMStrings.contains(choices.toArrayFirst(new String[0]),oldVal))
					return CMStrings.contains(choices.toArrayFirst(new String[0]),oldVal.toUpperCase().trim());
				return true;
			case MULTICHOICES:
				return CMath.isInteger(oldVal)||choices().contains(oldVal);
			case SPECIAL:
				break;
			}
			return false;
		}

		@Override
		public String[] fakeUserInput(String oldVal)
		{
			boolean emptyOK = false;
			switch(fieldType)
			{
			case STRINGORNULL:
				emptyOK = true;
			//$FALL-THROUGH$
			case ONEWORD:
			case STRING:
			{
				if(emptyOK && (oldVal.trim().length()==0))
					return new String[]{"NULL"};
				return new String[]{oldVal};
			}
			case NUMBER:
				return new String[]{oldVal};
			case CHOICES:
			{
				if(oldVal.trim().length()==0) return new String[]{"NULL"};
				final Vector<String> V = new XVector<String>(choices.toArrayFirst(new String[0]));
				for(int v=0;v<V.size();v++)
				{
					if(oldVal.equalsIgnoreCase(V.elementAt(v)))
						return new String[]{choices.get(v).second};
				}
				return new String[]{oldVal};
			}
			case MULTICHOICES:
				if(oldVal.trim().length()==0) 
					return new String[]{"NULL"};
				if(!CMath.isInteger(oldVal))
				{
					final Vector<String> V = new XVector<String>(choices.toArrayFirst(new String[0]));
					for(int v=0;v<V.size();v++)
					{
						if(oldVal.equalsIgnoreCase(V.elementAt(v)))
							return new String[]{choices.get(v).second,""};
					}
				}
				else
				{
					final Vector<String> V = new Vector<String>();
					for(int c=0;c<choices.size();c++)
					{
						if(CMath.bset(CMath.s_int(oldVal),CMath.s_int(choices.get(c).first)))
						{
							V.addElement(choices.get(c).second);
							V.addElement(choices.get(c).second);
						}
					}
					if(V.size()>0)
					{
						V.addElement("");
						return CMParms.toStringArray(V);
					}
				}
				return new String[]{"NULL"};
			case SPECIAL:
				break;
			}
			return new String[]{};
		}

		@Override
		public String commandLinePrompt(MOB mob, String oldVal, int[] showNumber, int showFlag)
		throws java.io.IOException
		{
			String str = null;
			boolean emptyOK = false;
			final boolean spaceOK = fieldType != ParmType.ONEWORD;
			switch(fieldType)
			{
			case STRINGORNULL:
				emptyOK = true;
			//$FALL-THROUGH$
			case ONEWORD:
			case STRING:
			{
				++showNumber[0];
				boolean proceed = true;
				while(proceed&&(!mob.session().isStopped()))
				{
					str = CMLib.genEd().prompt(mob,oldVal,showNumber[0],showFlag,prompt(),emptyOK).trim();
					if((!spaceOK) && (str.indexOf(' ') >= 0))
						mob.tell(L("Spaces are not allowed here."));
					else
						proceed=false;
				}
				break;
			}
			case NUMBER:
			{
				final String newStr=CMLib.genEd().prompt(mob,oldVal,++showNumber[0],showFlag,prompt(),true);
				if(newStr.trim().length()==0)
					str="";
				else
					str = Integer.toString(CMath.s_int(newStr));
				break;
			}
			case CHOICES:
				str = CMLib.genEd().promptMultiOrExtra(mob,oldVal,++showNumber[0],showFlag,prompt(),choices);
				break;
			case MULTICHOICES:
				str = CMLib.genEd().promptMultiOrExtra(mob,oldVal,++showNumber[0],showFlag,prompt(),choices);
				if(CMath.isInteger(str))
					str = Integer.toString(CMath.s_int(str));
				break;
			case SPECIAL:
				break;
			}
			return str;
		}

		@Override
		public String webValue(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
		{
			final String webValue = httpReq.getUrlParameter(fieldName);
			switch(fieldType)
			{
			case ONEWORD:
			case STRINGORNULL:
			case STRING:
			case NUMBER:
				return (webValue == null)?oldVal:webValue;
			case MULTICHOICES:
			{
				if(webValue == null)
					return oldVal;
				String id="";
				long num=0;
				int index=0;
				for(;httpReq.isUrlParameter(fieldName+id);id=""+(++index))
				{
					final String newVal = httpReq.getUrlParameter(fieldName+id);
					if(CMath.s_long(newVal)<=0)
						return newVal;
					num |= CMath.s_long(newVal);
				}
				return ""+num;
			}
			case CHOICES:
				return (webValue == null)?oldVal:webValue;
			case SPECIAL:
				break;
			}
			return "";
		}

		@Override
		public String webTableField(HTTPRequest httpReq, java.util.Map<String, String> parms, String oldVal)
		{
			return oldVal;
		}

		@Override
		public String webField(HTTPRequest httpReq, java.util.Map<String,String> parms, String oldVal, String fieldName)
		{
			int textSize = 50;
			final String webValue = webValue(httpReq,parms,oldVal,fieldName);
			String onChange = null;
			final Vector<String> choiceValues = new Vector<String>();
			switch(fieldType)
			{
			case ONEWORD:
				textSize = 10;
			//$FALL-THROUGH$
			case STRINGORNULL:
			case STRING:
				return "\n\r<INPUT TYPE=TEXT NAME=" + fieldName + " SIZE=" + textSize + " VALUE=\"" + webValue + "\">";
			case NUMBER:
				return "\n\r<INPUT TYPE=TEXT NAME=" + fieldName + " SIZE=10 VALUE=\"" + webValue + "\">";
			case MULTICHOICES:
			{
				onChange = " MULTIPLE ";
				if(!parms.containsKey("NOSELECT"))
					onChange+= "ONCHANGE=\"MultiSelect(this);\"";
				if(CMath.isInteger(webValue))
				{
					final int bits = CMath.s_int(webValue);
					for(int i=0;i<choices.size();i++)
					{
						final int bitVal =CMath.s_int(choices.get(i).first);
						if((bitVal>0)&&(CMath.bset(bits,bitVal)))
							choiceValues.addElement(choices.get(i).first);
					}
				}
			}
			//$FALL-THROUGH$
			case CHOICES:
			{
				if(choiceValues.size()==0)
					choiceValues.addElement(webValue);
				if((onChange == null)&&(!parms.containsKey("NOSELECT")))
					onChange = " ONCHANGE=\"Select(this);\"";
				else
				if(onChange==null)
					onChange="";
				final StringBuffer str= new StringBuffer("");
				str.append("\n\r<SELECT NAME="+fieldName+onChange+">");
				for(int i=0;i<choices.size();i++)
				{
					final String option = (choices.get(i).first);
					str.append("<OPTION VALUE=\""+option+"\" ");
					for(int c=0;c<choiceValues.size();c++)
					{
						if(option.equalsIgnoreCase(choiceValues.elementAt(c)))
							str.append("SELECTED");
					}
					str.append(">"+(choices.get(i).second));
				}
				return str.toString()+"</SELECT>";
			}
			case SPECIAL:
				break;
			}
			return "";
		}

		public abstract void createChoices();

		@Override
		public PairList<String,String> createChoices(Enumeration<? extends Object> e)
		{
			if(choices != null)
				return choices;
			choices = new PairVector<String,String>();
			Object o = null;
			for(;e.hasMoreElements();)
			{
				o = e.nextElement();
				if(o instanceof String)
					choices.add((String)o,CMStrings.capitalizeAndLower((String)o));
				else
				if(o instanceof Ability)
					choices.add(((Ability)o).ID(),((Ability)o).name());
				else
				if(o instanceof Race)
					choices.add(((Race)o).ID(),((Race)o).name());
				else
				if(o instanceof Environmental)
					choices.add(((Environmental)o).ID(),((Environmental)o).ID());
			}
			return choices;
		}

		@Override
		public PairList<String,String> createChoices(Vector<? extends Object> V)
		{
			return createChoices(V.elements());
		}

		@Override
		public PairList<String,String> createChoices(String[] S)
		{
			final XVector<String> X=new XVector<String>(S);
			Collections.sort(X);
			return createChoices(X.elements());
		}

		public PairList<String,String> createBinaryChoices(String[] S) 
		{
			if(choices != null)
				return choices;
			choices = createChoices(new XVector<String>(S).elements());
			for(int i=0;i<choices.size();i++)
			{
				if(i==0)
					choices.get(i).first =Integer.toString(0);
				else
					choices.get(i).first = Integer.toString(1<<(i-1));
			}
			return choices;
		}

		public PairList<String,String> createNumberedChoices(String[] S) 
		{
			if(choices != null)
				return choices;
			choices = createChoices(new XVector<String>(S).elements());
			for(int i=0;i<choices.size();i++)
				choices.get(i).first = Integer.toString(i);
			return choices;
		}