/
com/planet_ink/coffee_mud/Abilities/Common/
com/planet_ink/coffee_mud/Abilities/Diseases/
com/planet_ink/coffee_mud/Abilities/Druid/
com/planet_ink/coffee_mud/Abilities/Fighter/
com/planet_ink/coffee_mud/Abilities/Languages/
com/planet_ink/coffee_mud/Abilities/Misc/
com/planet_ink/coffee_mud/Abilities/Prayers/
com/planet_ink/coffee_mud/Abilities/Properties/
com/planet_ink/coffee_mud/Abilities/Skills/
com/planet_ink/coffee_mud/Abilities/Songs/
com/planet_ink/coffee_mud/Abilities/Spells/
com/planet_ink/coffee_mud/Abilities/Thief/
com/planet_ink/coffee_mud/Abilities/Traps/
com/planet_ink/coffee_mud/Behaviors/
com/planet_ink/coffee_mud/CharClasses/interfaces/
com/planet_ink/coffee_mud/Commands/
com/planet_ink/coffee_mud/Commands/interfaces/
com/planet_ink/coffee_mud/Common/
com/planet_ink/coffee_mud/Common/interfaces/
com/planet_ink/coffee_mud/Exits/interfaces/
com/planet_ink/coffee_mud/Items/Armor/
com/planet_ink/coffee_mud/Items/Basic/
com/planet_ink/coffee_mud/Items/CompTech/
com/planet_ink/coffee_mud/Items/MiscMagic/
com/planet_ink/coffee_mud/Items/Weapons/
com/planet_ink/coffee_mud/Items/interfaces/
com/planet_ink/coffee_mud/Libraries/
com/planet_ink/coffee_mud/Libraries/interfaces/
com/planet_ink/coffee_mud/Locales/
com/planet_ink/coffee_mud/MOBS/
com/planet_ink/coffee_mud/Races/
com/planet_ink/coffee_mud/Races/interfaces/
com/planet_ink/coffee_mud/WebMacros/
com/planet_ink/coffee_mud/WebMacros/interfaces/
com/planet_ink/coffee_mud/core/
com/planet_ink/coffee_mud/core/collections/
com/planet_ink/coffee_mud/core/interfaces/
com/planet_ink/coffee_mud/core/intermud/
com/planet_ink/coffee_mud/core/intermud/i3/
com/planet_ink/coffee_web/server/
com/planet_ink/siplet/applet/
lib/
resources/factions/
resources/fakedb/
resources/progs/autoplayer/
resources/quests/holidays/
web/
web/admin.templates/
web/admin/grinder/
web/admin/images/
web/clan.templates/
web/pub.templates/
web/pub/images/mxp/
web/pub/sounds/
web/pub/textedit/
package com.planet_ink.coffee_mud.Libraries;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.TimeClock.TimePeriod;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.XMLLibrary.XMLTag;

import java.util.*;

