@Override
	public String runMacro(HTTPRequest httpReq, String parm, HTTPResponse httpResp)
	{
		final java.util.Map<String,String> parms=parseParms(parm);
		final String last=httpReq.getUrlParameter("ROOM");
		if(last==null)
			return " @break@";
		final String mobCode=httpReq.getUrlParameter("MOB");
		if(mobCode==null)
			return "@break@";

		if(!CMProps.getBoolVar(CMProps.Bool.MUDSTARTED))
			return CMProps.getVar(CMProps.Str.MUDSTATUS);

		Room R=(Room)httpReq.getRequestObjects().get(last);
		if(R==null)
		{
			if(!last.equalsIgnoreCase("ANY"))
			{
				R=MUDGrinder.getRoomObject(httpReq, last);
				if(R==null)
					return "No Room?!";
				CMLib.map().resetRoom(R);
				httpReq.getRequestObjects().put(last,R);
			}
		}
		MOB M=null;
		synchronized(("SYNC"+((R!=null)?R.roomID():"null")).intern())
		{
			if(R!=null)
				R=CMLib.map().getRoom(R);
			M=(MOB)httpReq.getRequestObjects().get(mobCode);
			if(M==null)
			{
				if(mobCode.startsWith("CATALOG-")||mobCode.startsWith("NEWCATA-"))
				{
					M=CMLib.catalog().getCatalogMob(mobCode.substring(8));
					if(M==null)
						M=CMClass.getMOB("GenMob");
					else
						M=(MOB)M.copyOf();
				}
				else
				if(mobCode.equals("NEW"))
					M=CMClass.getMOB("GenMob");
				else
				if(mobCode.equals("NEWDEITY"))
				{
					M=CMClass.getMOB("GenDeity");
					final String deityName=httpReq.getUrlParameter("NEWMOBNAME");
					if((M!=null)&&(deityName!=null))
					{
						M.setDisplayText(CMStrings.replaceAll(((Deity)M).displayText(),CMStrings.capitalizeFirstLetter(M.name()),deityName));
						((Deity)M).setClericRitual(CMStrings.replaceAll(((Deity)M).getClericRitual(),M.name(),deityName));
						((Deity)M).setWorshipRitual(CMStrings.replaceAll(((Deity)M).getWorshipRitual(),M.name(),deityName));
					}
				}
				else
				if(R!=null)
					M=RoomData.getMOBFromCode(R,mobCode);
				else
					M=RoomData.getMOBFromCode(RoomData.getMOBCache(),mobCode);
				if((M==null)||(!M.isSavable()))
				{
					final StringBuffer str=new StringBuffer("No MOB?!");
					str.append(" Got: "+mobCode);
					str.append(", Includes: ");
					if(R!=null)
					for(int m=0;m<R.numInhabitants();m++)
					{
						final MOB M2=R.fetchInhabitant(m);
						if((M2!=null)&&(M2.isSavable()))
							str.append(M2.Name()+"="+RoomData.getMOBCode(R,M2));
					}
					return clearWebMacros(str);
				}
				httpReq.getRequestObjects().put(mobCode,M);
			}
		}
		final MOB oldM=M;
		// important generic<->non generic swap!
		final String newClassID=httpReq.getUrlParameter("CLASSES");
		if((newClassID!=null)
		&&(!newClassID.equals(CMClass.classID(M)))
		&&(CMClass.getMOB(newClassID)!=null))
			M=CMClass.getMOB(newClassID);

		boolean changedClass=((httpReq.isUrlParameter("CHANGEDCLASS"))
							 &&(httpReq.getUrlParameter("CHANGEDCLASS")).equals("true"));
		changedClass=changedClass
					 &&(mobCode.equals("NEW")
							 ||mobCode.equalsIgnoreCase("NEWDEITY")
							 ||mobCode.startsWith("CATALOG-")
							 ||mobCode.startsWith("NEWCATA-"));
		final boolean changedLevel=((httpReq.isUrlParameter("CHANGEDLEVEL"))&&(httpReq.getUrlParameter("CHANGEDLEVEL")).equals("true"));
		final boolean firstTime=(!httpReq.isUrlParameter("ACTION"))
				||(!(httpReq.getUrlParameter("ACTION")).equals("MODIFYMOB"))
				||(changedClass);

		if(((changedLevel)||(changedClass))&&(M.isGeneric()))
		{
			CMLib.leveler().fillOutMOB(M,CMath.s_int(firstTime?"0":httpReq.getUrlParameter("LEVEL")));
			httpReq.addFakeUrlParameter("REJUV",""+M.basePhyStats().rejuv());
			httpReq.addFakeUrlParameter("ARMOR",""+M.basePhyStats().armor());
			httpReq.addFakeUrlParameter("DAMAGE",""+M.basePhyStats().damage());
			httpReq.addFakeUrlParameter("SPEED",""+M.basePhyStats().speed());
			httpReq.addFakeUrlParameter("ATTACK",""+M.basePhyStats().attackAdjustment());
			httpReq.addFakeUrlParameter("MONEY",""+CMLib.beanCounter().getMoney(M));
		}

		final StringBuffer str=new StringBuffer("");
		for(MOBDataField o : MOBDataField.values())
		{
			final String parmName=o.name();
			if(parms.containsKey(parmName))
			{
				String old=httpReq.getUrlParameter(parmName);
				if(old==null)
					old="";
				switch(o)
				{
				case NAME: // name
					if(firstTime)
					{
						if((mobCode.equalsIgnoreCase("NEW")||mobCode.equalsIgnoreCase("NEWDEITY")||mobCode.startsWith("CATALOG-")||mobCode.startsWith("NEWCATA-"))
						&&(httpReq.isUrlParameter("NEWMOBNAME")))
							old=httpReq.getUrlParameter("NEWMOBNAME");
						else
							old=M.Name();
					}
					str.append(old);
					break;
				case CLASSES: // classes
					{
						if(firstTime)
							old=CMClass.classID(M);
						Object[] sorted=(Object[])Resources.getResource("MUDGRINDER-MOBS");
						if(sorted==null)
						{
							final Vector<String> sortMe=new Vector<String>();
							for(final Enumeration<MOB> m=CMClass.mobTypes();m.hasMoreElements();)
								sortMe.addElement(CMClass.classID(m.nextElement()));
							sorted=(new TreeSet<String>(sortMe)).toArray();
							Resources.submitResource("MUDGRINDER-MOBS",sorted);
						}
						if(parms.containsKey("CLASSESID"))
							str.append(old);
						else
						{
							for (final Object element : sorted)
							{
								final String cnam=(String)element;
								str.append("<OPTION VALUE=\""+cnam+"\"");
								if(old.equals(cnam))
									str.append(" SELECTED");
								str.append(">"+cnam);
							}
						}
					}
					break;
				case DISPLAYTEXT: // displaytext
					if(firstTime)
						old=M.displayText();
					str.append(old);
					break;
				case DESCRIPTION: // description
					if(firstTime)
						old=M.description();
					str.append(old);
					break;
				case LEVEL: // level
					if(firstTime)
						old=""+M.basePhyStats().level();
					str.append(old);
					break;
				case ABILITY: // ability;
					if(firstTime)
						old=""+M.basePhyStats().ability();
					str.append(old);
					break;
				case REJUV: // rejuv;
					if(firstTime)
						old=""+M.basePhyStats().rejuv();
					if(old.equals(""+Integer.MAX_VALUE))
						str.append("0");
					else
						str.append(old);
					break;
				case MISCTEXT: // misctext
					if(firstTime)
						old=M.text();
					str.append(old);
					break;
				case RACE: // race
					if(firstTime)
						old=""+M.baseCharStats().getMyRace().ID();
					for(final Enumeration<Race> r=CMClass.races();r.hasMoreElements();)
					{
						final Race R2=r.nextElement();
						str.append("<OPTION VALUE=\""+R2.ID()+"\"");
						if(R2.ID().equals(old))
							str.append(" SELECTED");
						str.append(">"+R2.name());
					}
					if((changedClass)||(changedLevel))
					{
						final Race R3=CMClass.getRace(old);
						char G=(char)M.baseCharStats().getStat(CharStats.STAT_GENDER);
						if((httpReq.isUrlParameter("GENDER"))&&((httpReq.getUrlParameter("GENDER")).length()>0))
							G=(httpReq.getUrlParameter("GENDER")).charAt(0);
						if(R3!=null)
						{
							R3.setHeightWeight(M.basePhyStats(),G);
							httpReq.addFakeUrlParameter("WEIGHT",""+M.basePhyStats().weight());
							httpReq.addFakeUrlParameter("HEIGHT",""+M.basePhyStats().height());
						}
					}
					break;
				case GENDER: // gender
					if(firstTime)
						old=""+((char)M.baseCharStats().getStat(CharStats.STAT_GENDER));
					if(old.toUpperCase().startsWith("M"))
					{
						str.append("<INPUT TYPE=RADIO NAME=GENDER CHECKED VALUE=M>Male");
						str.append("&nbsp;&nbsp; <INPUT TYPE=RADIO NAME=GENDER VALUE=F>Female");
						str.append("&nbsp;&nbsp; <INPUT TYPE=RADIO NAME=GENDER VALUE=N>Neuter");
					}
					else
					if(old.toUpperCase().startsWith("F"))
					{
						str.append("<INPUT TYPE=RADIO NAME=GENDER VALUE=M>Male");
						str.append("&nbsp;&nbsp; <INPUT TYPE=RADIO CHECKED NAME=GENDER VALUE=F>Female");
						str.append("&nbsp;&nbsp; <INPUT TYPE=RADIO NAME=GENDER VALUE=N>Neuter");
					}
					else
					{
						str.append("<INPUT TYPE=RADIO NAME=GENDER VALUE=M>Male");
						str.append("&nbsp;&nbsp; <INPUT TYPE=RADIO NAME=GENDER VALUE=F>Female");
						str.append("&nbsp;&nbsp; <INPUT CHECKED TYPE=RADIO NAME=GENDER VALUE=N>Neuter");
					}
					break;
				case HEIGHT: // height
					if(firstTime)
						old=""+M.basePhyStats().height();
					str.append(old);
					break;
				case WEIGHT: // weight
					if(firstTime)
						old=""+M.basePhyStats().weight();
					str.append(old);
					break;
				case SPEED: // speed
					if(firstTime)
						old=""+M.basePhyStats().speed();
					str.append(old);
					break;
				case ATTACK: // attack
					if(firstTime)
						old=""+M.basePhyStats().attackAdjustment();
					str.append(old);
					break;
				case DAMAGE: // damage
					if(firstTime)
						old=""+M.basePhyStats().damage();
					str.append(old);
					break;
				case ARMOR: // armor
					if(firstTime)
						old=""+M.basePhyStats().armor();
					str.append(old);
					break;
				case ALIGNMENT: // alignment
					if(CMLib.factions().getFaction(CMLib.factions().AlignID())!=null)
					{
						if(firstTime)
							old=""+M.fetchFaction(CMLib.factions().AlignID());
						for(final Faction.Align v : Faction.Align.values())
						{
							if(v!=Faction.Align.INDIFF)
							{
								str.append("<OPTION VALUE="+v.toString());
								if(old.equalsIgnoreCase(v.toString()))
									str.append(" SELECTED");
								str.append(">"+CMStrings.capitalizeAndLower(v.toString().toLowerCase()));
							}
						}
					}
					break;
				case MONEY: // money
					if(firstTime)
					{
						old=""+CMLib.beanCounter().getMoney(M);
						CMLib.beanCounter().clearInventoryMoney(M,null);
					}
					str.append(old);
					break;
				case ISRIDEABLE: // is rideable
					if(M instanceof Rideable)
						return "true";
					return "false";
				case RIDEABLETYPE: // rideable type
					if((firstTime)&&(M instanceof Rideable))
						old=""+((Rideable)M).rideBasis();
					for(int r=0;r<Rideable.RIDEABLE_DESCS.length;r++)
					{
						str.append("<OPTION VALUE=\""+r+"\"");
						if(r==CMath.s_int(old))
							str.append(" SELECTED");
						str.append(">"+Rideable.RIDEABLE_DESCS[r]);
					}
					break;
				case MOBSHELD: // rideable capacity
					if((firstTime)&&(M instanceof Rideable))
						old=""+((Rideable)M).riderCapacity();
					str.append(old);
					break;
				case ISSHOPKEEPER: // is shopkeeper
					if(M instanceof ShopKeeper)
						return "true";
					return "false";
				case SHOPKEEPERTYPE: // shopkeeper type
				{
					final HashSet<Integer> shopTypes=new HashSet<Integer>();
					if((firstTime)&&(M instanceof ShopKeeper))
					{
						for(int d=0;d<ShopKeeper.DEAL_DESCS.length;d++)
						{
							if(((ShopKeeper)M).isSold(d))
								shopTypes.add(Integer.valueOf(d));
						}
					}
					else
					{
						shopTypes.add(Integer.valueOf(CMath.s_int(old)));
						int x=1;
						while(httpReq.getUrlParameter(parmName+x)!=null)
						{
							shopTypes.add(Integer.valueOf(CMath.s_int(httpReq.getUrlParameter(parmName+x))));
							x++;
						}
					}
					if(M instanceof Banker)
					{
						int r=ShopKeeper.DEAL_BANKER;
						str.append("<OPTION VALUE=\""+r+"\"");
						if(shopTypes.contains(Integer.valueOf(r)))
							str.append(" SELECTED");
						str.append(">"+ShopKeeper.DEAL_DESCS[r]);
						r=ShopKeeper.DEAL_CLANBANKER;
						str.append("<OPTION VALUE=\""+r+"\"");
						if(shopTypes.contains(Integer.valueOf(r)))
							str.append(" SELECTED");
						str.append(">"+ShopKeeper.DEAL_DESCS[r]);
					}
					else
					if(M instanceof PostOffice)
					{
						int r=ShopKeeper.DEAL_POSTMAN;
						str.append("<OPTION VALUE=\""+r+"\"");
						if(shopTypes.contains(Integer.valueOf(r)))
							str.append(" SELECTED");
						str.append(">"+ShopKeeper.DEAL_DESCS[r]);
						r=ShopKeeper.DEAL_CLANPOSTMAN;
						str.append("<OPTION VALUE=\""+r+"\"");
						if(shopTypes.contains(Integer.valueOf(r)))
							str.append(" SELECTED");
						str.append(">"+ShopKeeper.DEAL_DESCS[r]);
					}
					else
					for(int r=0;r<ShopKeeper.DEAL_DESCS.length;r++)
					{
						if((r!=ShopKeeper.DEAL_CLANBANKER)
						&&(r!=ShopKeeper.DEAL_BANKER)
						&&(r!=ShopKeeper.DEAL_POSTMAN)
						&&(r!=ShopKeeper.DEAL_CLANPOSTMAN))
						{
							str.append("<OPTION VALUE=\""+r+"\"");
							if(shopTypes.contains(Integer.valueOf(r)))
								str.append(" SELECTED");
							str.append(">"+ShopKeeper.DEAL_DESCS[r]);
						}
					}
					break;
				}
				case ISGENERIC:
					if(M.isGeneric())
						return "true";
					return "false";
				case ISBANKER: // is banker
					if(M instanceof Banker)
						return "true";
					return "false";
				case COININT: // coin interest
					if((firstTime)&&(M instanceof Banker))
						old=""+((Banker)M).getCoinInterest();
					str.append(old);
					break;
				case ITEMINT: // item interest
					if((firstTime)&&(M instanceof Banker))
						old=""+((Banker)M).getItemInterest();
					str.append(old);
					break;
				case BANKNAME: // bank name
					if((firstTime)&&(M instanceof Banker))
						old=""+((Banker)M).bankChain();
					str.append(old);
					break;
				case SHOPPREJ: // prejudice factors
					if((firstTime)&&(M instanceof ShopKeeper))
						old=((ShopKeeper)M).prejudiceFactors();
					str.append(old);
					break;
				case ISDEITY: // is deity
					if(M instanceof Deity)
						return "true";
					return "false";
				case CLEREQ: // cleric requirements
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getClericRequirements();
					str.append(old);
					break;
				case CLERIT: // cleric ritual
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getClericRitual();
					str.append(old);
					break;
				case WORREQ: // worship requirements
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getWorshipRequirements();
					str.append(old);
					break;
				case WORRIT: // worship ritual
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getWorshipRitual();
					str.append(old);
					break;
				case CLESIN: // cleric sin
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getClericSin();
					str.append(old);
					break;
				case WORSIN: // worshipper sin
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getWorshipSin();
					str.append(old);
					break;
				case CLEPOW: // cleric power
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getClericPowerup();
					str.append(old);
					break;
				case CLANID: // clanid
					if(firstTime)
					{
						final StringBuilder oldBuf=new StringBuilder("");
						for(final Pair<Clan,Integer> p : M.clans())
							oldBuf.append(p.first.getName()).append("(").append(p.second.toString()).append("), ");
						old=oldBuf.toString();
					}
					str.append(old);
					break;
				case TATTOOS: // tattoos
					if(firstTime)
					{
						old="";
						for(final Enumeration<Tattoo> e=M.tattoos();e.hasMoreElements();)
							str.append(e.nextElement().toString()).append(";");
					}
					else
						str.append(old);
					break;
				case EXPERTISES: // expertises
					if(firstTime)
					{
						old="";
						for(final Enumeration<String> x=M.expertises();x.hasMoreElements();)
							str.append(x.nextElement()).append(';');
					}
					else
						str.append(old);
					break;
				case BUDGET: // budget
					if((firstTime)&&(M instanceof ShopKeeper))
						old=((ShopKeeper)M).budget();
					str.append(old);
					break;
				case DEVALRATE: // devaluation rate
					if((firstTime)&&(M instanceof ShopKeeper))
						old=((ShopKeeper)M).devalueRate();
					str.append(old);
					break;
				case INVRESETRATE: // inventory reset rate
					if((firstTime)&&(M instanceof ShopKeeper))
						old=""+((ShopKeeper)M).invResetRate();
					str.append(old);
					break;
				case IMAGE: // image
					if(firstTime)
						old=M.rawImage();
					str.append(old);
					break;
				case ISPOSTMAN: // ispostman
					if(M instanceof PostOffice)
						return "true";
					return "false";
				case POSTCHAIN: // postal chain
					if((firstTime)&&(M instanceof PostOffice))
						old=((PostOffice)M).postalChain();
					str.append(old);
					break;
				case POSTMIN: // minimum postage
					if((firstTime)&&(M instanceof PostOffice))
						old=""+((PostOffice)M).minimumPostage();
					str.append(old);
					break;
				case POSTLBS: // postage per pound
					if((firstTime)&&(M instanceof PostOffice))
						old=""+((PostOffice)M).postagePerPound();
					str.append(old);
					break;
				case POSTHOLD: // holding fee per pound
					if((firstTime)&&(M instanceof PostOffice))
						old=""+((PostOffice)M).holdFeePerPound();
					str.append(old);
					break;
				case POSTNEW: // new box fee
					if((firstTime)&&(M instanceof PostOffice))
						old=""+((PostOffice)M).feeForNewBox();
					str.append(old);
					break;
				case POSTHELD: // max held months
					if((firstTime)&&(M instanceof PostOffice))
						old=""+((PostOffice)M).maxMudMonthsHeld();
					str.append(old);
					break;
				case IGNOREMASK: // ignore mask
					if((firstTime)&&(M instanceof ShopKeeper))
						old=((ShopKeeper)M).ignoreMask();
					str.append(old);
					break;
				case LOANINT: // loan interest
					if((firstTime)&&(M instanceof Banker))
						old=""+((Banker)M).getLoanInterest();
					str.append(old);
					break;
				case SVCRIT: // service ritual
					if((firstTime)&&(M instanceof Deity))
						old=((Deity)M).getServiceRitual();
					str.append(old);
					break;
				case AUCCHAIN: // auction chain
					if((firstTime)&&(M instanceof Auctioneer))
						old=((Auctioneer)M).auctionHouse();
					str.append(old);
					break;
				case LIVELIST: // live list
					//if((firstTime)&&(M instanceof Auctioneer))
					//    old=""+((Auctioneer)M).liveListingPrice();
					//if(CMath.s_double(old)<0.0) old="";
					//str.append(old);
					break;
				case TIMELIST: // timed list
					if((firstTime)&&(M instanceof Auctioneer))
						old=""+((Auctioneer)M).timedListingPrice();
					if(CMath.s_double(old)<0.0)
						old="";
					str.append(old);
					break;
				case TIMELISTPCT: // timed list pct
					if((firstTime)&&(M instanceof Auctioneer))
						old=""+(((Auctioneer)M).timedListingPct()*100.0)+"%";
					if(CMath.s_pct(old)<0.0)
						old="";
					str.append(old);
					break;
				case LIVECUT: // live cut pct
					//if((firstTime)&&(M instanceof Auctioneer))
					//    old=""+(((Auctioneer)M).liveFinalCutPct()*100.0)+"%";
					//if(CMath.s_pct(old)<0.0) old="";
					str.append(old);
					break;
				case TIMECUT: // timed cut pct
					if((firstTime)&&(M instanceof Auctioneer))
						old=""+(((Auctioneer)M).timedFinalCutPct()*100.0)+"%";
					if(CMath.s_pct(old)<0.0)
						old="";
					str.append(old);
					break;
				case MAXDAYS: // max days
					if((firstTime)&&(M instanceof Auctioneer))
						old=""+((Auctioneer)M).maxTimedAuctionDays();
					if(CMath.s_double(old)<0.0)
						old="";
					str.append(old);
					break;
				case MINDAYS: // min days
					if((firstTime)&&(M instanceof Auctioneer))
						old=""+((Auctioneer)M).minTimedAuctionDays();
					if(CMath.s_double(old)<0.0)
						old="";
					str.append(old);
					break;
				case ISAUCTION: // is auction
					if(M instanceof Auctioneer)
						return "true";
					return "false";
				case DEITYID: // deityid
				{
					if(firstTime)
						old=M.getWorshipCharID();
					for(final Enumeration<Deity> d=CMLib.map().deities();d.hasMoreElements();)
					{
						final Deity D=d.nextElement();
						str.append("<OPTION VALUE=\""+D.Name()+"\"");
						if(D.Name().equalsIgnoreCase(old))
							str.append(" SELECTED");
						str.append(">"+D.Name());
					}
					break;
				}
				case VARMONEY: // varmoney
					if(firstTime)
						old=""+M.getMoneyVariation();
					str.append(old);
					break;
				case CATACAT: // catacat
					if((firstTime)&&(mobCode.startsWith("CATALOG-")||mobCode.startsWith("NEWCATA-")))
					{
						final String name=mobCode.substring(8);
						final CatalogLibrary.CataData data=CMLib.catalog().getCatalogMobData(name);
						if(data!=null)
							old=data.category();
					}
					str.append(old+", ");
					break;
				case CURSES:
					// moved to below
					break;
				case POWERS:
					// moved to below
					break;
				}
				if(firstTime)
					httpReq.addFakeUrlParameter(parmName,old.equals("checked")?"on":old);
			}
		}
		str.append(ExitData.dispositions(M,firstTime,httpReq,parms));
		str.append(MobData.senses(M,firstTime,httpReq,parms));
		str.append(AreaData.affects(M,httpReq,parms,1));
		str.append(AreaData.behaves(M,httpReq,parms,1));
		str.append(factions(M,httpReq,parms,1));
		str.append(MobData.abilities(M,httpReq,parms,1));
		str.append(MobData.clans(M,httpReq,parms,1));
		if(M instanceof Deity)
		{
			str.append(MobData.blessings((Deity)M,httpReq,parms,1));
			str.append(MobData.curses((Deity)M,httpReq,parms,1));
			str.append(MobData.powers((Deity)M,httpReq,parms,1));
		}
		if(M instanceof ShopKeeper)
			str.append(MobData.shopkeeper(R,(ShopKeeper)M,httpReq,parms,1));

		str.append(itemList(R,oldM,M,httpReq,parms,1));

		String strstr=str.toString();
		if(strstr.endsWith(", "))
			strstr=strstr.substring(0,strstr.length()-2);
		return clearWebMacros(strstr);
	}