/
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/Specializations/
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/
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/BasicTech/
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 java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import com.planet_ink.coffee_web.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.AbilityMapper.AbilityMapping;
import com.planet_ink.coffee_mud.Libraries.interfaces.AbilityParameters.AbilityParmEditor;
import com.planet_ink.coffee_mud.core.exceptions.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.Abilities.Common.CommonSkill;
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.AbilityComponent.CompLocation;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.RawMaterial.Material;
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 java.net.Socket;
import java.util.*;

/*
   Copyright 2015-2019 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 CMAbleComps extends StdLibrary implements AbilityComponents
{
	@Override
	public String ID()
	{
		return "CMAbleComps";
	}

	protected final boolean isRightMaterial(final long type, final long itemMaterial, final boolean mithrilOK)
	{
		if(itemMaterial == type)
			return true;
		if((mithrilOK) && (type == RawMaterial.MATERIAL_METAL))
		{
			if(itemMaterial==RawMaterial.MATERIAL_MITHRIL)
				return true;
		}
		return false;
	}

	protected Item makeItemComponent(final AbilityComponent comp, final boolean mithrilOK)
	{
		if(comp.getType()==AbilityComponent.CompType.STRING)
			return null;
		else
		if(comp.getType()==AbilityComponent.CompType.RESOURCE)
			return CMLib.materials().makeItemResource((int)comp.getLongType(), comp.getSubType());
		else
		if(comp.getType()==AbilityComponent.CompType.MATERIAL)
			return CMLib.materials().makeItemResource(RawMaterial.CODES.MOST_FREQUENT(((int)comp.getLongType())&RawMaterial.MATERIAL_MASK), comp.getSubType());
		return null;
	}

	protected boolean IsItemComponent(final MOB mob, final AbilityComponent comp, final int[] amt, Item I, final List<Object> thisSet, final boolean mithrilOK)
	{
		if(I==null)
			return false;
		Item container=null;
		if((comp.getType()==AbilityComponent.CompType.STRING)
		&&(!CMLib.english().containsString(I.name(),comp.getStringType())))
			return false;
		else
		if((comp.getType()==AbilityComponent.CompType.RESOURCE)
		&&((!(I instanceof RawMaterial))
			||(I.material()!=comp.getLongType())
			||((comp.getSubType().length()>0)&&(!((RawMaterial)I).getSubType().equalsIgnoreCase(comp.getSubType())))))
			return false;
		else
		if((comp.getType()==AbilityComponent.CompType.MATERIAL)
		&&((!(I instanceof RawMaterial))
			||(!isRightMaterial(comp.getLongType(),I.material()&RawMaterial.MATERIAL_MASK,mithrilOK))
			||((comp.getSubType().length()>0)&&(!((RawMaterial)I).getSubType().equalsIgnoreCase(comp.getSubType())))))
			return false;
		container=I.ultimateContainer(null);
		if(container==null)
			container=I;
		switch(comp.getLocation())
		{
		case INVENTORY:
			if((container.owner() instanceof Room)||(!container.amWearingAt(Wearable.IN_INVENTORY)))
				return false;
			break;
		case HELD:
			if((container.owner() instanceof Room)||(!container.amWearingAt(Wearable.WORN_HELD)))
				return false;
			break;
		case WORN:
			if((container.owner() instanceof Room)||(container.amWearingAt(Wearable.IN_INVENTORY)))
				return false;
			break;
		default:
		case NEARBY:
			if(!CMLib.flags().canBeSeenBy(container, mob))
				return false;
			break;
		case ONGROUND:
			if((!(container.owner() instanceof Room))||(!CMLib.flags().canBeSeenBy(container, mob)))
				return false;
			break;
		}
		if((comp.getType()!=AbilityComponent.CompType.STRING)
		&&(CMLib.flags().isOnFire(I)||CMLib.flags().isEnchanted(I)))
			return false;
		if(comp.getType()==AbilityComponent.CompType.STRING)
		{
			if(I instanceof PackagedItems)
				I=(Item)CMLib.materials().unbundle(I,amt[0],null);
			amt[0]-=I.numberOfItems();
		}
		else
		if(I.phyStats().weight()>amt[0])
		{
			I=(Item)CMLib.materials().splitBundle(I,amt[0],null);
			if(I==null)
				return false;
			amt[0]=amt[0]-I.phyStats().weight();
		}
		else
			amt[0]=amt[0]-I.phyStats().weight();
		thisSet.add(I);

		if(amt[0]<=0)
		{
			if(thisSet.size()>0)
				thisSet.add(Boolean.valueOf(comp.isConsumed()));
			return true;
		}
		return false;
	}

	// returns list of components for the requirement list.
	@Override
	public List<Item> componentsSample(final List<AbilityComponent> req, final boolean mithrilOK)
	{
		if((req==null)||(req.size()==0))
			return new Vector<Item>(0);
		final List<Item> passes=new Vector<Item>();
		AbilityComponent comp = null;
		for(int i=0;i<req.size();i++)
		{
			comp=req.get(i);
			final Item I=this.makeItemComponent(comp,mithrilOK);
			passes.add(I);
		}
		return passes;
	}

	// returns list of components found if all good, returns Integer of bad row if not.
	@Override
	public List<Object> componentCheck(final MOB mob, final List<AbilityComponent> req, final boolean mithrilOK)
	{
		if((mob==null)||(req==null)||(req.size()==0))
			return new Vector<Object>(0);
		boolean currentAND=false;
		boolean previousValue=true;
		final int[] amt={0};
		final List<Object> passes=new Vector<Object>();
		final List<Object> thisSet=new ArrayList<Object>();
		boolean found=false;
		AbilityComponent comp = null;
		final Room room = mob.location();
		for(int i=0;i<req.size();i++)
		{
			comp=req.get(i);
			currentAND=comp.getConnector()==AbilityComponent.CompConnector.AND;
			if(previousValue&&(!currentAND))
				return passes;
			if((!previousValue)&&currentAND)
				return null;

			// if they fail the zappermask, its like the req is NOT even there...
			if((comp.getCompiledMask()!=null)
			&&(!CMLib.masking().maskCheck(comp.getCompiledMask(),mob,true)))
				continue;
			amt[0]=comp.getAmount();
			thisSet.clear();
			found=false;
			if(comp.getLocation()!=CompLocation.ONGROUND)
			{
				for(int ii=0;ii<mob.numItems();ii++)
				{
					found=IsItemComponent(mob, comp, amt, mob.getItem(ii), thisSet,mithrilOK);
					if(found)
						break;
				}
			}
			if((!found)
			&&(room!=null)
			&&((comp.getLocation()==CompLocation.ONGROUND)||(comp.getLocation()==CompLocation.NEARBY)))
			{
				for(int ii=0;ii<room.numItems();ii++)
				{
					found=IsItemComponent(mob, comp, amt, room.getItem(ii), thisSet,mithrilOK);
					if(found)
						break;
				}
			}
			if((amt[0]>0)&&(currentAND)&&(i>0))
				return null;
			previousValue=amt[0]<=0;
			if(previousValue)
				passes.addAll(thisSet);
		}
		if(passes.size()==0)
			return null;
		return passes;
	}

	@Override
	public List<AbilityComponent> getAbilityComponents(final String AID)
	{
		return getAbilityComponentMap().get(AID.toUpperCase().trim());
	}

	protected List<PairList<String,String>> getAbilityComponentCodedPairsList(final String AID)
	{
		return getAbilityComponentCodedListLists(getAbilityComponents(AID));
	}

	@Override
	public PairList<String,String> getAbilityComponentCoded(final AbilityComponent comp)
	{
		final PairList<String,String> curr=new PairVector<String,String>();
		String itemDesc=null;
		curr.add("ANDOR",comp.getConnector()==AbilityComponent.CompConnector.AND?"&&":"||");
		if(comp.getLocation()==AbilityComponent.CompLocation.HELD)
			curr.add("DISPOSITION","held");
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.WORN)
			curr.add("DISPOSITION","worn");
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.NEARBY)
			curr.add("DISPOSITION","nearby");
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.ONGROUND)
			curr.add("DISPOSITION","onground");
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.INVENTORY)
			curr.add("DISPOSITION","inventory");

		if(comp.isConsumed())
			curr.add("FATE","consumed");
		else
			curr.add("FATE","kept");
		curr.add("AMOUNT",""+comp.getAmount());
		if(comp.getType()==AbilityComponent.CompType.STRING)
			itemDesc=comp.getStringType();
		else
		if(comp.getType()==AbilityComponent.CompType.MATERIAL)
			itemDesc=RawMaterial.Material.findByMask((int)comp.getLongType()).desc().toUpperCase();
		else
		if(comp.getType()==AbilityComponent.CompType.RESOURCE)
			itemDesc=RawMaterial.CODES.NAME((int)comp.getLongType()).toUpperCase();
		curr.add("COMPONENTID",itemDesc);
		curr.add("SUBTYPE",comp.getSubType());
		curr.add("MASK",comp.getMaskStr());
		return curr;
	}

	@Override
	public void setAbilityComponentCodedFromCodedPairs(final PairList<String,String> decodedDV, final AbilityComponent comp)
	{
		final String[] s=new String[7];
		for(int i=0;i<7;i++)
			s[i]=decodedDV.get(i).second;
		if(s[0].equalsIgnoreCase("||"))
			comp.setConnector(AbilityComponent.CompConnector.OR);
		else
			comp.setConnector(AbilityComponent.CompConnector.AND);
		if(s[1].equalsIgnoreCase("held"))
			comp.setLocation(AbilityComponent.CompLocation.HELD);
		else
		if(s[1].equalsIgnoreCase("worn"))
			comp.setLocation(AbilityComponent.CompLocation.WORN);
		else
		if(s[1].equalsIgnoreCase("nearby"))
			comp.setLocation(AbilityComponent.CompLocation.NEARBY);
		else
		if(s[1].equalsIgnoreCase("onground"))
			comp.setLocation(AbilityComponent.CompLocation.ONGROUND);
		else
			comp.setLocation(AbilityComponent.CompLocation.INVENTORY);
		if(s[2].equalsIgnoreCase("consumed"))
			comp.setConsumed(true);
		else
			comp.setConsumed(false);
		comp.setAmount(CMath.s_int(s[3]));
		final String compType=s[4];
		final String subType=s[5];
		int depth=CMLib.materials().getResourceCode(compType,false);
		if(depth>=0)
			comp.setType(AbilityComponent.CompType.RESOURCE, Integer.valueOf(depth), subType);
		else
		{
			depth=CMLib.materials().getMaterialCode(compType,false);
			if(depth>=0)
				comp.setType(AbilityComponent.CompType.MATERIAL, Integer.valueOf(depth), subType);
			else
				comp.setType(AbilityComponent.CompType.STRING, s[4].toUpperCase().trim(), "");
		}
		comp.setMask(s[6]);
	}

	protected List<PairList<String,String>> getAbilityComponentCodedListLists(final List<AbilityComponent> req)
	{
		if(req==null)
			return null;
		final List<PairList<String,String>> V=new Vector<PairList<String,String>>();
		for(final AbilityComponent comp : req)
			V.add(getAbilityComponentCoded(comp));
		return V;
	}

	@Override
	public AbilityComponent createBlankAbilityComponent()
	{
		final AbilityComponent comp = (AbilityComponent)CMClass.getCommon("DefaultAbilityComponent");
		comp.setConnector(AbilityComponent.CompConnector.AND);
		comp.setLocation(AbilityComponent.CompLocation.INVENTORY);
		comp.setConsumed(false);
		comp.setAmount(1);
		comp.setType(AbilityComponent.CompType.STRING, "resource-material-item name", "");
		comp.setMask("");
		return comp;
	}

	@Override
	public String getAbilityComponentCodedString(final List<AbilityComponent> comps)
	{
		return getAbilityComponentCodedStringFromCodedList(getAbilityComponentCodedListLists(comps));
	}

	protected String getAbilityComponentCodedStringFromCodedList(final List<PairList<String,String>> comps)
	{
		final StringBuilder buf=new StringBuilder("");
		PairList<String,String> curr=null;
		for(int c=0;c<comps.size();c++)
		{
			curr=comps.get(c);
			if(curr==null)
				continue;
			if(c>0)
				buf.append(curr.get(0).second);
			buf.append("(");
			buf.append(curr.get(1).second);
			buf.append(":");
			buf.append(curr.get(2).second);
			buf.append(":");
			buf.append(curr.get(3).second);
			buf.append(":");
			buf.append(curr.get(4).second);
			if(curr.get(5).second.toString().length()>0)
				buf.append("(").append(curr.get(5).second).append(")");
			buf.append(":");
			buf.append(curr.get(6).second);
			buf.append(")");
		}
		return buf.toString();
	}

	@Override
	public String getAbilityComponentCodedString(final String AID)
	{
		final StringBuffer buf=new StringBuffer("");
		final List<PairList<String,String>> comps=getAbilityComponentCodedPairsList(AID);
		buf.append(getAbilityComponentCodedStringFromCodedList(comps));
		return AID+"="+buf.toString();
	}

	@Override
	public String getAbilityComponentDesc(final MOB mob, final AbilityComponent comp, final boolean useConnector)
	{
		int amt=0;
		String itemDesc=null;
		final StringBuffer buf=new StringBuffer("");
		if(useConnector)
			buf.append(comp.getConnector()==AbilityComponent.CompConnector.AND?", and ":", or ");
		if((mob!=null)
		&&(comp.getCompiledMask()!=null)
		&&(!CMLib.masking().maskCheck(comp.getCompiledMask(),mob,true)))
			return "";
		if(mob==null)
		{
			if(comp.getCompiledMask()!=null)
				buf.append("MASK: "+comp.getMaskStr()+": ");
		}
		amt=comp.getAmount();
		String subType=comp.getSubType();
		if(subType.trim().length()>0)
		{
			subType=subType.trim().toLowerCase();
			if(comp.getType()==AbilityComponent.CompType.STRING)
				itemDesc=((amt>1)?(amt+" "+CMLib.english().makePlural(comp.getStringType())):CMLib.english().startWithAorAn(comp.getStringType()));
			else
			if(comp.getType()==AbilityComponent.CompType.MATERIAL)
			{
				if(subType.indexOf(' ')>0)
					itemDesc=amt+" "+subType;
				else
					itemDesc=amt+" "+RawMaterial.Material.findByMask((int)comp.getLongType()).noun().toLowerCase()+" ("+subType+") ";
			}
			else
			if(comp.getType()==AbilityComponent.CompType.RESOURCE)
			{
				final String matName=RawMaterial.CODES.NAME((int)comp.getLongType()).toLowerCase();
				if(subType.equals(matName)
				&& (((comp.getLongType()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_CLOTH)
					||((comp.getLongType()&RawMaterial.MATERIAL_MASK)==RawMaterial.MATERIAL_PAPER)))
					itemDesc=amt+" "+subType+" bolt";
				else
					itemDesc=amt+" "+subType;
			}
		}
		else
		{
			if(comp.getType()==AbilityComponent.CompType.STRING)
				itemDesc=((amt>1)?(amt+" "+comp.getStringType()+"s"):CMLib.english().startWithAorAn(comp.getStringType()));
			else
			if(comp.getType()==AbilityComponent.CompType.MATERIAL)
				itemDesc=amt+((amt>1)?" pounds":" pound")+" of "+RawMaterial.Material.findByMask((int)comp.getLongType()).noun().toLowerCase();
			else
			if(comp.getType()==AbilityComponent.CompType.RESOURCE)
				itemDesc=amt+((amt>1)?" pounds":" pound")+" of "+RawMaterial.CODES.NAME((int)comp.getLongType()).toLowerCase();
		}
		if(comp.getLocation()==AbilityComponent.CompLocation.INVENTORY)
			buf.append(itemDesc);
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.HELD)
			buf.append(itemDesc+" held");
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.WORN)
			buf.append(L("@x1 worn or wielded",itemDesc));
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.NEARBY)
			buf.append(itemDesc+" nearby");
		else
		if(comp.getLocation()==AbilityComponent.CompLocation.ONGROUND)
			buf.append(L("@x1 on the ground",itemDesc));
		return buf.toString();
	}

	@Override
	public String getAbilityComponentDesc(final MOB mob, final String AID)
	{
		return getAbilityComponentDesc(mob,getAbilityComponents(AID));
	}

	@Override
	public String getAbilityComponentDesc(final MOB mob, final List<AbilityComponent> req)
	{
		if(req==null)
			return null;
		final StringBuffer buf=new StringBuffer("");
		for (int r = 0; r < req.size(); r++)
		{
			buf.append(getAbilityComponentDesc(mob, req.get(r), r>0));
		}
		return buf.toString();
	}

	@Override
	public String addAbilityComponent(final String s, final Map<String, List<AbilityComponent>> H)
	{
		int x=s.indexOf('=');
		if(x<0)
			return "Malformed component line (code 0): "+s;
		final String id=s.substring(0,x).toUpperCase().trim();
		String parms=s.substring(x+1);

		String parmS=null;
		String rsc=null;
		List<AbilityComponent> parm=null;
		AbilityComponent build=null;
		int depth=0;
		parm=new Vector<AbilityComponent>();
		String error=null;
		while(parms.length()>0)
		{
			build=(AbilityComponent)CMClass.getCommon("DefaultAbilityComponent");
			build.setConnector(AbilityComponent.CompConnector.AND);
			if(parms.startsWith("||"))
			{
				build.setConnector(AbilityComponent.CompConnector.OR);
				parms=parms.substring(2).trim();
			}
			else
			if(parms.startsWith("&&"))
			{
				parms = parms.substring(2).trim();
			}

			if (!parms.startsWith("("))
			{
				error = "Malformed component line (code 1): " + parms;
				break;
			}

			depth=0;
			x=1;
			for(;x<parms.length();x++)
			{
				if((parms.charAt(x)==')')&&(depth==0))
					break;
				else
				if(parms.charAt(x)=='(')
					depth++;
				else
				if(parms.charAt(x)==')')
					depth--;
			}
			if (x == parms.length())
			{
				error = "Malformed component line (code 2): " + parms;
				break;
			}
			parmS=parms.substring(1,x).trim();
			parms=parms.substring(x+1).trim();

			build.setLocation(AbilityComponent.CompLocation.INVENTORY);
			x=parmS.indexOf(':');
			if(x<0)
			{
				error="Malformed component line (code 0-1): "+parmS;
				continue;
			}
			if(parmS.substring(0,x).equalsIgnoreCase("held"))
				build.setLocation(AbilityComponent.CompLocation.HELD);
			else
			if(parmS.substring(0,x).equalsIgnoreCase("worn"))
				build.setLocation(AbilityComponent.CompLocation.WORN);
			else
			if(parmS.substring(0,x).equalsIgnoreCase("nearby"))
				build.setLocation(AbilityComponent.CompLocation.NEARBY);
			else
			if(parmS.substring(0,x).equalsIgnoreCase("onground"))
				build.setLocation(AbilityComponent.CompLocation.ONGROUND);
			else
			if((x>0)&&(!parmS.substring(0,x).equalsIgnoreCase("inventory")))
			{
				error="Malformed component line (code 0-2): "+parmS;
				continue;
			}
			parmS=parmS.substring(x+1);

			build.setConsumed(true);
			x=parmS.indexOf(':');
			if (x < 0)
			{
				error = "Malformed component line (code 1-1): " + parmS;
				continue;
			}
			if(parmS.substring(0,x).equalsIgnoreCase("kept"))
				build.setConsumed(false);
			else
			if((x>0)&&(!parmS.substring(0,x).equalsIgnoreCase("consumed")))
			{
				error="Malformed component line (code 1-2): "+parmS;
				continue;
			}
			parmS=parmS.substring(x+1);

			build.setAmount(1);
			x=parmS.indexOf(':');
			if (x < 0)
			{
				error = "Malformed component line (code 2-1): " + parmS;
				continue;
			}
			if((x>0)&&(!CMath.isInteger(parmS.substring(0,x))))
			{
				error="Malformed component line (code 2-2): "+parmS;
				continue;
			}
			if(x>0)
				build.setAmount(CMath.s_int(parmS.substring(0,x)));
			parmS=parmS.substring(x+1);

			build.setType(AbilityComponent.CompType.STRING, "", "");
			x=parmS.indexOf(':');
			if (x <= 0)
			{
				error = "Malformed component line (code 3-1): " + parmS;
				continue;
			}
			rsc=parmS.substring(0,x);
			String compType=rsc;
			String subType="";
			if(rsc.endsWith(")"))
			{
				final int y=rsc.lastIndexOf('(');
				if(y>0)
				{
					compType=rsc.substring(0, y);
					subType=rsc.substring(y+1,rsc.length()-1);
				}
			}
			depth=CMLib.materials().getResourceCode(compType,false);
			if(depth>=0)
				build.setType(AbilityComponent.CompType.RESOURCE, Long.valueOf(depth), subType);
			else
			{
				depth=CMLib.materials().getMaterialCode(compType,false);
				if(depth>=0)
					build.setType(AbilityComponent.CompType.MATERIAL, Long.valueOf(depth), subType);
				else
					build.setType(AbilityComponent.CompType.STRING, rsc.toUpperCase().trim(), "");
			}
			parmS=parmS.substring(x+1);

			build.setMask(parmS);

			parm.add(build);
		}
		if(parm instanceof Vector)
			((Vector<?>)parm).trimToSize();
		if(parm instanceof SVector)
			((SVector<?>)parm).trimToSize();
		if(error!=null)
			return error;
		if(parm.size()>0)
			H.put(id.toUpperCase(),parm);
		return null;
	}

	// format of each data entry is 1=ANDOR(B), 2=DISPO(I), 3=CONSUMED(B), 4=AMT(I), 5=MATERIAL(L)RESOURCE(I)NAME(S), 6=MASK(S)
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public Map<String, List<AbilityComponent>> getAbilityComponentMap()
	{
		Map<String, List<AbilityComponent>> H=(Map)Resources.getResource("COMPONENT_MAP");
		if(H==null)
		{
			H=new Hashtable<String,List<AbilityComponent>>();
			final StringBuffer buf=new CMFile(Resources.makeFileResourceName("skills/components.txt"),null,CMFile.FLAG_LOGERRORS).text();
			List<String> V=new Vector<String>();
			if(buf!=null)
				V=Resources.getFileLineVector(buf);
			String s=null;
			String error=null;
			if(V!=null)
			for(int v=0;v<V.size();v++)
			{
				s=V.get(v).trim();
				if(s.startsWith("#")||(s.length()==0)||s.startsWith(";")||s.startsWith(":"))
					continue;
				error=addAbilityComponent(s,H);
				if(error!=null)
					Log.errOut("CMAble",error);
			}
			Resources.submitResource("COMPONENT_MAP",H);
		}
		return H;
	}

	@Override
	public MaterialLibrary.DeadResourceRecord destroyAbilityComponents(final List<Object> found)
	{
		final MaterialLibrary.DeadResourceRecord record = new MaterialLibrary.DeadResourceRecord();
		if((found==null)||(found.size()==0))
		{
			return record;
		}
		final XVector<Ability> props=new XVector<Ability>();
		record.lostProps=props;
		while(found.size()>0)
		{
			int i=0;
			boolean destroy=false;
			for(;i<found.size();i++)
			{
				if(found.get(i) instanceof Boolean)
				{
					destroy = ((Boolean) found.get(i)).booleanValue();
					break;
				}
			}
			while(i>=0)
			{
				if((destroy)
				&&(found.get(0) instanceof Item))
				{
					props.addAll(((Item)found.get(0)).effects());
					record.lostAmt += ((Item)found.get(0)).basePhyStats().weight();
					record.lostValue +=((Item)found.get(0)).value();
					((Item)found.get(0)).destroy();
				}
				found.remove(0);
				i--;
			}
		}
		return record;
	}

	@Override
	public void alterAbilityComponentFile(final String compID, final boolean delete)
	{
		final CMFile F=new CMFile(Resources.makeFileResourceName("skills/components.txt"),null,CMFile.FLAG_LOGERRORS);
		if(delete)
		{
			Resources.findRemoveProperty(F, compID);
			return;
		}
		final String parms=getAbilityComponentCodedString(compID);
		final StringBuffer text=F.textUnformatted();
		boolean lastWasCR=true;
		boolean addIt=true;
		int delFromHere=-1;
		final String upID=compID.toUpperCase();
		for(int t=0;t<text.length();t++)
		{
			if(text.charAt(t)=='\n')
				lastWasCR=true;
			else
			if(text.charAt(t)=='\r')
				lastWasCR=true;
			else
			if(Character.isWhitespace(text.charAt(t)))
				continue;
			else
			if((lastWasCR)&&(delFromHere>=0))
			{
				text.delete(delFromHere,t);
				text.insert(delFromHere,parms+'\n');
				delFromHere=-1;
				addIt=false;
				break;
			}
			else
			if((lastWasCR)&&(Character.toUpperCase(text.charAt(t))==upID.charAt(0)))
			{
				if((text.substring(t).toUpperCase().startsWith(upID))
				&&(text.substring(t+upID.length()).trim().startsWith("=")))
				{
					addIt=false;
					delFromHere=t;
				}
				lastWasCR=false;
			}
			else
				lastWasCR=false;
		}
		if(delFromHere>0)
		{
			text.delete(delFromHere,text.length());
			text.append(parms+'\n');
		}
		if(addIt)
		{
			if(!lastWasCR)
				text.append('\n');
			text.append(parms+'\n');
		}
		F.saveText(text.toString(),false);
	}

	@Override
	public AbilityLimits getSpecialSkillLimit(final MOB studentM)
	{
		final AbilityLimits aL = new AbilityLimits()
		{
			private int	commonSkills		= 0;
			private int	maxCommonSkills		= 0;
			private int	craftingSkills		= 0;
			private int	maxCraftingSkills	= 0;
			private int	nonCraftingSkills	= 0;
			private int	maxNonCraftingSkills= 0;
			private int	specificSkillLimit	= 0;
			private int maxLanguageSkills	= 0;
			private int languageSkills		= 0;

			@Override
			public AbilityLimits commonSkills(final int newVal)
			{
				commonSkills = newVal;
				if(newVal > maxCommonSkills)
					maxCommonSkills = newVal;
				return this;
			}

			@Override
			public AbilityLimits craftingSkills(final int newVal)
			{
				craftingSkills = newVal;
				if(newVal > maxCraftingSkills)
					maxCraftingSkills = newVal;
				return this;
			}

			@Override
			public AbilityLimits nonCraftingSkills(final int newVal)
			{
				nonCraftingSkills = newVal;
				if(newVal > maxNonCraftingSkills)
					maxNonCraftingSkills = newVal;
				return this;
			}

			@Override
			public AbilityLimits languageSkills(final int newVal)
			{
				languageSkills = newVal;
				if(newVal > maxLanguageSkills)
					maxLanguageSkills = newVal;
				return this;
			}

			@Override
			public AbilityLimits specificSkillLimit(final int newVal)
			{
				specificSkillLimit = newVal;
				return this;
			}

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

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

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

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

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

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

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

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

			@Override
			public int maxLanguageSkills()
			{
				return maxLanguageSkills;
			}
		};
		CharClass C = null;
		if(studentM!=null)
		{
			C=studentM.charStats().getCurrentClass();
		}
		if(C!=null)
		{
			if(C.maxCommonSkills() == 0)
				aL.commonSkills(Integer.MAX_VALUE);
			else
				aL.commonSkills(C.maxCommonSkills());
			if(C.maxCraftingSkills() == 0)
				aL.craftingSkills(Integer.MAX_VALUE);
			else
				aL.craftingSkills(C.maxCraftingSkills());
			if(C.maxNonCraftingSkills() == 0)
				aL.nonCraftingSkills(Integer.MAX_VALUE);
			else
				aL.nonCraftingSkills(C.maxNonCraftingSkills());
			if(C.maxLanguages() == 0)
				aL.languageSkills(Integer.MAX_VALUE);
			else
				aL.languageSkills(C.maxLanguages());
		}
		if((studentM != null) && (studentM.playerStats() != null))
		{
			final PlayerStats pStats = studentM.playerStats();
			if (aL.commonSkills() < Integer.MAX_VALUE)
			{
				aL.commonSkills(aL.commonSkills() + pStats.getBonusCommonSkillLimits());
				if(pStats.getAccount() != null)
					aL.commonSkills(aL.commonSkills() + pStats.getAccount().getBonusCommonSkillLimits());
			}
			if (aL.craftingSkills() < Integer.MAX_VALUE)
			{
				aL.craftingSkills(aL.craftingSkills() + pStats.getBonusCraftingSkillLimits());
				if(pStats.getAccount() != null)
					aL.craftingSkills(aL.craftingSkills() + pStats.getAccount().getBonusCraftingSkillLimits());
			}
			if (aL.nonCraftingSkills() < Integer.MAX_VALUE)
			{
				aL.nonCraftingSkills(aL.nonCraftingSkills() + pStats.getBonusNonCraftingSkillLimits());
				if(pStats.getAccount() != null)
					aL.nonCraftingSkills(aL.nonCraftingSkills() + pStats.getAccount().getBonusNonCraftingSkillLimits());
			}
			if (aL.maxLanguageSkills() < Integer.MAX_VALUE)
			{
				aL.languageSkills(aL.languageSkills() + pStats.getBonusLanguageLimits());
				if(pStats.getAccount() != null)
					aL.languageSkills(aL.languageSkills() + pStats.getAccount().getBonusLanguageLimits());
			}
		}
		return aL;
	}

	@Override
	public AbilityLimits getSpecialSkillLimit(final MOB studentM, final Ability A)
	{
		final AbilityLimits aL=getSpecialSkillLimit(studentM);
		aL.specificSkillLimit(Integer.MAX_VALUE);
		if(A==null)
			return aL;
		if(A instanceof CommonSkill)
		{
			final boolean crafting = ((A.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_CRAFTINGSKILL)
									||((A.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_BUILDINGSKILL);
			aL.specificSkillLimit(crafting ? aL.craftingSkills() : aL.nonCraftingSkills());
		}
		if((A.classificationCode()&Ability.ALL_ACODES)==Ability.ACODE_LANGUAGE)
		{
			aL.specificSkillLimit(aL.languageSkills());
		}
		return aL;
	}

	@Override
	public AbilityLimits getSpecialSkillRemainder(final MOB studentM, final Ability A)
	{
		final AbilityLimits aL = getSpecialSkillRemainders(studentM);
		aL.specificSkillLimit(Integer.MAX_VALUE);
		if(A==null)
			return aL;
		if(A instanceof CommonSkill)
		{
			final boolean crafting = ((A.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_CRAFTINGSKILL)
									||((A.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_BUILDINGSKILL);
			aL.specificSkillLimit(crafting ? aL.craftingSkills() : aL.nonCraftingSkills());
		}
		if((A.classificationCode()&Ability.ALL_ACODES)==Ability.ACODE_LANGUAGE)
		{
			aL.specificSkillLimit(aL.languageSkills());
		}
		return aL;
	}

	@Override
	public AbilityLimits getSpecialSkillRemainders(final MOB student)
	{
		final AbilityLimits aL = getSpecialSkillLimit(student);
		final CharStats CS=student.charStats();
		if(CS.getCurrentClass()==null)
			return aL;
		final HashSet<String> culturalAbilities=new HashSet<String>();
		final QuadVector<String,Integer,Integer,Boolean> culturalAbilitiesDV = student.baseCharStats().getMyRace().culturalAbilities();
		for(int i=0;i<culturalAbilitiesDV.size();i++)
			culturalAbilities.add(culturalAbilitiesDV.getFirst(i).toLowerCase());
		for(int a=0;a<student.numAbilities();a++)
		{
			final Ability A2=student.fetchAbility(a);
			if(A2 instanceof CommonSkill)
			{
				if(culturalAbilities.contains(A2.ID().toLowerCase()))
					continue;
				boolean foundInAClass=false;
				for(int c=0;c<CS.numClasses();c++)
				{
					if(CMLib.ableMapper().getQualifyingLevel(CS.getMyClass(c).ID(), false, A2.ID())>=0)
					{
						foundInAClass=true;
						break;
					}
				}
				if(foundInAClass)
					continue;
				aL.commonSkills(aL.commonSkills()-1);
				final boolean crafting = ((A2.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_CRAFTINGSKILL)
										||((A2.classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_BUILDINGSKILL);
				if(crafting)
					aL.craftingSkills(aL.craftingSkills()-1);
				else
					aL.nonCraftingSkills(aL.nonCraftingSkills()-1);
			}
			else
			if(((A2.classificationCode()&Ability.ALL_ACODES)==Ability.ACODE_LANGUAGE)
			&&(!A2.ID().equals("Common")))
			{
				if(culturalAbilities.contains(A2.ID().toLowerCase()))
					continue;
				boolean foundInAClass=false;
				for(int c=0;c<CS.numClasses();c++)
				{
					if(CMLib.ableMapper().getQualifyingLevel(CS.getMyClass(c).ID(), false, A2.ID())>=0)
					{
						foundInAClass=true;
						break;
					}
				}
				if(foundInAClass)
					continue;
				aL.languageSkills(aL.languageSkills()-1);
			}
		}
		return aL;
	}
}