/*
   Copyright 2005-2016 Bo Zimmerman

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

	   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
public class CoffeeShops extends StdLibrary implements ShoppingLibrary
{
	@Override
	public String ID()
	{
		return "CoffeeShops";
	}

	@Override
	public ShopKeeper getShopKeeper(Environmental E)
	{
		if(E==null)
			return null;
		if(E instanceof ShopKeeper)
			return (ShopKeeper)E;
		if(E instanceof Physical)
		{
			final Physical P=(Physical)E;
			for(final Enumeration<Ability> a=P.effects();a.hasMoreElements();)
			{
				final Ability A=a.nextElement();
				if(A instanceof ShopKeeper)
					return (ShopKeeper)A;
			}
		}
		if(E instanceof MOB)
		{
			Item I=null;
			final MOB mob=(MOB)E;
			for(int i=0;i<mob.numItems();i++)
			{
				I=mob.getItem(i);
				if(I instanceof ShopKeeper)
					return (ShopKeeper)I;
				if(I!=null)
				{
					for(final Enumeration<Ability> a=I.effects();a.hasMoreElements();)
					{
						final Ability A=a.nextElement();
						if(A instanceof ShopKeeper)
							return (ShopKeeper)A;
					}
				}
			}
		}
		return null;
	}

	@Override
	public List<Environmental> getAllShopkeepers(Room here, MOB notMOB)
	{
		final Vector<Environmental> V=new Vector<Environmental>();
		if(here!=null)
		{
			if(getShopKeeper(here)!=null)
				V.addElement(here);
			final Area A=here.getArea();
			if(getShopKeeper(A)!=null)
				V.addElement(A);
			final List<Area> V2=A.getParentsRecurse();
			for(int v2=0;v2<V2.size();v2++)
				if(getShopKeeper(V2.get(v2))!=null)
					V.addElement(V2.get(v2));

			for(int i=0;i<here.numInhabitants();i++)
			{
				final MOB thisMOB=here.fetchInhabitant(i);
				if((thisMOB!=null)
				&&(thisMOB!=notMOB)
				&&(getShopKeeper(thisMOB)!=null)
				&&((notMOB==null)||(CMLib.flags().canBeSeenBy(thisMOB,notMOB))))
					V.addElement(thisMOB);
			}
			for(int i=0;i<here.numItems();i++)
			{
				final Item thisItem=here.getItem(i);
				if((thisItem!=null)
				&&(thisItem!=notMOB)
				&&(getShopKeeper(thisItem)!=null)
				&&(!CMLib.flags().isGettable(thisItem))
				&&(thisItem.container()==null)
				&&((notMOB==null)||(CMLib.flags().canBeSeenBy(thisItem,notMOB))))
					V.addElement(thisItem);
			}
		}
		return V;
	}

	protected String getSellableElectronicsName(MOB viewerM, Electronics E)
	{
		String baseName=E.name(viewerM);
		final String[] marks=CMProps.getListFileStringList(CMProps.ListFile.TECH_LEVEL_NAMES);
		if(baseName.indexOf(E.getFinalManufacturer().name())<0)
			baseName+= "("+E.getFinalManufacturer().name()+")";
		if(marks.length>0)
			baseName+=" "+marks[E.techLevel()%marks.length];
		return baseName;
	}
	
	@Override
	public String getViewDescription(MOB viewerM, Environmental E)
	{
		final StringBuilder str=new StringBuilder("");
		if(E==null)
			return str.toString();
		str.append(L("Interested in @x1?",E.name()));
		str.append(L(" Here is some information for you:"));
		if(E instanceof Physical)
			str.append("\n\rLevel      : "+((Physical)E).phyStats().level());
		if(E instanceof Item)
		{
			final Item I=(Item)E;
			str.append(L("\n\rMaterial   : @x1",L(CMStrings.capitalizeAndLower(RawMaterial.CODES.NAME(I.material()).toLowerCase()))));
			str.append(L("\n\rWeight     : @x1 pounds",""+I.phyStats().weight()));
			if(I instanceof Electronics)
			{
				str.append(L("\n\rMake       : @x1",""+((Electronics)I).getFinalManufacturer().name()));
				str.append(L("\n\rType       : @x1",""+((Electronics)I).getTechType().getDisplayName()));
			}
			if(I instanceof Technical)
			{
				str.append(L("\n\rModel Num. : @x1",""+((Technical)I).techLevel()));
			}
			if(I instanceof BoardableShip)
			{
				final Area A=((BoardableShip)I).getShipArea();
				if(A!=null)
				{
					str.append(L("\n\rRooms      : @x1",""+A.numberOfProperIDedRooms()));
					final List<String> miscItems=new ArrayList<String>();
					for(final Enumeration<Room> r= A.getProperMap(); r.hasMoreElements();)
					{
						final Room R=r.nextElement();
						if(R==null)
							continue;
						for(final Enumeration<Item> i = R.items();i.hasMoreElements();)
						{
							final Item I2=i.nextElement();
							if(I2.displayText().length()>0)
							{
								if(I2 instanceof TechComponent)
									str.append(L("\n\r"+CMStrings.padRight(((TechComponent)I2).getTechType().getShortDisplayName(),11)+": @x1",getSellableElectronicsName(viewerM,(Electronics)I2)));
								else
									miscItems.add(I2.name(viewerM));
							}
						}
					}
					if(miscItems.size()>0)
					{
						str.append(L("\n\rMisc Items : "));
						str.append(CMParms.toListString(miscItems));
					}
				}
			}
			else
			if(I instanceof Weapon)
			{
				str.append(L("\n\rWeap. Type : @x1",L(CMStrings.capitalizeAndLower(Weapon.TYPE_DESCS[((Weapon)I).weaponDamageType()]))));
				str.append(L("\n\rWeap. Class: @x1",L(CMStrings.capitalizeAndLower(Weapon.CLASS_DESCS[((Weapon)I).weaponClassification()]))));
			}
			else
			if(I instanceof Armor)
			{
				str.append(L("\n\rWear Info  : Worn on "));
				final Wearable.CODES codes = Wearable.CODES.instance();
				final List<String> locs = new ArrayList<String>();
				for(final long wornCode : codes.all())
				{
					if(wornCode != Wearable.IN_INVENTORY)
					{
						if(codes.name(wornCode).length()>0)
						{
							if(((I.rawProperLocationBitmap()&wornCode)==wornCode))
							{
								locs.add(CMStrings.capitalizeAndLower(codes.name(wornCode)));
							}
						}
					}
				}
				str.append(CMParms.combineWith(locs, L(I.rawLogicalAnd() ? " and " : " or ")));
				if(I.phyStats().height()>0)
				{
					Armor.SizeDeviation deviation=((Armor) I).getSizingDeviation(viewerM);
					if(deviation != Armor.SizeDeviation.FITS)
						str.append(L("\n\rSize       : ")+I.phyStats().height()+" ("+L(deviation.toString().toLowerCase().replace('_',' ')+")"));
				}
			}
		}
		str.append(L("\n\rDescription: @x1",E.description()));
		return str.toString();
	}

	protected Ability getTrainableAbility(MOB teacher, Ability A)
	{
		if((teacher==null)||(A==null))
			return A;
		Ability teachableA=teacher.fetchAbility(A.ID());
		if(teachableA==null)
		{
			teachableA=(Ability)A.copyOf();
			teacher.addAbility(teachableA);
		}
		teachableA.setProficiency(100);
		return teachableA;
	}

	protected boolean shownInInventory(MOB seller, MOB buyer, Environmental product, ShopKeeper shopKeeper)
	{
		if(CMSecurity.isAllowed(buyer,buyer.location(),CMSecurity.SecFlag.CMDMOBS))
			return true;
		if(seller == buyer)
			return true;
		if(product instanceof Item)
		{
			if(((Item)product).container()!=null)
				return false;
			if(((Item)product).phyStats().level()>buyer.phyStats().level())
				return false;
			if(!CMLib.flags().canBeSeenBy(product,buyer))
				return false;
		}
		if(product instanceof MOB)
		{
			if(((MOB)product).phyStats().level()>buyer.phyStats().level())
				return false;
		}
		if(product instanceof Ability)
		{
			if(shopKeeper.isSold(ShopKeeper.DEAL_TRAINER))
			{
				if(!CMLib.ableMapper().qualifiesByLevel(buyer, (Ability)product))
					return false;
			}
		}
		return true;
	}

	public double rawSpecificGoldPrice(Environmental product, CoffeeShop shop)
	{
		double price=0.0;
		if(product instanceof Item)
			price=((Item)product).value();
		else
		if(product instanceof Ability)
		{
			if(shop.isSold(ShopKeeper.DEAL_TRAINER))
				price=CMLib.ableMapper().lowestQualifyingLevel(product.ID())*100;
			else
				price=CMLib.ableMapper().lowestQualifyingLevel(product.ID())*75;
		}
		else
		if(product instanceof MOB)
		{
			final MOB M=(MOB)product;
			final Ability A=M.fetchEffect("Prop_Retainable");
			if(A!=null)
			{
				if(A.text().indexOf(';')<0)
				{
					if(CMath.isDouble(A.text()))
						price=CMath.s_double(A.text());
					else
						price=CMath.s_int(A.text());
				}
				else
				{
					final String s2=A.text().substring(0,A.text().indexOf(';'));
					if(CMath.isDouble(s2))
						price=CMath.s_double(s2);
					else
						price=CMath.s_int(s2);
				}
			}
			if(price==0.0)
				price=(25.0+M.phyStats().level())*M.phyStats().level();
		}
		else
			price=CMLib.ableMapper().lowestQualifyingLevel(product.ID())*25;
		return price;
	}

	@Override
	public double prejudiceValueFromPart(MOB customer, boolean pawnTo, String part)
	{
		final int x=part.indexOf('=');
		if(x<0)
			return 0.0;
		final String sellorby=part.substring(0,x);
		part=part.substring(x+1);
		if(pawnTo&&(!sellorby.trim().equalsIgnoreCase("SELL")))
			return 0.0;
		if((!pawnTo)&&(!sellorby.trim().equalsIgnoreCase("BUY")))
			return 0.0;
		if(part.trim().indexOf(' ')<0)
			return CMath.s_double(part.trim());
		final Vector<String> V=CMParms.parse(part.trim());
		double d=0.0;
		boolean yes=false;
		final List<String> VF=customer.fetchFactionRanges();
		final String align=CMLib.flags().getAlignmentName(customer);
		final String sex=customer.charStats().genderName();
		for(int v=0;v<V.size();v++)
		{
			final String bit = V.elementAt(v);
			if (CMath.s_double(bit) != 0.0)
				d = CMath.s_double(bit);
			if ((bit.equalsIgnoreCase(customer.charStats().getCurrentClass().name()))
			||(bit.equalsIgnoreCase(customer.charStats().getCurrentClass().name(customer.charStats().getCurrentClassLevel())))
			||(bit.equalsIgnoreCase(sex))
			||(bit.equalsIgnoreCase(customer.charStats().getMyRace().racialCategory()))
			||(bit.equalsIgnoreCase(align)))
			{
				yes = true;
				break;
			}
			for (int vf = 0; vf < VF.size(); vf++)
			{
				if (bit.equalsIgnoreCase(V.elementAt(v)))
				{
					yes = true;
					break;
				}
			}
		}
		if(yes)
			return d;
		return 0.0;

	}

	@Override
	public double prejudiceFactor(MOB customer, String factors, boolean pawnTo)
	{
		factors=factors.toUpperCase();
		if(factors.length()==0)
		{
			factors=CMProps.getVar(CMProps.Str.PREJUDICE).trim();
			if(factors.length()==0)
				return 1.0;
		}
		if(factors.indexOf('=')<0)
		{
			if(CMath.s_double(factors)!=0.0)
				return CMath.s_double(factors);
			return 1.0;
		}
		int x=factors.indexOf(';');
		while(x>=0)
		{
			final String part=factors.substring(0,x).trim();
			factors=factors.substring(x+1).trim();
			final double d=prejudiceValueFromPart(customer,pawnTo,part);
			if(d!=0.0)
				return d;
			x=factors.indexOf(';');
		}
		final double d=prejudiceValueFromPart(customer,pawnTo,factors.trim());
		if(d!=0.0)
			return d;
		return 1.0;
	}

	protected double itemPriceFactor(Environmental E, Room R, String[] priceFactors, boolean pawnTo)
	{
		if(priceFactors.length==0)
			return 1.0;
		double factor=1.0;
		int x=0;
		String factorMask=null;
		ItemPossessor oldOwner=null;
		if(E instanceof Item)
		{
			oldOwner=((Item)E).owner();
			if(R!=null)
				((Item)E).setOwner(R);
		}
		for (final String priceFactor : priceFactors)
		{
			factorMask=priceFactor.trim();
			x=factorMask.indexOf(' ');
			if(x<0)
				continue;
			if(CMLib.masking().maskCheck(factorMask.substring(x+1).trim(),E,false))
				factor*=CMath.s_double(factorMask.substring(0,x).trim());
		}
		if(E instanceof Item)
			((Item)E).setOwner(oldOwner);
		if(factor!=0.0)
			return factor;
		return 1.0;
	}

	@Override
	public ShopKeeper.ShopPrice sellingPrice(MOB seller,
											 MOB buyer,
											 Environmental product,
											 ShopKeeper shop,
											 boolean includeSalesTax)
	{
		final ShopKeeper.ShopPrice val=new ShopKeeper.ShopPrice();
		if(product==null)
			return val;
		final int stockPrice=shop.getShop().stockPrice(product);
		if(stockPrice<=-100)
		{
			if(stockPrice<=-1000)
				val.experiencePrice=(stockPrice*-1)-1000;
			else
				val.questPointPrice=(stockPrice*-1)-100;
			return val;
		}
		if(stockPrice>=0)
			val.absoluteGoldPrice=stockPrice;
		else
			val.absoluteGoldPrice=rawSpecificGoldPrice(product,shop.getShop());

		if(buyer==null)
		{
			if(val.absoluteGoldPrice>0.0)
				val.absoluteGoldPrice=CMLib.beanCounter().abbreviatedRePrice(seller,val.absoluteGoldPrice);
			return val;
		}

		double prejudiceFactor=prejudiceFactor(buyer,shop.finalPrejudiceFactors(),false);
		final Room loc=CMLib.map().roomLocation(shop);
		prejudiceFactor*=itemPriceFactor(product,loc,shop.finalItemPricingAdjustments(),false);
		val.absoluteGoldPrice=CMath.mul(prejudiceFactor,val.absoluteGoldPrice);

		// the price is 200% at 0 charisma, and 100% at 35
		if(seller.isMonster() && (!CMLib.flags().isGolem(seller)))
		{
			val.absoluteGoldPrice=val.absoluteGoldPrice
								 +val.absoluteGoldPrice
								 -CMath.mul(val.absoluteGoldPrice,CMath.div(buyer.charStats().getStat(CharStats.STAT_CHARISMA),35.0));
		}
		
		if(includeSalesTax)
		{
			final double salesTax=getSalesTax(seller.getStartRoom(),seller);
			val.absoluteGoldPrice+=((salesTax>0.0)?(CMath.mul(val.absoluteGoldPrice,CMath.div(salesTax,100.0))):0.0);
		}
		if(val.absoluteGoldPrice<=0.0)
			val.absoluteGoldPrice=1.0;
		else
			val.absoluteGoldPrice=CMLib.beanCounter().abbreviatedRePrice(seller,val.absoluteGoldPrice);

		// the magical aura discount for miscmagic (potions, anything else.. MUST be basePhyStats tho!
		if((CMath.bset(buyer.basePhyStats().disposition(),PhyStats.IS_BONUS))
		&&(product instanceof MiscMagic)
		&&(val.absoluteGoldPrice>2.0))
			val.absoluteGoldPrice/=2;

		return val;
	}

	private final static String[] emptyStringArray = new String[0]; 
	
	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public String[] parseItemPricingAdjustments(String factors)
	{
		if((factors == null) || (factors.trim().length() == 0))
			return emptyStringArray;
		Map<String,String[]> hashedPriceAdjustments = (Map)Resources.getResource("SYSTEM_HASHED_PRICINGADJUSTMENTS");
		if(hashedPriceAdjustments == null)
		{
			hashedPriceAdjustments = new Hashtable<String,String[]>();
			Resources.submitResource("SYSTEM_HASHED_PRICINGADJUSTMENTS", hashedPriceAdjustments);
		}
		
		String[] pricingAdjustments = hashedPriceAdjustments.get(factors);
		if(pricingAdjustments != null)
			return pricingAdjustments;
		pricingAdjustments = CMParms.parseSemicolons(factors,true).toArray(new String[0]);
		hashedPriceAdjustments.put(factors, pricingAdjustments);
		return pricingAdjustments;
	}
	
	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public String[] parsePrejudiceFactors(String factors)
	{
		if((factors == null) || (factors.trim().length() == 0))
			return emptyStringArray;
		Map<String,String[]> hashedPrejudiceFactors = (Map)Resources.getResource("SYSTEM_HASHED_PREJUDICEFACTORS");
		if(hashedPrejudiceFactors == null)
		{
			hashedPrejudiceFactors = new Hashtable<String,String[]>();
			Resources.submitResource("SYSTEM_HASHED_PREJUDICEFACTORS", hashedPrejudiceFactors);
		}
		
		String[] prejudiceFactors = hashedPrejudiceFactors.get(factors);
		if(prejudiceFactors != null)
			return prejudiceFactors;
		prejudiceFactors = CMParms.parseSemicolons(factors,true).toArray(new String[0]);
		hashedPrejudiceFactors.put(factors, prejudiceFactors);
		return prejudiceFactors;
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected Pair<Double,String>[] alternateParseItemPricingAdjustments(String factors)
	{
		if((factors == null) || (factors.trim().length() == 0))
			return new Pair[0];
		Map<String,Pair<Double,String>[]> hashedPriceAdjustments = (Map)Resources.getResource("SYSTEM_HASHED_PRICINGADJUSTMENTS");
		if(hashedPriceAdjustments == null)
		{
			hashedPriceAdjustments = new Hashtable<String,Pair<Double,String>[]>();
			Resources.submitResource("SYSTEM_HASHED_PRICINGADJUSTMENTS", hashedPriceAdjustments);
		}
		
		Pair<Double,String>[] pricingAdjustments = hashedPriceAdjustments.get(factors);
		if(pricingAdjustments != null)
			return pricingAdjustments;
		List<String> semiParsed = CMParms.parseSemicolons(factors,true);
		List<Pair<Double,String>> almostParsed = new ArrayList<Pair<Double,String>>();
		for(String s : semiParsed)
		{
			int x=s.indexOf(' ');
			if(x<0)
				continue;
			almostParsed.add(new Pair<Double,String>(Double.valueOf(CMath.s_double(s.substring(0,x).trim())),s.substring(x+1).trim()));
		}
		pricingAdjustments = almostParsed.toArray(new Pair[0]);
		hashedPriceAdjustments.put(factors, pricingAdjustments);
		return pricingAdjustments;
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Pair<Long,TimeClock.TimePeriod> parseBudget(String budget)
	{
		if((budget == null) || (budget.trim().length() == 0))
			return null;
		else
		{
			Map<String,Pair<Long,TimeClock.TimePeriod>> hashedBudgets = (Map)Resources.getResource("SYSTEM_PARSED_BUDGETS");
			if(hashedBudgets == null)
			{
				hashedBudgets = new Hashtable<String,Pair<Long,TimeClock.TimePeriod>>();
				Resources.submitResource("SYSTEM_PARSED_BUDGETS", hashedBudgets);
			}
			Pair<Long,TimeClock.TimePeriod> budgetVals = hashedBudgets.get(budget);
			if(budgetVals != null)
				return budgetVals;
			budgetVals = new Pair<Long,TimeClock.TimePeriod>(Long.valueOf(Long.MAX_VALUE / 2), TimePeriod.HOUR);
			final Vector<String> V = CMParms.parse(budget.trim().toUpperCase());
			if (V.size() > 0)
			{
				if (V.firstElement().equals("0"))
					budgetVals.first = Long.valueOf(0);
				else
				{
					budgetVals.first = Long.valueOf(CMath.s_long(V.firstElement()));
					if (budgetVals.first.longValue() == 0)
						budgetVals.first = Long.valueOf(Long.MAX_VALUE / 2);
				}
				if (V.size() > 1)
				{
					String s = V.lastElement().toUpperCase();
					budgetVals.second = (TimePeriod)CMath.s_valueOf(TimePeriod.class, s);
					if((budgetVals.second == null)&&(s.endsWith("S")))
						budgetVals.second = (TimePeriod)CMath.s_valueOf(TimePeriod.class, s.substring(0,s.length()-1));
					if(budgetVals.second == null)
						budgetVals.second = TimePeriod.HOUR;
				}
			}
			hashedBudgets.put(budget, budgetVals);
			return budgetVals;
		}
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public double[] parseDevalueRate(String factors)
	{
		if((factors == null) || (factors.trim().length() == 0))
			return null;
		else
		{
			Map<String,double[]> hashedValueRates = (Map)Resources.getResource("SYSTEM_PARSED_DEVALUE_RATES");
			if(hashedValueRates == null)
			{
				hashedValueRates = new Hashtable<String,double[]>();
				Resources.submitResource("SYSTEM_PARSED_DEVALUE_RATES", hashedValueRates);
			}
			double[] devalueRate = hashedValueRates.get(factors);
			if(devalueRate != null)
				return devalueRate;
			
			final Vector<String> V=CMParms.parse(factors.trim());
			if(V.size()==1)
			{
				double rate = CMath.div(CMath.s_double(V.firstElement()),100.0); 
				devalueRate = new double[] { rate , rate };
			}
			else
			{
				devalueRate = new double[] { CMath.s_pct(V.firstElement()) , CMath.s_pct(V.lastElement()) };
			}
			hashedValueRates.put(factors, devalueRate);
			return devalueRate;
		}
	}
	
	public double devalue(ShopKeeper shop, Environmental product, double number)
	{
		int num=shop.getShop().numberInStock(product);
		num += (int)Math.round(Math.floor(number/2.0));
		if(num<=0)
			return 0.0;
		final double[] rates=shop.finalDevalueRate();
		if(rates == null)
			return 0.0;
		double rate=(product instanceof RawMaterial)?rates[1]:rates[0];
		if(rate<=0.0)
			return 0.0;
		rate=rate*num;
		if(rate>1.0)
			rate=1.0;
		if(rate<0.0)
			rate=0.0;
		return rate;
	}

	@Override
	public ShopKeeper.ShopPrice pawningPrice(MOB seller,
											 MOB buyer,
											 Environmental product,
											 ShopKeeper shop)
	{
		double number=1.0;
		final ShopKeeper.ShopPrice val=new ShopKeeper.ShopPrice();
		try
		{
			if(product instanceof PackagedItems)
			{
				number=((PackagedItems)product).numberOfItemsInPackage();
				product=((PackagedItems)product).peekFirstItem();
			}
			else
			if(product instanceof RawMaterial)
			{
				number = ((RawMaterial)product).basePhyStats().weight();
				product = (Environmental)product.copyOf();
				((RawMaterial) product).basePhyStats().setWeight(1);
				((RawMaterial) product).phyStats().setWeight(1);
			}
			if(product==null)
				return val;
			final int stockPrice=shop.getShop().stockPrice(product);
			if(stockPrice<=-100)
			{
				return val;
			}
	
			if(stockPrice>=0.0)
				val.absoluteGoldPrice=stockPrice;
			else
				val.absoluteGoldPrice=rawSpecificGoldPrice(product,shop.getShop());
	
			if(buyer==null)
			{
				val.absoluteGoldPrice *= number;
				return val;
			}
	
			double prejudiceFactor=prejudiceFactor(buyer,shop.finalPrejudiceFactors(),true);
			final Room loc=CMLib.map().roomLocation(shop);
			prejudiceFactor*=itemPriceFactor(product,loc,shop.finalItemPricingAdjustments(),true);
			val.absoluteGoldPrice=CMath.mul(prejudiceFactor,val.absoluteGoldPrice);
	
			// gets the shopkeeper a deal on junk.  Pays 5% at 3 charisma, and 50% at 35
			double buyPrice=CMath.div(CMath.mul(val.absoluteGoldPrice,buyer.charStats().getStat(CharStats.STAT_CHARISMA)),70.0);
			if(!(product instanceof Ability))
				buyPrice=CMath.mul(buyPrice,1.0-devalue(shop,product,number));
	
	
			final double sellPrice=sellingPrice(seller,buyer,product,shop,false).absoluteGoldPrice;
	
			if(buyPrice>sellPrice)
				val.absoluteGoldPrice=sellPrice;
			else
				val.absoluteGoldPrice=buyPrice;
	
			val.absoluteGoldPrice *= number;

			if(val.absoluteGoldPrice<=0.0)
				val.absoluteGoldPrice=1.0;
		}
		finally
		{
			if((number > 1.0)&&(product!=null))
				product.destroy();
		}
		return val;
	}


	@Override
	public double getSalesTax(Room homeRoom, MOB seller)
	{
		if((seller==null)||(homeRoom==null))
			return 0.0;
		final Law theLaw=CMLib.law().getTheLaw(homeRoom,seller);
		if(theLaw!=null)
		{
			final String taxs=(String)theLaw.taxLaws().get("SALESTAX");
			if(taxs!=null)
				return CMath.s_double(taxs);
		}
		return 0.0;

	}

	@Override
	public boolean standardSellEvaluation(MOB seller,
										  MOB buyer,
										  Environmental product,
										  ShopKeeper shop,
										  double maxToPay,
										  double maxEverPaid,
										  boolean sellNotValue)
	{
		if((product!=null)
		&&(shop.doISellThis(product))
		&&(!(product instanceof Coins)))
		{
			final Room sellerR=seller.location();
			if(sellerR!=null)
			{
				final int medianLevel=sellerR.getArea().getAreaIStats()[Area.Stats.MED_LEVEL.ordinal()];
				if(medianLevel>0)
				{
					final String range=CMParms.getParmStr(shop.finalPrejudiceFactors(),"RANGE","0");
					int rangeI=0;
					if((range.endsWith("%"))&&(CMath.isInteger(range.substring(0,range.length()-1))))
					{
						rangeI=CMath.s_int(range.substring(0,range.length()-1));
						rangeI=(int)Math.round(CMath.mul(medianLevel,CMath.div(rangeI,100.0)));
					}
					else
					if(CMath.isInteger(range))
						rangeI=CMath.s_int(range);
					if((rangeI>0)
					&&(product instanceof Physical)
					&&((((Physical)product).phyStats().level()>(medianLevel+rangeI))
						||(((Physical)product).phyStats().level()<(medianLevel-rangeI))))
					{
						CMLib.commands().postSay(seller,buyer,L("I'm sorry, that's out of my level range."),true,false);
						return false;
					}
				}
			}
			final double yourValue=pawningPrice(seller,buyer,product,shop).absoluteGoldPrice;
			if(yourValue<2)
			{
				CMLib.commands().postSay(seller,buyer,L("I'm not interested."),true,false);
				return false;
			}
			if((product instanceof Physical)&&CMLib.flags().isEnspelled((Physical)product) || CMLib.flags().isOnFire((Physical)product))
			{
				CMLib.commands().postSay(seller, buyer, L("I won't buy that in it's present state."), true, false);
				return false;
			}
			if((sellNotValue)&&(yourValue>maxToPay))
			{
				if(yourValue>maxEverPaid)
					CMLib.commands().postSay(seller,buyer,L("That's way out of my price range! Try AUCTIONing it."),true,false);
				else
					CMLib.commands().postSay(seller,buyer,L("Sorry, I can't afford that right now.  Try back later."),true,false);
				return false;
			}
			if(product instanceof Ability)
			{
				CMLib.commands().postSay(seller,buyer,L("I'm not interested."),true,false);
				return false;
			}
			if((product instanceof Container)&&(((Container)product).hasALock()))
			{
				boolean found=false;
				final List<Item> V=((Container)product).getContents();
				for(int i=0;i<V.size();i++)
				{
					final Item I=V.get(i);
					if((I instanceof DoorKey)
					&&(((DoorKey)I).getKey().equals(((Container)product).keyName())))
						found=true;
					else
					if(CMLib.flags().isEnspelled(I) || CMLib.flags().isOnFire(I))
					{
						CMLib.commands().postSay(seller, buyer, L("I won't buy the contents of that in it's present state."), true, false);
						return false;
					}
				}
				if(!found)
				{
					CMLib.commands().postSay(seller,buyer,L("I won't buy that back unless you put the key in it."),true,false);
					return false;
				}
			}
			if((product instanceof Item)&&(buyer.isMine(product)))
			{
				final CMMsg msg2=CMClass.getMsg(buyer,product,CMMsg.MSG_DROP,null);
				if(!buyer.location().okMessage(buyer,msg2))
					return false;
			}
			return true;
		}
		CMLib.commands().postSay(seller,buyer,L("I'm sorry, I'm not buying those."),true,false);
		return false;
	}


	@Override
	public boolean standardBuyEvaluation(MOB seller,
										 MOB buyer,
										 Environmental product,
										 ShopKeeper shop,
										 boolean buyNotView)
	{
		if((product!=null)
		&&(shop.getShop().doIHaveThisInStock("$"+product.Name()+"$",buyer)))
		{
			if(buyNotView)
			{
				final ShopKeeper.ShopPrice price=sellingPrice(seller,buyer,product,shop,true);
				if((price.experiencePrice>0)&&(price.experiencePrice>buyer.getExperience()))
				{
					CMLib.commands().postSay(seller,buyer,L("You aren't experienced enough to buy @x1.",product.name()),false,false);
					return false;
				}
				if((price.questPointPrice>0)&&(price.questPointPrice>buyer.getQuestPoint()))
				{
					CMLib.commands().postSay(seller,buyer,L("You don't have enough quest points to buy @x1.",product.name()),false,false);
					return false;
				}
				if((price.absoluteGoldPrice>0.0)
				&&(price.absoluteGoldPrice>CMLib.beanCounter().getTotalAbsoluteShopKeepersValue(buyer,seller)))
				{
					CMLib.commands().postSay(seller,buyer,L("You can't afford to buy @x1.",product.name()),false,false);
					return false;
				}
			}
			if(product instanceof Item)
			{
				if(((Item)product).phyStats().level()>buyer.phyStats().level())
				{
					CMLib.commands().postSay(seller,buyer,L("That's too advanced for you, I'm afraid."),true,false);
					return false;
				}
			}
			if((product instanceof LandTitle)
			&&((shop.isSold(ShopKeeper.DEAL_CLANDSELLER))||(shop.isSold(ShopKeeper.DEAL_CSHIPSELLER))))
			{
				final Pair<Clan,Integer> clanPair=CMLib.clans().findPrivilegedClan(buyer, Clan.Function.PROPERTY_OWNER);
				if(clanPair==null)
				{
					if(!buyer.clans().iterator().hasNext())
						CMLib.commands().postSay(seller,buyer,L("I only sell to clans."),true,false);
					else
					if(!buyer.isMonster())
						CMLib.commands().postSay(seller,buyer,L("You are not authorized by your clan to handle property."),true,false);
					return false;
				}
			}
			if(product instanceof MOB)
			{
				if(buyer.totalFollowers()>=buyer.maxFollowers())
				{
					CMLib.commands().postSay(seller,buyer,L("You can't accept any more followers."),true,false);
					return false;
				}
				if((CMProps.getIntVar(CMProps.Int.FOLLOWLEVELDIFF)>0)
				&&(!CMSecurity.isAllowed(seller,seller.location(),CMSecurity.SecFlag.ORDER))
				&&(!CMSecurity.isAllowed(buyer,buyer.location(),CMSecurity.SecFlag.ORDER)))
				{
					if(seller.phyStats().level() > (buyer.phyStats().level() + CMProps.getIntVar(CMProps.Int.FOLLOWLEVELDIFF)))
					{
						buyer.tell(L("@x1 is too advanced for you.",product.name()));
						return false;
					}
					if(seller.phyStats().level() < (buyer.phyStats().level() - CMProps.getIntVar(CMProps.Int.FOLLOWLEVELDIFF)))
					{
						buyer.tell(L("@x1 is too inexperienced for you.",product.name()));
						return false;
					}
				}
			}
			if(product instanceof Ability)
			{
				if(shop.isSold(ShopKeeper.DEAL_TRAINER))
				{
					final MOB teacher=CMClass.getMOB("Teacher");
					final Ability teachableA=getTrainableAbility(teacher, (Ability)product);
					if((teachableA==null)||(!teachableA.canBeLearnedBy(teacher,buyer)))
					{
						teacher.destroy();
						return false;
					}
					teacher.destroy();
				}
				else
				if(buyNotView)
				{
					final Ability A=(Ability)product;
					if(A.canTarget(Ability.CAN_MOBS)){}
					else
					if(A.canTarget(Ability.CAN_ITEMS))
					{
						Item I=buyer.fetchWieldedItem();
						if(I==null)
							I=buyer.fetchHeldItem();
						if(I==null)
						{
							CMLib.commands().postSay(seller,buyer,L("You need to be wielding or holding the item you want this cast on."),true,false);
							return false;
						}
					}
					else
					{
						CMLib.commands().postSay(seller,buyer,L("I don't know how to sell that spell."),true,false);
						return false;
					}
				}
			}
			return true;
		}
		CMLib.commands().postSay(seller,buyer,L("I don't have that in stock.  Ask for my LIST."),true,false);
		return false;
	}

	@Override
	public String getListInventory(MOB seller,
								   MOB buyer,
								   List<? extends Environmental> rawInventory,
								   int limit,
								   ShopKeeper shop,
								   String mask)
	{
		final StringBuilder str=new StringBuilder("");
		int csize=0;
		final Vector<Environmental> inventory=new Vector<Environmental>();
		Environmental E=null;
		for(int i=0;i<rawInventory.size();i++)
		{
			E=rawInventory.get(i);
			if(shownInInventory(seller,buyer,E,shop)
			&&((mask==null)||(mask.length()==0)||(CMLib.english().containsString(E.name(),mask))))
				inventory.addElement(E);
		}

		if(inventory.size()>0)
		{
			final int totalCols=((shop.isSold(ShopKeeper.DEAL_LANDSELLER))
							   ||(shop.isSold(ShopKeeper.DEAL_CLANDSELLER))
							   ||(shop.isSold(ShopKeeper.DEAL_SHIPSELLER))
							   ||(shop.isSold(ShopKeeper.DEAL_CSHIPSELLER)))?1:2;
			final int totalWidth=CMLib.lister().fixColWidth(60.0/totalCols,buyer);
			String showPrice=null;
			ShopKeeper.ShopPrice price=null;
			for(int i=0;i<inventory.size();i++)
			{
				E=inventory.elementAt(i);
				price=sellingPrice(seller,buyer,E,shop,true);
				if((price.experiencePrice>0)&&(((""+price.experiencePrice).length()+2)>(4+csize)))
					csize=(""+price.experiencePrice).length()-2;
				else
				if((price.questPointPrice>0)&&(((""+price.questPointPrice).length()+2)>(4+csize)))
					csize=(""+price.questPointPrice).length()-2;
				else
				{
					showPrice=CMLib.beanCounter().abbreviatedPrice(seller,price.absoluteGoldPrice);
					if(showPrice.length()>(4+csize))
						csize=showPrice.length()-4;
				}
			}

			final String c="^x["+CMStrings.padRight(L("Cost"),4+csize)+"] "+CMStrings.padRight(L("Product"),Math.max(totalWidth-csize,5));
			str.append(c+((totalCols>1)?c:"")+"^.^N^<!ENTITY shopkeeper \""+CMStrings.removeColors(seller.name())+"\"^>^.^N\n\r");
			int colNum=0;
			int rowNum=0;
			String col=null;
			for(int i=0;i<inventory.size();i++)
			{
				E=inventory.elementAt(i);
				price=sellingPrice(seller,buyer,E,shop,true);
				col=null;
				if(price.questPointPrice>0)
					col=CMStrings.padRight(L("[@x1qp",""+price.questPointPrice),(5+csize))+"] ^<SHOP^>"+CMStrings.padRight(E.name(),"^</SHOP^>",Math.max(totalWidth-csize,5));
				else
				if(price.experiencePrice>0)
					col=CMStrings.padRight(L("[@x1xp",""+price.experiencePrice),(5+csize))+"] ^<SHOP^>"+CMStrings.padRight(E.name(),"^</SHOP^>",Math.max(totalWidth-csize,5));
				else
					col=CMStrings.padRight("["+CMLib.beanCounter().abbreviatedPrice(seller,price.absoluteGoldPrice),5+csize)+"] ^<SHOP^>"+CMStrings.padRight(E.name(),"^</SHOP^>",Math.max(totalWidth-csize,5));
				if((++colNum)>totalCols)
				{
					str.append("\n\r");
					rowNum++;
					if((limit>0)&&(rowNum>limit))
						break;
					colNum=1;
				}
				str.append(col);
			}
		}
		if(str.length()==0)
		{
			if((!shop.isSold(ShopKeeper.DEAL_BANKER))
			&&(!shop.isSold(ShopKeeper.DEAL_CLANBANKER))
			&&(!shop.isSold(ShopKeeper.DEAL_CLANPOSTMAN))
			&&(!shop.isSold(ShopKeeper.DEAL_AUCTIONEER))
			&&(!shop.isSold(ShopKeeper.DEAL_POSTMAN)))
				return seller.name()+" has nothing for sale.";
			return "";
		}
		final double salesTax=getSalesTax(seller.getStartRoom(),seller);
		return "\n\r"+str
				+((salesTax<=0.0)?"":"\n\r\n\rPrices above include a "+salesTax+"% sales tax.")
				+"^T";
	}

	@Override
	public String findInnRoom(InnKey key, String addThis, Room R)
	{
		if(R==null)
			return null;
		final String keyNum=key.getKey();
		for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
		{
			if((R.getExitInDir(d)!=null)&&(R.getExitInDir(d).keyName().equals(keyNum)))
			{
				final String dirName=((R instanceof BoardableShip)||(R.getArea() instanceof BoardableShip))?
						Directions.getShipDirectionName(d):Directions.getDirectionName(d);
				if(addThis.length()>0)
					return addThis+" and to the "+dirName.toLowerCase();
				return "to the "+dirName.toLowerCase();
			}
		}
		return null;
	}

	@Override
	public MOB parseBuyingFor(MOB buyer, String message)
	{
		MOB mobFor=buyer;
		if((message!=null)&&(message.length()>0)&&(buyer.location()!=null))
		{
			final Vector<String> V=CMParms.parse(message);
			if(V.elementAt(V.size()-2).equalsIgnoreCase("for"))
			{
				String s=V.lastElement();
				if(s.endsWith("."))
					s=s.substring(0,s.length()-1);
				final MOB M=buyer.location().fetchInhabitant("$"+s+"$");
				if(M!=null)
					mobFor=M;
			}
		}
		return mobFor;
	}

	@Override
	public double transactPawn(MOB shopkeeper,
							   MOB pawner,
							   ShopKeeper shop,
							   Environmental product)
	{
		Environmental coreSoldItem=product;
		final Environmental rawSoldItem=product;
		int number=1;
		if(coreSoldItem instanceof PackagedItems)
		{
			coreSoldItem=((PackagedItems)rawSoldItem).peekFirstItem();
			number=((PackagedItems)rawSoldItem).numberOfItemsInPackage();
		}
		if((coreSoldItem!=null)&&(shop.doISellThis(coreSoldItem)))
		{
			final double val=pawningPrice(shopkeeper,pawner,rawSoldItem,shop).absoluteGoldPrice;
			final String currency=CMLib.beanCounter().getCurrency(shopkeeper);
			if(!(shopkeeper instanceof ShopKeeper))
				CMLib.beanCounter().subtractMoney(shopkeeper,currency,val);
			CMLib.beanCounter().giveSomeoneMoney(shopkeeper,pawner,currency,val);
			pawner.recoverPhyStats();
			pawner.tell(L("@x1 pays you @x2 for @x3.",shopkeeper.name(),CMLib.beanCounter().nameCurrencyShort(shopkeeper,val),rawSoldItem.name()));
			if(rawSoldItem instanceof Item)
			{
				List<Item> V=null;
				if(rawSoldItem instanceof Container)
					V=((Container)rawSoldItem).getDeepContents();
				((Item)rawSoldItem).unWear();
				((Item)rawSoldItem).removeFromOwnerContainer();
				if(V!=null)
				for(int v=0;v<V.size();v++)
					V.get(v).removeFromOwnerContainer();
				shop.getShop().addStoreInventory(coreSoldItem,number,-1);
				if(V!=null)
				for(int v=0;v<V.size();v++)
				{
					final Item item2=V.get(v);
					if(!shop.doISellThis(item2)||(item2 instanceof DoorKey))
						item2.destroy();
					else
						shop.getShop().addStoreInventory(item2,1,-1);
				}
			}
			else
			if(product instanceof MOB)
			{
				final MOB newMOB=(MOB)product.copyOf();
				newMOB.setStartRoom(null);
				final Ability A=newMOB.fetchEffect("Skill_Enslave");
				if(A!=null)
					A.setMiscText("");
				newMOB.setLiegeID("");
				newMOB.setClan("", Integer.MIN_VALUE); // delete all sequence
				shop.getShop().addStoreInventory(newMOB);
				((MOB)product).setFollowing(null);
				if((((MOB)product).basePhyStats().rejuv()>0)
				&&(((MOB)product).basePhyStats().rejuv()!=PhyStats.NO_REJUV)
				&&(((MOB)product).getStartRoom()!=null))
					((MOB)product).killMeDead(false);
				else
					((MOB)product).destroy();
			}
			else
			if(product instanceof Ability)
			{

			}
			return val;
		}
		return Double.MIN_VALUE;
	}

	@Override
	public void transactMoneyOnly(MOB seller,
								  MOB buyer,
								  ShopKeeper shop,
								  Environmental product,
								  boolean sellerGetsPaid)
	{
		if((seller==null)||(seller.location()==null)||(buyer==null)||(shop==null)||(product==null))
			return;
		final Room room=seller.location();
		final ShopKeeper.ShopPrice price=sellingPrice(seller,buyer,product,shop,true);
		if(price.absoluteGoldPrice>0.0)
		{
			CMLib.beanCounter().subtractMoney(buyer,CMLib.beanCounter().getCurrency(seller),price.absoluteGoldPrice);
			double totalFunds=price.absoluteGoldPrice;
			if(getSalesTax(seller.getStartRoom(),seller)!=0.0)
			{
				final Law theLaw=CMLib.law().getTheLaw(room,seller);
				final Area A2=CMLib.law().getLegalObject(room);
				if((theLaw!=null)&&(A2!=null))
				{
					final Law.TreasurySet treas=theLaw.getTreasuryNSafe(A2);
					final Room treasuryR=treas.room;
					final Container treasuryContainer=treas.container;
					if(treasuryR!=null)
					{
						final double taxAmount=totalFunds-sellingPrice(seller,buyer,product,shop,false).absoluteGoldPrice;
						totalFunds-=taxAmount;
						final Coins COIN=CMLib.beanCounter().makeBestCurrency(CMLib.beanCounter().getCurrency(seller),taxAmount,treasuryR,treasuryContainer);
						if(COIN!=null)
							COIN.putCoinsBack();
					}
				}
			}
			if(seller.isMonster())
			{
				final LandTitle T=CMLib.law().getLandTitle(seller.getStartRoom());
				if((T!=null)&&(T.getOwnerName().length()>0))
				{
					CMLib.beanCounter().modifyLocalBankGold(seller.getStartRoom().getArea(),
															T.getOwnerName(),
															CMLib.utensils().getFormattedDate(buyer)
																+": Deposit of "+CMLib.beanCounter().nameCurrencyShort(seller,totalFunds)
																+": Purchase: "+product.Name()+" from "+seller.Name(),
															CMLib.beanCounter().getCurrency(seller),
															totalFunds);
				}
			}
			if(sellerGetsPaid)
				CMLib.beanCounter().giveSomeoneMoney(seller,seller,CMLib.beanCounter().getCurrency(seller),totalFunds);
		}
		if(price.questPointPrice>0)
			buyer.setQuestPoint(buyer.getQuestPoint()-price.questPointPrice);
		if(price.experiencePrice>0)
			CMLib.leveler().postExperience(buyer,null,null,-price.experiencePrice,false);
		buyer.recoverPhyStats();
	}

	@Override
	public boolean purchaseItems(Item baseProduct, List<Environmental> products, MOB seller, MOB mobFor)
	{
		if((seller==null)||(seller.location()==null)||(mobFor==null))
			return false;
		final Room room=seller.location();
		for(int p=0;p<products.size();p++)
		{
			if(products.get(p) instanceof Item)
				room.addItem((Item)products.get(p),ItemPossessor.Expire.Player_Drop);
		}
		final CMMsg msg2=CMClass.getMsg(mobFor,baseProduct,seller,CMMsg.MSG_GET,null);
		if((baseProduct instanceof LandTitle)||(room.okMessage(mobFor,msg2)))
		{
			room.send(mobFor,msg2);
			if(baseProduct instanceof InnKey)
			{
				final InnKey item =(InnKey)baseProduct;
				String buf=findInnRoom(item, "", room);
				if(buf==null)
					buf=findInnRoom(item, "upstairs", room.getRoomInDir(Directions.UP));
				if(buf==null)
					buf=findInnRoom(item, "downstairs", room.getRoomInDir(Directions.DOWN));
				if(buf!=null)
					CMLib.commands().postSay(seller,mobFor,L("Your room is @x1.",buf),true,false);
			}
			return true;
		}
		return false;
	}

	@Override
	public boolean purchaseMOB(MOB product, MOB seller, ShopKeeper shop, MOB mobFor)
	{
		if((seller==null)||(seller.location()==null)||(product==null)||(shop==null)||(mobFor==null))
			return false;
		product.basePhyStats().setRejuv(PhyStats.NO_REJUV);
		product.recoverPhyStats();
		product.setMiscText(product.text());
		Ability slaveA=null;
		if(shop.isSold(ShopKeeper.DEAL_SLAVES))
		{
			slaveA=product.fetchEffect("Skill_Enslave");
			if(slaveA!=null)
				slaveA.setMiscText("");
			else
			if(!CMLib.flags().isAnimalIntelligence(product))
			{
				slaveA=CMClass.getAbility("Skill_Enslave");
				if(slaveA!=null)
					product.addNonUninvokableEffect(slaveA);
			}
		}
		product.bringToLife(seller.location(),true);
		if(slaveA!=null)
		{
			product.setLiegeID("");
			product.setClan("", Integer.MIN_VALUE); // delete all sequence
			product.setStartRoom(null);
			slaveA.setMiscText(mobFor.Name());
			product.text();
		}
		CMLib.commands().postFollow(product,mobFor,false);
		if(product.amFollowing()==null)
		{
			mobFor.tell(L("You cannot accept seem to accept this follower!"));
			return false;
		}
		return true;
	}

	@Override
	public void purchaseAbility(Ability A,
								MOB seller,
								ShopKeeper shop,
								MOB mobFor)
	{
		if((seller==null)||(seller.location()==null)||(A==null)||(shop==null)||(mobFor==null))
			return ;
		final Room room=seller.location();
		if(shop.isSold(ShopKeeper.DEAL_TRAINER))
		{
			final MOB teacher=CMClass.getMOB("Teacher");
			teacher.setName(seller.name());
			teacher.setBaseCharStats(seller.baseCharStats());
			teacher.setLocation(room);
			teacher.recoverCharStats();
			final Ability teachableA=getTrainableAbility(teacher,A);
			if(teachableA!=null)
				CMLib.expertises().postTeach(teacher,mobFor,A);
			teacher.destroy();
		}
		else
		{
			if(seller.isMonster())
			{
				seller.curState().setMana(seller.maxState().getMana());
				seller.curState().setMovement(seller.maxState().getMovement());
			}
			final Object[][] victims=new Object[room.numInhabitants()][2];
			for(int x=0;x>victims.length;x++)
			{ // save victim status
				final MOB M=room.fetchInhabitant(x);
				if(M!=null){ victims[x][0]=M;victims[x][1]=M.getVictim();}
			}
			final Vector<String> V=new Vector<String>();
			if(A.canTarget(Ability.CAN_MOBS))
			{
				V.addElement("$"+mobFor.name()+"$");
				A.invoke(seller,V,mobFor,true,0);
			}
			else
			if(A.canTarget(Ability.CAN_ITEMS))
			{
				Item I=mobFor.fetchWieldedItem();
				if(I==null)
					I=mobFor.fetchHeldItem();
				if(I==null)
					I=mobFor.fetchItem(null,Wearable.FILTER_WORNONLY,"all");
				if(I==null)
					I=mobFor.fetchItem(null,Wearable.FILTER_UNWORNONLY,"all");
				if(I!=null)
				{
					V.addElement("$"+I.name()+"$");
					seller.addItem(I);
					A.invoke(seller,V,I,true,0);
					seller.delItem(I);
					if(!mobFor.isMine(I))
						mobFor.addItem(I);
				}
			}
			if(seller.isMonster())
			{
				seller.curState().setMana(seller.maxState().getMana());
				seller.curState().setMovement(seller.maxState().getMovement());
			}
			for(int x=0;x>victims.length;x++)
				((MOB)victims[x][0]).setVictim((MOB)victims[x][1]);
		}
	}

	private void addShipProperty(MOB buyer, List<Environmental> V, ItemCollection extItems)
	{
		for(final Enumeration<Item> i=extItems.items();i.hasMoreElements();)
		{
			final Item I=i.nextElement();
			if((I instanceof PrivateProperty)&&(I instanceof BoardableShip))
			{
				final PrivateProperty P = (PrivateProperty)I;
				if(CMLib.law().doesOwnThisProperty(buyer,P))
				{
					final LandTitle titleI=(LandTitle)CMClass.getItem("GenTitle");
					titleI.setLandPropertyID(I.Name());
					if(!titleI.Name().endsWith(" (Copy)"))
						titleI.setName(L("@x1 (Copy)",titleI.Name()));
					titleI.text();
					((Item)titleI).recoverPhyStats();
					V.add(titleI);
				}
			}
		}
	}
	
	@Override
	public List<Environmental> addRealEstateTitles(List<Environmental> V, MOB buyer, CoffeeShop shop, Room myRoom)
	{
		if((myRoom==null)||(buyer==null))
			return V;
		final Area myArea=myRoom.getArea();
		String name=buyer.Name();
		Pair<Clan,Integer> buyerClanPair=null;
		if(shop.isSold(ShopKeeper.DEAL_CLANDSELLER)
		&&((buyerClanPair=CMLib.clans().findPrivilegedClan(buyer, Clan.Function.PROPERTY_OWNER))!=null))
			name=buyerClanPair.first.clanID();
		if((shop.isSold(ShopKeeper.DEAL_LANDSELLER)||(buyerClanPair!=null))
		&&(myArea!=null))
		{
			final HashSet<Environmental> roomsHandling=new HashSet<Environmental>();
			final Map<Environmental,LandTitle> titles=new Hashtable<Environmental,LandTitle>();
			for(final Enumeration<Room> r=myArea.getProperMap();r.hasMoreElements();)
			{
				final Room R=r.nextElement();
				final LandTitle A=CMLib.law().getLandTitle(R);
				if((A!=null)&&(R.roomID().length()>0))
					titles.put(R,A);
			}

			for(final Environmental R : titles.keySet())
			{
				final LandTitle A=titles.get(R);
				if(!roomsHandling.contains(R))
				{
					if(R instanceof Area)
						roomsHandling.add(R);
					else
					{
						final List<Room> V2=A.getAllTitledRooms();
						roomsHandling.addAll(V2);
					}
					if((A.getOwnerName().length()>0)
					&&(!A.getOwnerName().equals(name))
					&&((!A.getOwnerName().equals(buyer.getLiegeID()))||(!buyer.isMarriedToLiege())))
						continue;
					boolean skipThisOne=false;
					if(R instanceof Room)
					{
						for(int d=0;d<4;d++)
						{
							final Room R2=((Room)R).getRoomInDir(d);
							LandTitle L2=null;
							if(R2!=null)
							{
								L2=titles.get(R2);
								if(L2==null)
								{
									skipThisOne = false;
									break;
								}
							}
							else
								continue;
							if((L2.getOwnerName().equals(name))
							||(L2.getOwnerName().equals(buyer.getLiegeID())&&(buyer.isMarriedToLiege())))
							{
								skipThisOne = false;
								break;
							}
							if(L2.getOwnerName().length()>0)
								skipThisOne=true;
						}
					}
					if(skipThisOne)
						continue;
					final Item I=CMClass.getItem("GenTitle");
					if(R instanceof Room)
						((LandTitle)I).setLandPropertyID(CMLib.map().getExtendedRoomID((Room)R));
					else
						((LandTitle)I).setLandPropertyID(R.Name());
					if((((LandTitle)I).getOwnerName().length()>0)
					&&(!I.Name().endsWith(" (Copy)")))
						I.setName(L("@x1 (Copy)",I.Name()));
					I.text();
					I.recoverPhyStats();
					if((A.getOwnerName().length()==0)
					&&(I.Name().endsWith(" (Copy)")))
						I.setName(I.Name().substring(0,I.Name().length()-7));
					V.add(I);
				}
			}
		}
		
		if(shop.isSold(ShopKeeper.DEAL_SHIPSELLER))
		{
			final PlayerStats pStats = buyer.playerStats();
			if((pStats != null)&&(pStats.getExtItems()!=null))
				this.addShipProperty(buyer, V, pStats.getExtItems());
		}
		if(shop.isSold(ShopKeeper.DEAL_CSHIPSELLER))
		{
			buyerClanPair=CMLib.clans().findPrivilegedClan(buyer, Clan.Function.PROPERTY_OWNER);
			if((buyerClanPair != null)&&(buyerClanPair.first!=null)&&(buyerClanPair.first.getExtItems()!=null))
				this.addShipProperty(buyer, V, buyerClanPair.first.getExtItems());
		}
		
		if(V.size()<2)
			return V;
		final Vector<Environmental> V2=new Vector<Environmental>(V.size());
		LandTitle L=null;
		LandTitle L2=null;
		int x=-1;
		int x2=-1;
		while(V.size()>0)
		{
			if(((!(V.get(0) instanceof LandTitle)))
			||((x=(L=(LandTitle)V.get(0)).landPropertyID().lastIndexOf('#'))<0))
			{
				if(V2.size()==0)
					V2.addElement(V.remove(0));
				else
					V2.insertElementAt(V.remove(0),0);
			}
			else
			{
				int lowest=CMath.s_int(L.landPropertyID().substring(x+1).trim());
				int chk=0;
				for(int v=1;v<V.size();v++)
				{
					if(V.get(v) instanceof LandTitle)
					{
						L2=(LandTitle)V.get(v);
						x2=L2.landPropertyID().lastIndexOf('#');
						if(x2>0)
						{
							chk=CMath.s_int(L2.landPropertyID().substring(x2+1).trim());
							if(chk<lowest)
							{
								lowest=chk;
								L=L2;
							}
						}
					}
				}
				V.remove(L);
				V2.addElement(L);
			}
		}
		return V2;
	}

	@Override
	public boolean ignoreIfNecessary(MOB mob, String ignoreMask, MOB whoIgnores)
	{
		if((ignoreMask.length()>0)&&(!CMLib.masking().maskCheck(ignoreMask,mob,false)))
		{
			mob.tell(whoIgnores,null,null,L("<S-NAME> appear(s) to be ignoring you."));
			return false;
		}
		return true;
	}

	@Override
	public String storeKeeperString(CoffeeShop shop)
	{
		if(shop==null)
			return "";
		if(shop.isSold(ShopKeeper.DEAL_ANYTHING))
			return L("*Anything*");

		final Vector<String> V=new Vector<String>();
		for(int d=1;d<ShopKeeper.DEAL_DESCS.length;d++)
		{
			if(shop.isSold(d))
			{
				switch(d)
				{
				case ShopKeeper.DEAL_GENERAL:
					V.addElement(L("General items"));
					break;
				case ShopKeeper.DEAL_ARMOR:
					V.addElement(L("Armor"));
					break;
				case ShopKeeper.DEAL_MAGIC:
					V.addElement(L("Miscellaneous Magic Items"));
					break;
				case ShopKeeper.DEAL_WEAPONS:
					V.addElement(L("Weapons"));
					break;
				case ShopKeeper.DEAL_PETS:
					V.addElement(L("Pets and Animals"));
					break;
				case ShopKeeper.DEAL_LEATHER:
					V.addElement(L("Leather"));
					break;
				case ShopKeeper.DEAL_INVENTORYONLY:
					V.addElement(L("Only my Inventory"));
					break;
				case ShopKeeper.DEAL_TRAINER:
					V.addElement(L("Training in skills/spells/prayers/songs"));
					break;
				case ShopKeeper.DEAL_CASTER:
					V.addElement(L("Caster of spells/prayers"));
					break;
				case ShopKeeper.DEAL_ALCHEMIST:
					V.addElement(L("Potions"));
					break;
				case ShopKeeper.DEAL_INNKEEPER:
					V.addElement(L("My services as an Inn Keeper"));
					break;
				case ShopKeeper.DEAL_JEWELLER:
					V.addElement(L("Precious stones and jewellery"));
					break;
				case ShopKeeper.DEAL_BANKER:
					V.addElement(L("My services as a Banker"));
					break;
				case ShopKeeper.DEAL_CLANBANKER:
					V.addElement(L("My services as a Banker to Clans"));
					break;
				case ShopKeeper.DEAL_LANDSELLER:
					V.addElement(L("Real estate"));
					break;
				case ShopKeeper.DEAL_CLANDSELLER:
					V.addElement(L("Clan estates"));
					break;
				case ShopKeeper.DEAL_ANYTECHNOLOGY:
					V.addElement(L("Any technology"));
					break;
				case ShopKeeper.DEAL_BUTCHER:
					V.addElement(L("Meats"));
					break;
				case ShopKeeper.DEAL_FOODSELLER:
					V.addElement(L("Foodstuff"));
					break;
				case ShopKeeper.DEAL_GROWER:
					V.addElement(L("Vegetables"));
					break;
				case ShopKeeper.DEAL_HIDESELLER:
					V.addElement(L("Hides and Furs"));
					break;
				case ShopKeeper.DEAL_LUMBERER:
					V.addElement(L("Lumber"));
					break;
				case ShopKeeper.DEAL_METALSMITH:
					V.addElement(L("Metal ores"));
					break;
				case ShopKeeper.DEAL_STONEYARDER:
					V.addElement(L("Stone and rock"));
					break;
				case ShopKeeper.DEAL_SHIPSELLER:
					V.addElement(L("Ships"));
					break;
				case ShopKeeper.DEAL_CSHIPSELLER:
					V.addElement(L("Clan Ships"));
					break;
				case ShopKeeper.DEAL_SLAVES:
					V.addElement(L("Slaves"));
					break;
				case ShopKeeper.DEAL_POSTMAN:
					V.addElement(L("My services as a Postman"));
					break;
				case ShopKeeper.DEAL_CLANPOSTMAN:
					V.addElement(L("My services as a Postman for Clans"));
					break;
				case ShopKeeper.DEAL_AUCTIONEER:
					V.addElement(L("My services as an Auctioneer"));
					break;
				default:
					V.addElement(L("... I have no idea WHAT I sell"));
					break;
				}
			}
		}
		return CMParms.toListString(V);
	}

	protected boolean shopKeeperItemTypeCheck(Environmental E, int dealCode, ShopKeeper shopKeeper)
	{
		switch(dealCode)
		{
		case ShopKeeper.DEAL_ANYTHING:
			return !(E instanceof LandTitle);
		case ShopKeeper.DEAL_ARMOR:
			return (E instanceof Armor);
		case ShopKeeper.DEAL_MAGIC:
			return (E instanceof MiscMagic);
		case ShopKeeper.DEAL_WEAPONS:
			return (E instanceof Weapon)||(E instanceof Ammunition);
		case ShopKeeper.DEAL_GENERAL:
			return ((E instanceof Item)
					&&(!(E instanceof Armor))
					&&(!(E instanceof MiscMagic))
					&&(!(E instanceof ClanItem))
					&&(!(E instanceof Weapon))
					&&(!(E instanceof Ammunition))
					&&(!(E instanceof MOB))
					&&(!(E instanceof LandTitle))
					&&(!(E instanceof BoardableShip))
					&&(!(E instanceof RawMaterial))
					&&(!(E instanceof Ability)));
		case ShopKeeper.DEAL_LEATHER:
			return ((E instanceof Item)
					&&((((Item)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_LEATHER)
					&&(!(E instanceof RawMaterial)));
		case ShopKeeper.DEAL_PETS:
			return ((E instanceof MOB)&&(CMLib.flags().isAnimalIntelligence((MOB)E)));
		case ShopKeeper.DEAL_SLAVES:
			return ((E instanceof MOB)&&(!CMLib.flags().isAnimalIntelligence((MOB)E)));
		case ShopKeeper.DEAL_INVENTORYONLY:
			return (shopKeeper.getShop().inEnumerableInventory(E));
		case ShopKeeper.DEAL_INNKEEPER:
			return E instanceof InnKey;
		case ShopKeeper.DEAL_JEWELLER:
			return ((E instanceof Item)
					&&(!(E instanceof Weapon))
					&&(!(E instanceof MiscMagic))
					&&(!(E instanceof ClanItem))
					&&(((((Item)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_GLASS)
					||((((Item)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_PRECIOUS)
					||((Item)E).fitsOn(Wearable.WORN_EARS)
					||((Item)E).fitsOn(Wearable.WORN_NECK)
					||((Item)E).fitsOn(Wearable.WORN_RIGHT_FINGER)
					||((Item)E).fitsOn(Wearable.WORN_LEFT_FINGER)));
		case ShopKeeper.DEAL_ALCHEMIST:
			return (E instanceof Potion);
		case ShopKeeper.DEAL_LANDSELLER:
		case ShopKeeper.DEAL_CLANDSELLER:
			return ((E instanceof LandTitle)&&(CMLib.map().getShip(((LandTitle)E).landPropertyID())==null));
		case ShopKeeper.DEAL_SHIPSELLER:
		case ShopKeeper.DEAL_CSHIPSELLER:
			return ((E instanceof BoardableShip)
					||((E instanceof LandTitle)&&(CMLib.map().getShip(((LandTitle)E).landPropertyID())!=null)));
		case ShopKeeper.DEAL_ANYTECHNOLOGY:
			return (E instanceof Electronics);
		case ShopKeeper.DEAL_BUTCHER:
			return ((E instanceof RawMaterial)
				&&(((RawMaterial)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_FLESH);
		case ShopKeeper.DEAL_FOODSELLER:
			return (((E instanceof Food)||(E instanceof Drink))
					&&(!(E instanceof RawMaterial)));
		case ShopKeeper.DEAL_GROWER:
			return ((E instanceof RawMaterial)
				&&(((RawMaterial)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_VEGETATION);
		case ShopKeeper.DEAL_HIDESELLER:
			return ((E instanceof RawMaterial)
				&&((((RawMaterial)E).material()==RawMaterial.RESOURCE_HIDE)
				||(((RawMaterial)E).material()==RawMaterial.RESOURCE_FEATHERS)
				||(((RawMaterial)E).material()==RawMaterial.RESOURCE_LEATHER)
				||(((RawMaterial)E).material()==RawMaterial.RESOURCE_SCALES)
				||(((RawMaterial)E).material()==RawMaterial.RESOURCE_WOOL)
				||(((RawMaterial)E).material()==RawMaterial.RESOURCE_FUR)));
		case ShopKeeper.DEAL_LUMBERER:
			return ((E instanceof RawMaterial)
				&&((((RawMaterial)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_WOODEN));
		case ShopKeeper.DEAL_METALSMITH:
			return ((E instanceof RawMaterial)
				&&(((((RawMaterial)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_METAL)
				||(((RawMaterial)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_MITHRIL));
		case ShopKeeper.DEAL_STONEYARDER:
			return ((E instanceof RawMaterial)
				&&((((RawMaterial)E).material()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_ROCK));
		}
		return false;
	}

	@Override
	public boolean doISellThis(Environmental thisThang, ShopKeeper shop)
	{
		if(thisThang instanceof PackagedItems)
			thisThang=((PackagedItems)thisThang).peekFirstItem();
		if(thisThang==null)
			return false;
		if((thisThang instanceof Coins)
		||(thisThang instanceof DeadBody)
		||(CMLib.flags().isChild(thisThang)))
			return false;
		if(shop.isSold(ShopKeeper.DEAL_ANYTHING))
			return !(thisThang instanceof LandTitle);
		else
		{
			for(int d=1;d<ShopKeeper.DEAL_DESCS.length;d++)
			{
				if(shop.isSold(d) && shopKeeperItemTypeCheck(thisThang,d,shop))
					return true;
			}
		}
		return false;
	}

	@Override
	public void returnMoney(MOB to, String currency, double amt)
	{
		if(amt>0)
			CMLib.beanCounter().giveSomeoneMoney(to, currency, amt);
		else
			CMLib.beanCounter().subtractMoney(to, currency,-amt);
		if(amt!=0)
		{
			if(!CMLib.flags().isInTheGame(to,true))
				CMLib.database().DBUpdatePlayerItems(to);
		}
	}

	@Override
	public String[] bid(MOB mob, double bid, String bidCurrency, AuctionData auctionData, Item I, List<String> auctionAnnounces)
	{
		String bidWords=CMLib.beanCounter().nameCurrencyShort(auctionData.getCurrency(),auctionData.getBid());
		final String currencyName=CMLib.beanCounter().getDenominationName(auctionData.getCurrency());
		if(bid==0.0)
			return new String[]{"Up for auction: "+I.name()+".  The current bid is "+bidWords+".",null};

		if(!bidCurrency.equals(auctionData.getCurrency()))
			return new String[]{"This auction is being bid in "+currencyName+" only.",null};

		if(bid>CMLib.beanCounter().getTotalAbsoluteValue(mob,auctionData.getCurrency()))
			return new String[]{"You don't have enough "+currencyName+" on hand to cover that bid.",null};

		if((bid<auctionData.getBid())||(bid==0))
		{
			final String bwords=CMLib.beanCounter().nameCurrencyShort(bidCurrency, bid);
			return new String[]{"Your bid of "+bwords+" is insufficient."+((auctionData.getBid()>0)?" The current high bid is "+bidWords+".":""),null};
		}
		else
		if((bid>auctionData.getHighBid())||((bid>auctionData.getBid())&&(auctionData.getHighBid()==0)))
		{
			final MOB oldHighBider=auctionData.getHighBidderMob();
			if(auctionData.getHighBidderMob()!=null)
				returnMoney(auctionData.getHighBidderMob(),auctionData.getCurrency(),auctionData.getHighBid());
			auctionData.setHighBidderMob(mob);
			if(auctionData.getHighBid()<=0.0)
			{
				if(auctionData.getBid()>0)
					auctionData.setHighBid(auctionData.getBid());
				else
					auctionData.setHighBid(0.0);
			}
			auctionData.setBid(auctionData.getHighBid()+1.0);
			auctionData.setHighBid(bid);
			returnMoney(auctionData.getHighBidderMob(),auctionData.getCurrency(),-bid);
			bidWords=CMLib.beanCounter().nameCurrencyShort(auctionData.getCurrency(),auctionData.getBid());
			final String yourBidWords = CMLib.beanCounter().abbreviatedPrice(currencyName, auctionData.getHighBid());
			auctionAnnounces.add("A new bid has been entered for "+I.name()+". The current high bid is "+bidWords+".");
			if((oldHighBider!=null)&&(oldHighBider==mob))
				return new String[]{"You have submitted a new high bid of "+yourBidWords+" for "+I.name()+".",null};
			else
			if((oldHighBider!=null)&&(oldHighBider!=mob))
				return new String[]{"You have the new high reserve bid of "+yourBidWords+" for "+I.name()+". The current nominal high bid is "+bidWords+".","You have been outbid for "+I.name()+"."};
			else
				return new String[]{"You have submitted a bid of "+yourBidWords+" for "+I.name()+".",null};
		}
		else
		if((bid==auctionData.getBid())&&(auctionData.getHighBidderMob()!=null))
		{
			return new String[]{"You must bid higher than "+bidWords+" to have your bid accepted.",null};
		}
		else
		if((bid==auctionData.getHighBid())&&(auctionData.getHighBidderMob()!=null))
		{
			if((auctionData.getHighBidderMob()!=null)&&(auctionData.getHighBidderMob()!=mob))
			{
				auctionData.setBid(bid);
				bidWords=CMLib.beanCounter().nameCurrencyShort(auctionData.getCurrency(),auctionData.getBid());
				auctionAnnounces.add("A new bid has been entered for "+I.name()+". The current bid is "+bidWords+".");
				return new String[]{"You have been outbid by proxy for "+I.name()+".","Your high bid for "+I.name()+" has been reached."};
			}
		}
		else
		{
			auctionData.setBid(bid);
			bidWords=CMLib.beanCounter().nameCurrencyShort(auctionData.getCurrency(),auctionData.getBid());
			auctionAnnounces.add("A new bid has been entered for "+I.name()+". The current bid is "+bidWords+".");
			return new String[]{"You have been outbid by proxy for "+I.name()+".",null};
		}
		return null;
	}

	@Override
	public AuctionData getEnumeratedAuction(String named, String auctionHouse)
	{
		final List<AuctionData> V=getAuctions(null,auctionHouse);
		final Vector<Item> V2=new Vector<Item>();
		for(int v=0;v<V.size();v++)
			V2.addElement(V.get(v).getAuctionedItem());
		Environmental E=CMLib.english().fetchEnvironmental(V2,named,true);
		if(!(E instanceof Item))
			E=CMLib.english().fetchEnvironmental(V2,named,false);
		if(E!=null)
		for(int v=0;v<V.size();v++)
		{
			if(V.get(v).getAuctionedItem()==E)
				return V.get(v);
		}
		return null;
	}

	@Override
	public void saveAuction(AuctionData data, String auctionHouse, boolean updateOnly)
	{
		if(data.getAuctionedItem() instanceof Container)
			((Container)data.getAuctionedItem()).emptyPlease(false);
		final StringBuilder xml=new StringBuilder("<AUCTION>");
		xml.append("<PRICE>"+data.getBid()+"</PRICE>");
		xml.append("<BUYOUT>"+data.getBuyOutPrice()+"</BUYOUT>");
		if(data.getHighBidderMob()!=null)
			xml.append("<BIDDER>"+data.getHighBidderMob().Name()+"</BIDDER>");
		else
			xml.append("<BIDDER />");
		xml.append("<MAXBID>"+data.getHighBid()+"</MAXBID>");
		xml.append("<AUCTIONITEM>");
		xml.append(CMLib.coffeeMaker().getItemXML(data.getAuctionedItem()).toString());
		xml.append("</AUCTIONITEM>");
		xml.append("</AUCTION>");
		if(!updateOnly)
		{
			CMLib.database().DBWriteJournal("SYSTEM_AUCTIONS_"+auctionHouse.toUpperCase().trim(),
											data.getAuctioningMob().Name(),
											""+data.getAuctionTickDown(),
											CMStrings.limit(data.getAuctionedItem().name(),38),
											xml.toString());
		}
		else
			CMLib.database().DBUpdateJournal(data.getAuctionDBKey(), data.getAuctionedItem().Name(),xml.toString(), 0);
	}

	@Override
	public List<AuctionData> getAuctions(Object ofLike, String auctionHouse)
	{
		final Vector<AuctionData> auctions=new Vector<AuctionData>();
		final String house="SYSTEM_AUCTIONS_"+auctionHouse.toUpperCase().trim();
		final List<JournalEntry> otherAuctions=CMLib.database().DBReadJournalMsgsByUpdateDate(house, true);
		for(int o=0;o<otherAuctions.size();o++)
		{
			final JournalEntry auctionData=otherAuctions.get(o);
			final String from=auctionData.from();
			final String to=auctionData.to();
			final String key=auctionData.key();
			if((ofLike instanceof MOB)&&(!((MOB)ofLike).Name().equals(to)))
				continue;
			if((ofLike instanceof String)&&(!((String)ofLike).equals(key)))
				continue;
			final AuctionData data=(AuctionData)CMClass.getCommon("DefaultAuction");
			data.setStartTime(auctionData.date());
			data.setAuctionTickDown(CMath.s_long(to));
			final String xml=auctionData.msg();
			List<XMLLibrary.XMLTag> xmlV=CMLib.xml().parseAllXML(xml);
			xmlV=CMLib.xml().getContentsFromPieces(xmlV,"AUCTION");
			final String bid=CMLib.xml().getValFromPieces(xmlV,"PRICE");
			final double oldBid=CMath.s_double(bid);
			data.setBid(oldBid);
			final String highBidder=CMLib.xml().getValFromPieces(xmlV,"BIDDER");
			if(highBidder.length()>0)
				data.setHighBidderMob(CMLib.players().getLoadPlayer(highBidder));
			final String maxBid=CMLib.xml().getValFromPieces(xmlV,"MAXBID");
			final double oldMaxBid=CMath.s_double(maxBid);
			data.setHighBid(oldMaxBid);
			data.setAuctionDBKey(key);
			final String buyOutPrice=CMLib.xml().getValFromPieces(xmlV,"BUYOUT");
			data.setBuyOutPrice(CMath.s_double(buyOutPrice));
			data.setAuctioningMob(CMLib.players().getLoadPlayer(from));
			data.setCurrency(CMLib.beanCounter().getCurrency(data.getAuctioningMob()));
			for(int v=0;v<xmlV.size();v++)
			{
				final XMLTag X=xmlV.get(v);
				if(X.tag().equalsIgnoreCase("AUCTIONITEM"))
				{
					data.setAuctionedItem(CMLib.coffeeMaker().getItemFromXML(X.value()));
					break;
				}
			}
			if((ofLike instanceof Item)&&(!((Item)ofLike).sameAs(data.getAuctionedItem())))
				continue;
			auctions.addElement(data);
		}
		return auctions;
	}

	@Override
	public String getListForMask(String targetMessage)
	{
		if(targetMessage==null)
			return null;
		final int x=targetMessage.toUpperCase().lastIndexOf("FOR '");
		if(x>0)
		{
			final int y=targetMessage.lastIndexOf('\'');
			if(y>x)
				return targetMessage.substring(x+5,y);
		}
		return null;
	}

	@Override
	public String getAuctionInventory(MOB seller, MOB buyer, Auctioneer auction, String mask)
	{
		final StringBuilder str=new StringBuilder("");
		str.append("^x"+CMStrings.padRight(L("Lvl"),3)+" "+CMStrings.padRight(L("Item"),50)+" "+CMStrings.padRight(L("Days"),4)+" ["+CMStrings.padRight(L("Bid"),6)+"] Buy^.^N\n\r");
		final List<AuctionData> auctions=getAuctions(null,auction.auctionHouse());
		for(int v=0;v<auctions.size();v++)
		{
			final AuctionData data=auctions.get(v);
			if(shownInInventory(seller,buyer,data.getAuctionedItem(),auction))
			{
				if(((mask==null)||(mask.length()==0)||(CMLib.english().containsString(data.getAuctionedItem().name(),mask)))
				&&((data.getAuctionTickDown()>System.currentTimeMillis())||(data.getAuctioningMob()==buyer)||(data.getHighBidderMob()==buyer)))
				{
					Area area=CMLib.map().getStartArea(seller);
					if(area==null)
						area=CMLib.map().getStartArea(buyer);
					str.append(CMStrings.padRight(""+data.getAuctionedItem().phyStats().level(),3)+" ");
					str.append(CMStrings.padRight(data.getAuctionedItem().name(),50)+" ");
					if(data.getAuctionTickDown()>System.currentTimeMillis())
					{
						final long days=data.daysRemaining(buyer,seller);
						str.append(CMStrings.padRight(""+days,4)+" ");
					}
					else
					if(data.getAuctioningMob()==buyer)
						str.append("DONE ");
					else
						str.append("WON! ");
					str.append("["+CMStrings.padRight(CMLib.beanCounter().abbreviatedPrice(seller,data.getBid()),6)+"] ");
					if(data.getBuyOutPrice()<=0.0)
						str.append(CMStrings.padRight("-",6));
					else
						str.append(CMStrings.padRight(CMLib.beanCounter().abbreviatedPrice(seller,data.getBuyOutPrice()),6));
					str.append("\n\r");
				}
			}
		}
		return "\n\r"+str.toString();
	}

	@Override
	public void auctionNotify(MOB M, String resp, String regardingItem)
	{
		try
		{
			if(CMLib.flags().isInTheGame(M,true))
				M.tell(resp);
			else
			if(M.playerStats()!=null)
			{
				CMLib.smtp().emailIfPossible(CMProps.getVar(CMProps.Str.SMTPSERVERNAME),
											"auction@"+CMProps.getVar(CMProps.Str.MUDDOMAIN).toLowerCase(),
											"noreply@"+CMProps.getVar(CMProps.Str.MUDDOMAIN).toLowerCase(),
											M.playerStats().getEmail(),
											"Auction Update for item: "+regardingItem,
											resp);
			}
		}
		catch (final Exception e)
		{
		}
	}

	@Override
	public void cancelAuction(String auctionHouse, AuctionData data)
	{
		if(data.getAuctionedItem()!=null)
		{
			if(data.getAuctioningMob()!=null)
				data.getAuctioningMob().moveItemTo(data.getAuctionedItem());
			if(data.getHighBidderMob()!=null)
			{
				final MOB M=data.getHighBidderMob();
				auctionNotify(M,"The auction for "+data.getAuctionedItem().Name()+" was closed early.  You have been refunded your max bid.",data.getAuctionedItem().Name());
				CMLib.coffeeShops().returnMoney(M,data.getCurrency(),data.getHighBid());
			}
		}
		CMLib.database().DBDeleteJournal(auctionHouse, data.getAuctionDBKey());
		if(data.getAuctionedItem()!=null)
			data.getAuctioningMob().tell(L("Auction ended."));
	}
}