@Override
	public String evaluateAchievement(final Agent agent, String row, boolean addIfPossible)
	{
		if(row.trim().startsWith("#")||row.trim().startsWith(";")||(row.trim().length()==0))
			return null;
		int x=row.indexOf('=');
		while((x>=1)&&(row.charAt(x-1)=='\\'))
			x=row.indexOf('=',x+1);
		if(x<0)
			return "Error: Invalid line! Not comment, whitespace, and does not contain an = sign!";
		final String tattoo=row.substring(0,x).toUpperCase().trim();
		if(tattoo.length()==0)
			return "Error: Blank achievement tattoo: "+tattoo+"!";
		if(Character.isDigit(tattoo.charAt(0)))
			return "Error: Invalid achievement tattoo: "+tattoo+"!";
		final String params=row.substring(x+1).trim();
		String eventStr=CMParms.getParmStr(params, "EVENT", "");
		final Event eventType = (Event)CMath.s_valueOf(Event.class, eventStr.toUpperCase().trim());
		if(eventType == null)
			return "Error: Blank or unknown achievement type: "+eventStr+"!";
		final String displayStr=CMStrings.deEscape(CMParms.getParmStr(params, "DISPLAY", ""));
		final String titleStr=CMStrings.deEscape(CMParms.getParmStr(params, "TITLE", ""));
		String rewardStr=CMStrings.deEscape(CMParms.getParmStr(params, "REWARDS", ""));
		String[] awardSet = CMParms.parse(rewardStr).toArray(new String[0]);
		List<Award> awardsList = new ArrayList<Award>();
		if(titleStr.length()>0)
		{
			awardsList.add(new TitleAward()
			{
				@Override
				public AwardType getType()
				{
					return AwardType.TITLE;
				}

				@Override
				public String getTitle()
				{
					return titleStr;
				}

				@Override
				public boolean isPreAwarded()
				{
					return false;
				}

				@Override
				public String getDescription()
				{
					return L("The title: @x1",getTitle());
				}

				@Override
				public boolean isNotAwardedOnRemort()
				{
					return false;
				}
			});
		}
		for(int a=0;a<awardSet.length;a++)
		{
			if(awardSet[a].length()>0)
			{
				String thing = "";
				if(CMath.isInteger(awardSet[a]))
				{
					final int number = CMath.s_int(awardSet[a]);
					a++;
					while((a<awardSet.length)&&(awardSet[a].length()>0)&&(!CMath.isInteger(awardSet[a])))
					{
						thing += awardSet[a]+" ";
						a++;
					}
					a--;
					thing = thing.toUpperCase().trim();
					if(thing.equals("XP") || thing.startsWith("EXPERIEN") || thing.equals("EXP"))
					{
						awardsList.add(new AmountAward()
						{
							@Override
							public AwardType getType()
							{
								return AwardType.XP;
							}

							@Override
							public int getAmount()
							{
								return number;
							}

							@Override
							public boolean isPreAwarded()
							{
								return false;
							}

							@Override
							public boolean isNotAwardedOnRemort()
							{
								return false;
							}

							@Override
							public String getDescription()
							{
								return L("@x1 Experience Points",""+getAmount());
							}
						});
					}
					else
					if(thing.equals("QP") || thing.startsWith("QUEST"))
					{
						awardsList.add(new AmountAward()
						{
							@Override
							public AwardType getType()
							{
								return AwardType.QP;
							}

							@Override
							public int getAmount()
							{
								return number;
							}

							@Override
							public boolean isPreAwarded()
							{
								return false;
							}

							@Override
							public String getDescription()
							{
								return L("@x1 Quest Points",""+getAmount());
							}
							
							@Override
							public boolean isNotAwardedOnRemort()
							{
								return false;
							}
						});
					}
					else
					{
						int y=thing.indexOf('(');
						String parms="";
						if((y>0) && thing.endsWith(")"))
						{
							parms=thing.substring(y+1,thing.length()-1);
							thing=thing.substring(0,y);
						}
						Ability A=CMClass.getAbility(thing);
						if(A!=null)
						{
							String ableParms = CMStrings.deEscape(CMParms.getParmStr(parms, "PARMS", ""));
							String mask = CMStrings.deEscape(CMParms.getParmStr(parms, "MASK", ""));
							String preReqs = CMStrings.deEscape(CMParms.getParmStr(parms, "PREREQS", ""));
							boolean autoGain = CMParms.getParmBool(parms, "AUTOGAIN", true);
							final AbilityMapper.AbilityMapping mapp=CMLib.ableMapper().newAbilityMapping();
							mapp.abilityID(A.ID())
								.qualLevel(number)
								.autoGain(autoGain)
								.extraMask(mask)
								.defaultParm(ableParms);
							if(preReqs.length()>0)
							{
								mapp.originalSkillPreReqList(preReqs);
							}
							awardsList.add(new AbilityAward()
							{
								@Override
								public AwardType getType()
								{
									return AwardType.ABILITY;
								}

								@Override
								public AbilityMapping getAbilityMapping()
								{
									return mapp;
								}
								
								@Override
								public boolean isPreAwarded()
								{
									return false;
								}
								
								@Override
								public String getDescription()
								{
									final Ability skillA = CMClass.getAbility(getAbilityMapping().abilityID());
									if(getAbilityMapping().autoGain())
										return L("@x1 at level @x2",skillA.name(),""+getAbilityMapping().qualLevel());
									else
										return L("@x1 qualification at level @x2",skillA.name(),""+getAbilityMapping().qualLevel());
								}

								@Override
								public boolean isNotAwardedOnRemort()
								{
									return false;
								}
							});
						}
						else
						if(CMLib.expertises().findDefinition(thing, true) != null)
						{
							final boolean isAutoGained = CMParms.getParmBool(parms, "AUTOGAIN", true);
							final ExpertiseDefinition oldDef=CMLib.expertises().findDefinition(thing, true);
							final String ID = oldDef.ID();
							final ExpertiseDefinition def = new ExpertiseDefinition()
							{
								volatile WeakReference<ExpertiseDefinition> ref=new WeakReference<ExpertiseDefinition>(CMLib.expertises().findDefinition(ID, true));
								private ExpertiseDefinition baseDef()
								{
									if(ref == null)
										return null;
									ExpertiseDefinition curDef = ref.get();
									if(curDef == null)
									{
										curDef = CMLib.expertises().findDefinition(ID, true); 
										if(curDef==null)
										{
											ref = null;
										}
										else
											ref = new WeakReference<ExpertiseDefinition>(curDef);
									}
									return curDef;
								}
								
								@Override
								public String ID()
								{
									return ID;
								}

								@Override
								public String name()
								{
									final ExpertiseDefinition curDef = baseDef();
									return (curDef == null) ? "" : curDef.name();
								}

								@Override
								public CMObject newInstance()
								{
									return baseDef();
								}

								@Override
								public CMObject copyOf()
								{
									return baseDef();
								}

								@Override
								public void initializeClass()
								{
								}

								@Override
								public int compareTo(CMObject o)
								{
									final ExpertiseDefinition curDef = baseDef();
									return (curDef == null) ? -1 : curDef.compareTo(o);
								}

								@Override
								public String getBaseName()
								{
									final ExpertiseDefinition curDef = baseDef();
									return (curDef == null) ? "" : curDef.getBaseName();
								}

								@Override
								public void setBaseName(String baseName)
								{
								}

								@Override
								public void setName(String name)
								{
								}

								@Override
								public void setID(String ID)
								{
								}

								@Override
								public void setData(String[] data)
								{
								}

								@Override
								public ExpertiseDefinition getParent()
								{
									final ExpertiseDefinition curDef = baseDef();
									return (curDef == null) ? null : curDef.getParent();
								}

								@Override
								public int getMinimumLevel()
								{
									return number;
								}

								@Override
								public String[] getData()
								{
									final ExpertiseDefinition curDef = baseDef();
									return (curDef == null) ? new String[0] : curDef.getData();
								}

								@Override
								public CompiledZMask compiledListMask()
								{
									return CMLib.masking().getPreCompiledMask("-LEVEL +>="+number);
								}

								@Override
								public CompiledZMask compiledFinalMask()
								{
									return CMLib.masking().createEmptyMask();
								}

								@Override
								public String allRequirements()
								{
									return "-LEVEL +>="+number;
								}

								@Override
								public String listRequirements()
								{
									return "-LEVEL +>="+number;
								}

								@Override
								public String finalRequirements()
								{
									return "";
								}

								@Override
								public void addListMask(String mask)
								{
								}

								@Override
								public void addFinalMask(String mask)
								{
								}

								@Override
								public void addCost(CostType type, Double value)
								{
								}

								@Override
								public String costDescription()
								{
									if(isAutoGained)
										return "";
									final ExpertiseDefinition curDef = baseDef();
									return (curDef == null) ? "" : curDef.costDescription();
								}

								@Override
								public boolean meetsCostRequirements(MOB mob)
								{
									if(isAutoGained)
										return true;
									final ExpertiseDefinition curDef = baseDef();
									return (curDef == null) ? false : curDef.meetsCostRequirements(mob);
								}

								@Override
								public void spendCostRequirements(MOB mob)
								{
									if(isAutoGained)
										return;
									final ExpertiseDefinition curDef = baseDef();
									if(curDef != null)
										curDef.spendCostRequirements(mob);
								}
							};
							awardsList.add(new ExpertiseAward()
							{
								@Override
								public AwardType getType()
								{
									return AwardType.EXPERTISE;
								}

								@Override
								public int getLevel()
								{
									return number;
								}

								@Override
								public ExpertiseDefinition getExpertise()
								{
									return def;
								}
								

								@Override
								public boolean isPreAwarded()
								{
									return false;
								}

								@Override
								public String getDescription()
								{
									final ExpertiseDefinition defE = getExpertise();
									if(defE.costDescription().length()==0)
										return L("@x1 at level @x2",defE.name(),""+defE.getMinimumLevel());
									else
										return L("@x1 qualification at level @x1",""+defE.getMinimumLevel());
								}

								@Override
								public boolean isNotAwardedOnRemort()
								{
									return false;
								}
							});
						}
						else
						if(CMLib.coffeeMaker().isAnyGenStat(CMClass.samplePlayer(), thing))
						{
							final String stat1 = thing.toUpperCase().trim();
							String astat=CMLib.coffeeMaker().getFinalStatName(stat1);
							boolean isSave = false;
							for(int t : CharStats.CODES.SAVING_THROWS())
								isSave  = isSave  || CharStats.CODES.NAME(t).equals(astat.toUpperCase());
							final boolean isSavingThrow = isSave;
							final boolean isPreAwarded=
								(astat.startsWith("MAX") 
									&& (CharStats.CODES.findWhole(astat,true) >=0)
									&& (CMParms.contains(CharStats.CODES.MAXCODES(),CharStats.CODES.findWhole(astat,true))))
								|| astat.endsWith("BONUSCHARSTATS");
							final boolean isNotPremortAwarded = ((PlayerStats)CMClass.getCommonPrototype("DefaultPlayerStats")).isStat(astat);
							awardsList.add(new StatAward()
							{
								final String stat = stat1;
								final boolean savingThrow = isSavingThrow;
								final boolean preAwarded = isPreAwarded;
								final boolean noRemort = isNotPremortAwarded;
								@Override
								public AwardType getType()
								{
									return AwardType.STAT;
								}

								@Override
								public int getAmount()
								{
									return number;
								}

								@Override
								public String getStat()
								{
									return stat;
								}

								@Override
								public String getDescription()
								{
									if(getAmount()<0)
										return getAmount() + " " + L(CMStrings.capitalizeAndLower(getStat())+(savingThrow ? " resistance":""));
									else
										return "+"+getAmount() + " " + L(CMStrings.capitalizeAndLower(getStat())+(savingThrow ? " resistance":""));
								}

								@Override
								public boolean isPreAwarded()
								{
									return preAwarded;
								}

								@Override
								public boolean isNotAwardedOnRemort()
								{
									return noRemort;
								}
							});
						}
						else
						if(thing.toUpperCase().startsWith("ACCOUNT ")
						&&(CMClass.samplePlayer().playerStats()!=null)
						&&(CMClass.samplePlayer().playerStats().getAccount()!=null)
						&&CMLib.coffeeMaker().isAnyGenStat(CMClass.samplePlayer(), thing.substring(8)))
						{
							final String stat1 = thing.toUpperCase().trim();
							String astat=CMLib.coffeeMaker().getFinalStatName(stat1);
							boolean isSave = false;
							for(int t : CharStats.CODES.SAVING_THROWS())
								isSave  = isSave  || CharStats.CODES.NAME(t).equals(astat.toUpperCase());
							final boolean isSavingThrow = isSave;
							final boolean isPreAwarded=
								(astat.startsWith("MAX") 
									&& (CharStats.CODES.findWhole(astat,true) >=0)
									&& (CMParms.contains(CharStats.CODES.MAXCODES(),CharStats.CODES.findWhole(astat,true))))
								|| astat.endsWith("BONUSCHARSTATS");
							final boolean isNotPremortAwarded = ((PlayerStats)CMClass.getCommonPrototype("DefaultPlayerStats")).isStat(astat);
							awardsList.add(new StatAward()
							{
								final String stat = stat1;
								final boolean savingThrow = isSavingThrow;
								final boolean preAwarded = isPreAwarded;
								final boolean noRemort = isNotPremortAwarded;
								
								@Override
								public AwardType getType()
								{
									return AwardType.STAT;
								}

								@Override
								public int getAmount()
								{
									return number;
								}

								@Override
								public String getStat()
								{
									return stat;
								}

								@Override
								public String getDescription()
								{
									return "+"+getAmount() + " " + L(CMStrings.capitalizeAndLower(getStat())+(savingThrow?" resistance":""));
								}

								@Override
								public boolean isPreAwarded()
								{
									return preAwarded;
								}

								@Override
								public boolean isNotAwardedOnRemort()
								{
									return noRemort;
								}
							});
						}
						else
						{
							String currency = CMLib.english().matchAnyCurrencySet(thing);
							if(currency == null)
								Log.debugOut("Achievement", "Unknown award type: "+thing);
							else
							{
								double denomination = CMLib.english().matchAnyDenomination(currency, thing);
								if(denomination == 0.0)
									Log.debugOut("Achievement", "Unknown award type: "+thing);
								else
								{
									final String currencyName = thing;
									awardsList.add(new CurrencyAward()
									{
										@Override
										public AwardType getType()
										{
											return AwardType.CURRENCY;
										}

										@Override
										public int getAmount()
										{
											return number;
										}

										@Override
										public String getCurrency()
										{
											return currencyName;
										}

										@Override
										public boolean isPreAwarded()
										{
											return false;
										}

										@Override
										public String getDescription()
										{
											return getAmount() + " " + CMStrings.capitalizeAndLower(getCurrency());
										}

										@Override
										public boolean isNotAwardedOnRemort()
										{
											return false;
										}
									});
								}
							}
						}
					}
				}
			}
		}
		final Award[] rewardList = awardsList.toArray(new Award[0]); 
		Achievement A;
		switch(eventType)
		{
		case KILLS:
			A=new Achievement()
			{
				private int num = -1;
				private MaskingLibrary.CompiledZMask npcMask = null;
				private MaskingLibrary.CompiledZMask playerMask = null;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}
				
				@Override
				public Agent getAgent()
				{
					return agent;
				}
				
				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return num;
					
				}
				
				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}
				
				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						private volatile int count = oldCount;

						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (num>=0) && (getCount(mob) >= num);
						}

						@Override
						public int getCount(MOB mob)
						{
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms)
						{
							if((parms.length>0)
							&&(parms[0] instanceof MOB)
							&&((npcMask==null)||(CMLib.masking().maskCheck(npcMask, (MOB)parms[0], true)))
							&&((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true))))
							{
								count+=bumpNum;
								return true;
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					num=CMath.s_int(numStr);
					String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "ZAPPERMASK", ""));
					if(zapperMask.trim().length()==0)
						return "Error: Missing or invalid ZAPPERMASK parameter: "+zapperMask+"!";
					this.npcMask = CMLib.masking().getPreCompiledMask(zapperMask);
					zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					this.playerMask = null;
					if(zapperMask.trim().length()>0)
						this.playerMask = CMLib.masking().getPreCompiledMask(zapperMask);
					return "";
				}
			};
			break;
		case JUSTBE:
			A=new Achievement()
			{
				private MaskingLibrary.CompiledZMask playerMask = null;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}
				
				@Override
				public Agent getAgent()
				{
					return agent;
				}
				
				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return Integer.MIN_VALUE;
				}
				
				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}
				
				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							if((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true)))
								return true;
							return false;
						}

						@Override
						public int getCount(MOB mob)
						{
							return 0;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms)
						{
							if((parms.length>0)
							&&(parms[0] instanceof MOB)
							&&((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true))))
							{
								return true;
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					if(zapperMask.trim().length()==0)
						return "Error: Missing or invalid PLAYERMASK parameter: "+zapperMask+"!";
					this.playerMask = CMLib.masking().getPreCompiledMask(zapperMask);
					return "";
				}
			};
			break;
		case STATVALUE:
			A=new Achievement()
			{
				private String	statName= "";
				private int 	value	= 0;
				private int		abelo	= 0;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}
				
				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return value;
				}

				@Override
				public boolean isTargetFloor()
				{
					return abelo > 0;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (abelo > 0) ? (getCount(mob) > value) : (getCount(mob) < value);
						}

						@Override
						public int getCount(MOB mob)
						{
							return CMath.s_int(CMLib.coffeeMaker().getAnyGenStat(mob, statName));
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					MOB mob = CMClass.getFactoryMOB();
					final String numStr=CMParms.getParmStr(parms, "VALUE", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid VALUE parameter: "+numStr+"!";
					value=CMath.s_int(numStr);
					final String aboveBelow=CMParms.getParmStr(parms, "ABOVEBELOW", "").toUpperCase().trim();
					if((!aboveBelow.equals("ABOVE")) && (!aboveBelow.equals("BELOW")))
						return "Error: Missing or invalid ABOVEBELOW parameter: "+aboveBelow+"!";
					this.abelo = aboveBelow.equals("ABOVE")? 1 : -1;
					final String statName=CMParms.getParmStr(parms, "STAT", "").toUpperCase().trim();
					if((statName.length()==0)||(!CMLib.coffeeMaker().isAnyGenStat(mob, statName)))
						return "Error: Missing or invalid STAT parameter: "+statName+"!";
					this.statName = statName;
					return "";
				}
			};
			break;
		case FACTION:
			A=new Achievement()
			{
				private String	factionID	= "";
				private int 	value		= 0;
				private int		abelo		= 0;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public boolean isTargetFloor()
				{
					return abelo > 0;
				}
				
				@Override
				public int getTargetCount()
				{
					return value;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							if(mob.fetchFaction(factionID)==Integer.MAX_VALUE)
								return false;
							return (abelo > 0) ? (getCount(mob) > value) : (getCount(mob) < value);
						}

						@Override
						public int getCount(MOB mob)
						{
							int f=mob.fetchFaction(factionID);
							if(f == Integer.MAX_VALUE)
								return 0;
							return f;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "VALUE", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid VALUE parameter: "+numStr+"!";
					value=CMath.s_int(numStr);
					final String aboveBelow=CMParms.getParmStr(parms, "ABOVEBELOW", "").toUpperCase().trim();
					if((!aboveBelow.equals("ABOVE")) && (!aboveBelow.equals("BELOW")))
						return "Error: Missing or invalid ABOVEBELOW parameter: "+aboveBelow+"!";
					this.abelo = aboveBelow.equals("ABOVE")? 1 : -1;
					final String factionID=CMParms.getParmStr(parms, "ID", "").toUpperCase().trim();
					if(factionID.length()==0)
						return "Error: Missing ID parameter: "+factionID+"!";
					if(CMLib.factions().getFaction(factionID)==null)
						return "Error: Unknown faction ID parameter: "+factionID+"!";
					this.factionID=factionID;
					return "";
				}
			};
			break;
		case FACTIONS:
			A=new Achievement()
			{
				private final List<Faction>	factions	= new LinkedList<Faction>();
				private int 			number		= 0;
				private int 			value		= 0;
				private int				abelo		= 0;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public boolean isTargetFloor()
				{
					return abelo > 0;
				}
				
				@Override
				public int getTargetCount()
				{
					return number;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return getCount(mob) >= number;
						}

						@Override
						public int getCount(MOB mob)
						{
							int num=0;
							for(Faction F : factions)
							{
								int f = mob.fetchFaction(F.factionID());
								if((f!=Integer.MAX_VALUE)
								&&((abelo > 0) ? (f > value) : (f < value)))
									num++;
							}
							return num;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					String numStr=CMParms.getParmStr(parms, "VALUE", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid VALUE parameter: "+numStr+"!";
					value=CMath.s_int(numStr);
					numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					number=CMath.s_int(numStr);
					final String aboveBelow=CMParms.getParmStr(parms, "ABOVEBELOW", "").toUpperCase().trim();
					if((!aboveBelow.equals("ABOVE")) && (!aboveBelow.equals("BELOW")))
						return "Error: Missing or invalid ABOVEBELOW parameter: "+aboveBelow+"!";
					this.abelo = aboveBelow.equals("ABOVE")? 1 : -1;
					final String factionIDMask=CMParms.getParmStr(parms, "IDMASK", "").toUpperCase().trim();
					this.factions.clear();
					for(Enumeration<Faction> f=CMLib.factions().factions();f.hasMoreElements();)
					{
						final Faction F=f.nextElement();
						if(CMStrings.filenameMatcher(F.factionID(), factionIDMask))
							this.factions.add(F);
					}
					if(this.factions.size()==0)
						return "Error: No existing factions match: "+factionIDMask+"!";
					return "";
				}
			};
			break;
		case EXPLORE:
			A=new Achievement()
			{
				private String	areaID	= "";
				private int	 	pct		= 0;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public int getTargetCount()
				{
					return pct;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}
				
				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return getCount(mob) >= pct;
						}

						@Override
						public int getCount(MOB mob)
						{
							final PlayerStats pstats=mob.playerStats();
							if(pstats != null)
							{
								if(areaID.equals("WORLD"))
								{
									final Room R=mob.location();
									if((R!=null)&&(CMLib.map().getExtendedRoomID(CMLib.map().getRoom(R)).length()>0))
										return pstats.percentVisited(mob,null);
									else
										return 0;
								}
								else
								{
									final Area A=CMLib.map().getArea(areaID);
									if(A!=null)
									{
										return pstats.percentVisited(mob, A);
									}
								}
							}
							return 0;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}

				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "PERCENT", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid PERCENT parameter: "+numStr+"!";
					this.pct=CMath.s_int(numStr);
					final String areaID=CMParms.getParmStr(parms, "AREA", "").toUpperCase().trim();
					if(areaID.length()==0)
						return "Error: Missing AREA parameter: "+areaID+"!";
					if((CMLib.map().getArea(areaID)==null)&&(!areaID.equals("WORLD")))
						return "Error: Unknown AREA: "+areaID+"!";
					this.areaID=areaID;
					return "";
				}
			};
			break;
		case CRAFTING:
		case MENDER:
			A=new Achievement()
			{
				private int 				num 		= 0;
				private final Set<String>	abilityIDs 	= new TreeSet<String>();
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return num;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}
				
				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						private volatile int count = oldCount;
						
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return getCount(mob) >= num;
						}

						@Override
						public int getCount(MOB mob)
						{
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							final Ability A;
							if(parms.length>0)
							{
								if(parms[0] instanceof String)
									A=CMClass.getAbility((String)parms[0]);
								else
								if(parms[0] instanceof Ability)
									A=(Ability)parms[0];
								else
									A=null;
								if((A!=null)&&(abilityIDs.contains("*")||abilityIDs.contains(A.ID())))
								{
									count+=bumpNum;
									return true;
								}
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}

				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					this.num=CMath.s_int(numStr);
					final String abilityIDs=CMParms.getParmStr(parms, "ABILITYID", "").toUpperCase().trim();
					if(abilityIDs.length()==0)
						return "Error: Missing ABILITYID parameter: "+abilityIDs+"!";
					final String[] strList=abilityIDs.split(",");
					this.abilityIDs.clear();
					for(int i=0;i<strList.length;i++)
					{
						String abilityID = strList[i].trim();
						if(abilityID.equals("*"))
						{
							this.abilityIDs.add(abilityID);
							break;
						}
						else
						{
							final Ability A=CMClass.getAbility(abilityID);
							if((A==null)
							||((CMClass.getAbility(abilityID).classificationCode() & Ability.ALL_ACODES)!=Ability.ACODE_COMMON_SKILL)
							||((CMClass.getAbility(abilityID).classificationCode() & Ability.ALL_DOMAINS)!=Ability.DOMAIN_CRAFTINGSKILL))
								return "Error: Unknown crafting ABILITYID: "+abilityID+"!";
							this.abilityIDs.add(A.ID());
						}
					}
					if(this.abilityIDs.size()==0)
						return "Error: Unknown crafting ABILITYIDs: "+abilityIDs+"!";
					return "";
				}
			};
			break;
		case SKILLUSE:
			A=new Achievement()
			{
				private int 				num 		= 0;
				private final Set<String>	abilityIDs 	= new TreeSet<String>();
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public int getTargetCount()
				{
					return num;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						private volatile int count = oldCount;
						
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return getCount(mob) >= num;
						}

						@Override
						public int getCount(MOB mob)
						{
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							final Ability A;
							if(parms.length>0)
							{
								if(parms[0] instanceof String)
									A=CMClass.getAbility((String)parms[0]);
								else
								if(parms[0] instanceof Ability)
									A=(Ability)parms[0];
								else
									A=null;
								if((A!=null)
								&&(abilityIDs.contains("*")
									||abilityIDs.contains(A.ID())
									||(abilityIDs.contains(Ability.ACODE_DESCS[A.classificationCode()&Ability.ALL_ACODES]))
									||(abilityIDs.contains(Ability.DOMAIN_DESCS[(A.classificationCode()&Ability.ALL_DOMAINS)>>5]))))
								{
									count+=bumpNum;
									return true;
								}
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}

				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					this.num=CMath.s_int(numStr);
					final String abilityIDs=CMParms.getParmStr(parms, "ABILITYID", "").toUpperCase().trim();
					if(abilityIDs.length()==0)
						return "Error: Missing ABILITYID parameter: "+abilityIDs+"!";
					final String[] strList=abilityIDs.split(",");
					this.abilityIDs.clear();
					for(int i=0;i<strList.length;i++)
					{
						String abilityID = strList[i].trim();
						if(abilityID.equals("*"))
						{
							this.abilityIDs.add(abilityID);
							break;
						}
						else
						{
							final Ability A;
							if(CMParms.contains(Ability.ACODE_DESCS,abilityID)
							||CMParms.contains(Ability.DOMAIN_DESCS,abilityID))
								A=null;
							else
							{
								A=CMClass.getAbility(abilityID);
								if(A==null)
									return "Error: Unknown ABILITYID: "+abilityID+"!";
							}
							if(A!=null)
								this.abilityIDs.add(A.ID());
							else
								this.abilityIDs.add(abilityID.toUpperCase());
						}
					}
					if(this.abilityIDs.size()==0)
						return "Error: Unknown crafting ABILITYIDs: "+abilityIDs+"!";
					return "";
				}
			};
			break;
		case QUESTOR:
			A=new Achievement()
			{
				private int num = -1;
				private MaskingLibrary.CompiledZMask mask = null;
				private java.util.regex.Pattern questPattern = null;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public int getTargetCount()
				{
					return num;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						private volatile int count = oldCount;
						
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (num>=0) && getCount(mob) >= num;
						}

						@Override
						public int getCount(MOB mob)
						{
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							if((mask!=null)&&(!CMLib.masking().maskCheck(mask, mob, true)))
								return false;
							if(parms.length>0)
							{
								boolean match=questPattern == null;
								if(!match)
								{
									if(parms[0] instanceof String)
									{
										match = questPattern.matcher((String)parms[0]).find();
									}
									else
									if(parms[0] instanceof Quest)
									{
										Quest Q=(Quest)parms[0];
										match = 
											questPattern.matcher(Q.name()).find()
											|| questPattern.matcher(Q.displayName()).find();
									}
								}
								if(match)
								{
									count+=bumpNum;
									return true;
								}
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}

				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					num=CMath.s_int(numStr);
					final String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					if(zapperMask.trim().length()>0)
						this.mask = CMLib.masking().getPreCompiledMask(zapperMask);
					else
						this.mask = null;
					final String questMask=CMStrings.deEscape(CMParms.getParmStr(parms, "QUESTMASK", ""));
					this.questPattern = null;
					if(questMask.trim().length()>0)
					{
						try
						{
							java.util.regex.Pattern P = java.util.regex.Pattern.compile(questMask);
							if(P!=null)
								questPattern = P;
						}
						catch(Exception e)
						{
							return "Error: Invalid QUESTMASK regular expression parameter: "+questMask+": "+e.getMessage()+"!";
						}
					}
					return "";
				}
			};
			break;
		case ACHIEVER:
			A=new Achievement()
			{
				final Set<String> achievementList = new TreeSet<String>();
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public int getTargetCount()
				{
					if(achievementList.size()<2)
						return Integer.MIN_VALUE;
					else
						return achievementList.size();
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return getCount(mob) >= achievementList.size();
						}

						@Override
						public int getCount(MOB mob)
						{
							int count = 0;
							Tattooable other;
							if((CMProps.isUsingAccountSystem())
							&&(mob.playerStats()!=null))
								other=mob.playerStats().getAccount();
							else
								other=null;
							for(String s : achievementList)
							{
								if(mob.findTattoo(s)!=null)
									count++;
								else
								if((other!=null)&&(other.findTattoo(s)!=null))
									count++;
							}
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}

				@Override
				public String parseParms(final String parms)
				{
					final String list=CMStrings.deEscape(CMParms.getParmStr(parms, "ACHIEVEMENTLIST", ""));
					if(list.trim().length()==0)
						return "Error: Missing or invalid ACHIEVEMENTLIST parameter: "+list+"!";
					final String[] listArray = list.toUpperCase().trim().split(",");
					achievementList.clear();
					for(String s : listArray)
					{
						if(s.trim().length()>0)
							achievementList.add(s.trim());
					}
					if(achievementList.size()==0)
						return "Error: Missing or invalid ACHIEVEMENTLIST parameter: "+list+"!";
					return "";
				}
			};
			break;
		case ROOMENTER:
			A=new Achievement()
			{
				final Set<String> roomIDs = new TreeSet<String>();
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public int getTargetCount()
				{
					return roomIDs.size();
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return getCount(mob) >= roomIDs.size();
						}

						@Override
						public int getCount(MOB mob)
						{
							int count = 0;
							for(String s : roomIDs)
							{
								if(mob.playerStats().hasVisited(CMLib.map().getRoom(s)))
									count++;
							}
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms) 
						{
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}

				@Override
				public String parseParms(final String parms)
				{
					final String list=CMStrings.deEscape(CMParms.getParmStr(parms, "ROOMID", ""));
					if(list.trim().length()==0)
						return "Error: Missing or invalid ROOMID parameter: "+list+"!";
					final String[] listArray = list.toUpperCase().trim().split(",");
					roomIDs.clear();
					for(String s : listArray)
					{
						s=s.trim();
						if(s.length()>0)
						{
							Room R=CMLib.map().getRoom(s);
							if(R==null)
							{
								if(CMProps.getBoolVar(CMProps.Bool.MUDSTARTED))
									return "Error: Missing or invalid ROOMID: "+s+"!";
								else
									roomIDs.add(s);
							}
							else
								roomIDs.add(CMLib.map().getExtendedRoomID(R));
						}
					}
					if(roomIDs.size()==0)
						return "Error: Missing or invalid ROOMID parameter: "+list+"!";
					return "";
				}
			};
			break;
		case CLASSLEVELSGAINED:
			A=new Achievement()
			{
				private int num = -1;
				private CharClass charClass = null;
				private MaskingLibrary.CompiledZMask playerMask = null;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return num;
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (num>=0) && (getCount(mob) > num);
						}

						@Override
						public int getCount(MOB mob)
						{
							if(mob == null)
								return 0;
							int classLevel = mob.charStats().getClassLevel(charClass);
							if(classLevel < 0)
								return 0;
							return classLevel + 1;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms)
						{
							if(((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true)))
							&&(mob.charStats().getCurrentClass() == charClass))
							{
								return true;
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					num=CMath.s_int(numStr);
					String charClassID=CMStrings.deEscape(CMParms.getParmStr(parms, "CLASS", ""));
					this.charClass = CMClass.getCharClass(charClassID);
					if(this.charClass == null)
						this.charClass = CMClass.findCharClass(charClassID);
					if(this.charClass == null)
						return "Error: Missing or invalid CLASS parameter: "+charClassID+"!";
					String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					if(zapperMask.trim().length()>0)
						this.playerMask = CMLib.masking().getPreCompiledMask(zapperMask);
					return "";
				}
			};
			break;
		case RETIRE:
		case REMORT:
		case LEVELSGAINED:
			A=new Achievement()
			{
				private int num = -1;
				private MaskingLibrary.CompiledZMask playerMask = null;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}

				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return num;
				}

				@Override
				public boolean isTargetFloor()
				{
					return true;
				}

				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						private volatile int count = oldCount;

						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (num>=0) && (getCount(mob) >= num);
						}

						@Override
						public int getCount(MOB mob)
						{
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms)
						{
							if((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true)))
							{
								count+=bumpNum;
								return true;
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					num=CMath.s_int(numStr);
					String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					if(zapperMask.trim().length()>0)
						this.playerMask = CMLib.masking().getPreCompiledMask(zapperMask);
					return "";
				}
			};
			break;
		case GOTITEM:
			A=new Achievement()
			{
				private MaskingLibrary.CompiledZMask playerMask = null;
				private MaskingLibrary.CompiledZMask itemMask = null;
				private int num = 1;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}
				
				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return num;
				}
				
				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (num>=0) && (getCount(mob) >= num);
						}

						@Override
						public int getCount(MOB mob)
						{
							if(mob != null)
							{
								if((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true)))
								{
									int count = 0;
									final boolean noMask=(itemMask==null);
									for(Enumeration<Item> i=mob.items();i.hasMoreElements();)
									{
										final Item I=i.nextElement();
										if(noMask || (CMLib.masking().maskCheck(itemMask, I, true)))
											count++;
									}
									return count;
								}
							}
							return 0;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms)
						{
							if((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true)))
							{
								if((parms[0] instanceof Item)
								&&((itemMask==null)||(CMLib.masking().maskCheck(itemMask, (Item)parms[0], true))))
								{
									return true;
								}
							}
							return false;
						}

						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return false;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(numStr.trim().length()==0)
						num=1;
					else
					if(!CMath.isInteger(numStr))
						return "Error: Invalid NUM parameter!";
					else
						num=CMath.s_int(numStr);
					String itemZapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "ITEMMASK", ""));
					if(itemZapperMask.trim().length()>0)
						this.itemMask = CMLib.masking().getPreCompiledMask(itemZapperMask);
					else
						return "Error: Missing or invalid ITEMMASK parameter!";
					String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					if(zapperMask.trim().length()>0)
						this.playerMask = CMLib.masking().getPreCompiledMask(zapperMask);
					return "";
				}
			};
			break;
		case TIMEPLAYED:
			A=new Achievement()
			{
				private int seconds = 0;
				private MaskingLibrary.CompiledZMask playerMask = null;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}

				@Override
				public Agent getAgent()
				{
					return agent;
				}
				
				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return seconds;
					
				}
				
				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}

				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						private volatile int count = oldCount;

						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (getCount(mob) >= seconds);
						}

						@Override
						public int getCount(MOB mob)
						{
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms)
						{
							if((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true)))
							{
								count+=bumpNum;
								return true;
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "SECONDS", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid SECONDS parameter: "+numStr+"!";
					seconds=CMath.s_int(numStr);
					String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					if(zapperMask.trim().length()>0)
						this.playerMask = CMLib.masking().getPreCompiledMask(zapperMask);
					return "";
				}
			};
			break;
		case DEATHS:
			A=new Achievement()
			{
				private int num = -1;
				private MaskingLibrary.CompiledZMask npcMask = null;
				private MaskingLibrary.CompiledZMask playerMask = null;
				
				@Override
				public Event getEvent()
				{
					return eventType;
				}
				
				@Override
				public Agent getAgent()
				{
					return agent;
				}
				
				@Override
				public String getTattoo()
				{
					return tattoo;
				}

				@Override
				public int getTargetCount()
				{
					return num;
					
				}
				
				@Override
				public boolean isTargetFloor()
				{
					return true;
				}
				
				@Override
				public String getDisplayStr()
				{
					return displayStr;
				}

				@Override
				public Award[] getRewards()
				{
					return rewardList;
				}
				
				@Override
				public String getRawParmVal(String str)
				{
					return CMParms.getParmStr(params,str,"");
				}

				@Override
				public Tracker getTracker(final int oldCount)
				{
					final Achievement me=this;
					return new Tracker()
					{
						private volatile int count = oldCount;

						@Override
						public Achievement getAchievement() 
						{
							return me;
						}

						@Override
						public boolean isAchieved(MOB mob) 
						{
							return (num>=0) && (getCount(mob) >= num);
						}

						@Override
						public int getCount(MOB mob)
						{
							return count;
						}

						@Override
						public boolean testBump(MOB mob, int bumpNum, Object... parms)
						{
							if((parms.length>0)
							&&(parms[0] instanceof MOB)
							&&((npcMask==null)||(CMLib.masking().maskCheck(npcMask, (MOB)parms[0], true)))
							&&((playerMask==null)||(CMLib.masking().maskCheck(playerMask, mob, true))))
							{
								count+=bumpNum;
								return true;
							}
							return false;
						}
						
						@Override
						public Tracker copyOf()
						{
							try
							{
								return (Tracker)this.clone();
							}
							catch(Exception e)
							{
								return this;
							}
						}
					};
				}
				
				@Override
				public boolean isSavableTracker()
				{
					return true;
				}
				
				@Override
				public String parseParms(final String parms)
				{
					final String numStr=CMParms.getParmStr(parms, "NUM", "");
					if(!CMath.isInteger(numStr))
						return "Error: Missing or invalid NUM parameter: "+numStr+"!";
					num=CMath.s_int(numStr);
					this.npcMask = null;
					String zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "ZAPPERMASK", ""));
					if(zapperMask.trim().length()>=0)
						this.npcMask = CMLib.masking().getPreCompiledMask(zapperMask);
					zapperMask=CMStrings.deEscape(CMParms.getParmStr(parms, "PLAYERMASK", ""));
					this.playerMask = null;
					if(zapperMask.trim().length()>0)
						this.playerMask = CMLib.masking().getPreCompiledMask(zapperMask);
					return "";
				}
			};
			break;
			default:
				A=null;
				break;
		}
		
		if(A==null)
		{
			return "Error: Unimplemented achievement type: "+eventStr+"!";
		}
		
		final String err = A.parseParms(params);
		if((err!=null)&&(err.length()>0))
		{
			return err;
		}
		
		if(addIfPossible)
		{
			// the call to achievements below will ensure others are loaded.
			for(final Enumeration<Achievement> a2  = achievements(null); a2.hasMoreElements();)
			{
				final Achievement A2=a2.nextElement();
				if(A2.getTattoo().equalsIgnoreCase(tattoo))
					return "Error: Duplicate achievement ID: "+tattoo+"!";
			}
			switch(agent)
			{
			case ACCOUNT:
				accountAchievements.add(A);
				break;
			case PLAYER:
				playerAchievements.add(A);
				break;
			}
			List<Achievement> eventList = eventMap.get(A.getEvent());
			if(eventList == null)
			{
				eventList = new LinkedList<Achievement>();
				eventMap.put(A.getEvent(), eventList);
			}
			eventList.add(A);
		}
		return null;
	}