@SuppressWarnings("unchecked")
	protected void genClassAbilities(MOB mob, CharClass E, int showNumber, int showFlag)
		throws IOException
	{
		if((showFlag>0)&&(showFlag!=showNumber))
			return;
		if((showFlag!=showNumber)&&(showFlag>-999))
		{
			mob.tell(L("@x1. Class Abilities: [...].",""+showNumber));
			return;
		}
		while((mob.session()!=null)&&(!mob.session().isStopped())&&(true))
		{
			final StringBuffer parts=new StringBuffer("");
			int numAbles=CMath.s_int(E.getStat("NUMCABLE"));
			final DVector levelSets=new DVector(2);
			int maxAbledLevel=Integer.MIN_VALUE;
			for(int v=0;v<numAbles;v++)
			{
				final Ability A=CMClass.getAbility(E.getStat("GETCABLE"+v));
				if((A!=null)
				&&(!CMLib.ableMapper().getAllQualified(E.ID(), true, A.ID())))
				{
					final AbilityMapper.AbilityMapping aMAP=CMLib.ableMapper().newAbilityMapping().ID(A.ID());
					aMAP.abilityID(A.ID());
					aMAP.autoGain(CMath.s_bool(E.getStat("GETCABLEGAIN"+v)));
					aMAP.defaultProficiency(CMath.s_int(E.getStat("GETCABLEPROF"+v)));
					aMAP.qualLevel(CMath.s_int(E.getStat("GETCABLELVL"+v)));
					aMAP.isSecret(CMath.s_bool(E.getStat("GETCABLESECR"+v)));
					aMAP.maxProficiency(CMath.s_int(E.getStat("GETCABLEMAXP"+v)));
					aMAP.defaultParm(E.getStat("GETCABLEPARM"+v));
					aMAP.originalSkillPreReqList(E.getStat("GETCABLEPREQ"+v));
					aMAP.extraMask(E.getStat("GETCABLEMASK"+v));
					final int lvlIndex=levelSets.indexOf(Integer.valueOf(aMAP.qualLevel()));
					Vector<AbilityMapper.AbilityMapping> set=null;
					if(lvlIndex<0)
					{
						set=new Vector<AbilityMapper.AbilityMapping>();
						levelSets.addElement(Integer.valueOf(aMAP.qualLevel()),set);
						if(aMAP.qualLevel()>maxAbledLevel)
							maxAbledLevel=aMAP.qualLevel();
					}
					else
						set=(Vector<AbilityMapper.AbilityMapping>)levelSets.elementAt(lvlIndex,2);
					set.addElement(aMAP);
				}
			}
			final String header=showNumber+". Class Abilities: ";
			final String spaces=CMStrings.repeat(' ',2+(""+showNumber).length());
			parts.append("\n\r");
			parts.append(spaces+CMStrings.padRight(L("Lvl"),3)+" "
							   +CMStrings.padRight(L("Skill"),25)+" "
							   +CMStrings.padRight(L("Proff"),5)+" "
							   +CMStrings.padRight(L("Gain"),5)+" "
							   +CMStrings.padRight(L("Secret"),6)+" "
							   +CMStrings.padRight(L("Parm"),7)+" "
							   +CMStrings.padRight(L("Preq"),7)+" "
							   +CMStrings.padRight(L("Mask"),6)+"\n\r"
							   );
			for(int i=0;i<=maxAbledLevel;i++)
			{
				final int index=levelSets.indexOf(Integer.valueOf(i));
				if(index<0)
					continue;
				final List<AbilityMapper.AbilityMapping> set=(List<AbilityMapper.AbilityMapping>)levelSets.elementAt(index,2);
				for(int s=0;s<set.size();s++)
				{
					final AbilityMapper.AbilityMapping aMAP=set.get(s);
					parts.append(spaces+CMStrings.padRight(""+i,3)+" "
									   +CMStrings.padRight(""+aMAP.abilityID(),25)+" "
									   +CMStrings.padRight(""+aMAP.defaultProficiency(),5)+" "
									   +CMStrings.padRight(""+aMAP.autoGain(),5)+" "
									   +CMStrings.padRight(""+aMAP.isSecret(),6)+" "
									   +CMStrings.padRight(""+aMAP.defaultParm(),7)+" "
									   +CMStrings.padRight(""+aMAP.originalSkillPreReqList(),7)+" "
									   +CMStrings.padRight(""+aMAP.extraMask(),6)+"\n\r"
									   );
				}
			}

			mob.session().wraplessPrintln(header+parts.toString());
			final String newName=mob.session().prompt(L("Enter an ability name to add or remove (?)\n\r:"),"");
			if(newName.equalsIgnoreCase("?"))
				mob.tell(CMLib.lister().reallyList(mob,CMClass.abilities(),-1).toString());
			else
			if(newName.length()>0)
			{
				int lvlIndex=-1;
				int ableIndex=-1;
				List<AbilityMapper.AbilityMapping> myLevelSet=null;
				for(int s=0;s<levelSets.size();s++)
				{
					final List<AbilityMapper.AbilityMapping> lvls=(List<AbilityMapper.AbilityMapping>)levelSets.elementAt(s,2);
					for(int l=0;l<lvls.size();l++)
					{
						if(CMLib.english().containsString(lvls.get(l).abilityID(),newName))
						{
							lvlIndex=s;
							ableIndex=l;
							myLevelSet=lvls;
							break;
						}
					}
					if(lvlIndex>=0)
						break;
				}
				boolean updateList=false;
				if(ableIndex<0)
				{
					final Ability A=CMClass.getAbility(newName);
					if(A==null)
						mob.tell(L("That is neither an existing ability name, nor a valid one to add.  Use ? for a list."));
					else
					{
						// add new one here
						if(genClassAbleMod(mob,levelSets,A.ID(),-1,-1)!=null)
						{
							mob.tell(L("@x1 added.",A.ID()));
							updateList=true;
							numAbles++;
						}
					}
				}
				else
				if(myLevelSet!=null)
				{
					final String aID=myLevelSet.get(ableIndex).abilityID();
					if(genClassAbleMod(mob,levelSets,aID,lvlIndex,ableIndex)!=null)
						mob.tell(L("@x1 modified.",aID));
					else
					{
						mob.tell(L("@x1 removed.",aID));
						numAbles--;
					}

					updateList=true;
				}
				if(updateList)
				{
					if(numAbles>0)
						E.setStat("NUMCABLE",""+numAbles);
					else
						E.setStat("NUMCABLE","");
					int dex=0;
					for(int s=0;s<levelSets.size();s++)
					{
						final Integer lvl=(Integer)levelSets.elementAt(s,1);
						final List<AbilityMapper.AbilityMapping> lvls=(List<AbilityMapper.AbilityMapping>)levelSets.elementAt(s,2);
						for(int l=0;l<lvls.size();l++)
						{
							final AbilityMapper.AbilityMapping aMAP=lvls.get(l);
							E.setStat("GETCABLELVL"+dex,lvl.toString());
							E.setStat("GETCABLEGAIN"+dex,""+aMAP.autoGain());
							E.setStat("GETCABLEPROF"+dex,""+aMAP.defaultProficiency());
							E.setStat("GETCABLESECR"+dex,""+aMAP.isSecret());
							E.setStat("GETCABLEPARM"+dex,""+aMAP.defaultParm());
							E.setStat("GETCABLEPREQ"+dex,aMAP.originalSkillPreReqList());
							E.setStat("GETCABLEMASK"+dex,aMAP.extraMask());
							E.setStat("GETCABLEMAXP"+dex,""+aMAP.maxProficiency());
							// CABLE MUST BE LAST
							E.setStat("GETCABLE"+dex,aMAP.abilityID());
							dex++;
						}
					}
				}
			}
			else
			{
				mob.tell(L("(no change)"));
				return;
			}
		}
	}