Bug 565945: add user title depending on skills & progress

  the user gets a title that depends on the most advanced skill that
  offers a specific title

Change-Id: I9d83723c160101f01f6a71c7ade4caf1b2ae5790
diff --git a/plugins/org.eclipse.skills.ui.questeditor/plugin.properties b/plugins/org.eclipse.skills.ui.questeditor/plugin.properties
index e85c1cc..dc71110 100644
--- a/plugins/org.eclipse.skills.ui.questeditor/plugin.properties
+++ b/plugins/org.eclipse.skills.ui.questeditor/plugin.properties
Binary files differ
diff --git a/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/QuestItemProvider.java b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/QuestItemProvider.java
index 0097449..ad1f74e 100644
--- a/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/QuestItemProvider.java
+++ b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/QuestItemProvider.java
@@ -185,6 +185,7 @@
 				return;
 			case ISkillsPackage.QUEST__TASKS:
 			case ISkillsPackage.QUEST__SKILLS:
+			case ISkillsPackage.QUEST__USER_TITLES:
 				fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), true, false));
 				return;
 		}
diff --git a/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/SkillsItemProviderAdapterFactory.java b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/SkillsItemProviderAdapterFactory.java
index 3bb3551..8305a81 100644
--- a/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/SkillsItemProviderAdapterFactory.java
+++ b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/SkillsItemProviderAdapterFactory.java
@@ -555,6 +555,29 @@
 	}
 
 	/**
+	 * This keeps track of the one adapter used for all {@link org.eclipse.skills.model.IUserTitle} instances.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	protected UserTitleItemProvider userTitleItemProvider;
+
+	/**
+	 * This creates an adapter for a {@link org.eclipse.skills.model.IUserTitle}.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public Adapter createUserTitleAdapter() {
+		if (userTitleItemProvider == null) {
+			userTitleItemProvider = new UserTitleItemProvider(this);
+		}
+
+		return userTitleItemProvider;
+	}
+
+	/**
 	 * This returns the root adapter factory that contains this factory.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
@@ -680,6 +703,7 @@
 		if (skillDependencyItemProvider != null) skillDependencyItemProvider.dispose();
 		if (hintItemProvider != null) hintItemProvider.dispose();
 		if (factorProgressionItemProvider != null) factorProgressionItemProvider.dispose();
+		if (userTitleItemProvider != null) userTitleItemProvider.dispose();
 	}
 
 }
diff --git a/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/UserItemProvider.java b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/UserItemProvider.java
index e89bfc0..54d800f 100644
--- a/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/UserItemProvider.java
+++ b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/UserItemProvider.java
@@ -68,6 +68,7 @@
 			addUsertasksPropertyDescriptor(object);
 			addImageLocationPropertyDescriptor(object);
 			addExperiencePropertyDescriptor(object);
+			addTitlePropertyDescriptor(object);
 		}
 		return itemPropertyDescriptors;
 	}
@@ -183,6 +184,28 @@
 	}
 
 	/**
+	 * This adds a property descriptor for the Title feature.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	protected void addTitlePropertyDescriptor(Object object) {
+		itemPropertyDescriptors.add
+			(createItemPropertyDescriptor
+				(((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(),
+				 getResourceLocator(),
+				 getString("_UI_User_title_feature"),
+				 getString("_UI_PropertyDescriptor_description", "_UI_User_title_feature", "_UI_User_type"),
+				 ISkillsPackage.Literals.USER__TITLE,
+				 false,
+				 false,
+				 false,
+				 ItemPropertyDescriptor.GENERIC_VALUE_IMAGE,
+				 null,
+				 null));
+	}
+
+	/**
 	 * This specifies how to implement {@link #getChildren} and is used to deduce an appropriate feature for an
 	 * {@link org.eclipse.emf.edit.command.AddCommand}, {@link org.eclipse.emf.edit.command.RemoveCommand} or
 	 * {@link org.eclipse.emf.edit.command.MoveCommand} in {@link #createCommand}.
diff --git a/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/UserTitleItemProvider.java b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/UserTitleItemProvider.java
new file mode 100644
index 0000000..9793c81
--- /dev/null
+++ b/plugins/org.eclipse.skills.ui.questeditor/src-gen/org/eclipse/skills/model/provider/UserTitleItemProvider.java
@@ -0,0 +1,203 @@
+/**
+ */
+package org.eclipse.skills.model.provider;
+
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.emf.common.notify.AdapterFactory;
+import org.eclipse.emf.common.notify.Notification;
+
+import org.eclipse.emf.common.util.ResourceLocator;
+
+import org.eclipse.emf.edit.provider.ComposeableAdapterFactory;
+import org.eclipse.emf.edit.provider.IEditingDomainItemProvider;
+import org.eclipse.emf.edit.provider.IItemLabelProvider;
+import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
+import org.eclipse.emf.edit.provider.IItemPropertySource;
+import org.eclipse.emf.edit.provider.IStructuredItemContentProvider;
+import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
+import org.eclipse.emf.edit.provider.ItemPropertyDescriptor;
+import org.eclipse.emf.edit.provider.ItemProviderAdapter;
+import org.eclipse.emf.edit.provider.ViewerNotification;
+
+import org.eclipse.skills.model.ISkillsPackage;
+import org.eclipse.skills.model.IUserTitle;
+
+/**
+ * This is the item provider adapter for a {@link org.eclipse.skills.model.IUserTitle} object.
+ * <!-- begin-user-doc -->
+ * <!-- end-user-doc -->
+ * @generated
+ */
+public class UserTitleItemProvider 
+	extends ItemProviderAdapter
+	implements
+		IEditingDomainItemProvider,
+		IStructuredItemContentProvider,
+		ITreeItemContentProvider,
+		IItemLabelProvider,
+		IItemPropertySource {
+	/**
+	 * This constructs an instance from a factory and a notifier.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	public UserTitleItemProvider(AdapterFactory adapterFactory) {
+		super(adapterFactory);
+	}
+
+	/**
+	 * This returns the property descriptors for the adapted class.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public List<IItemPropertyDescriptor> getPropertyDescriptors(Object object) {
+		if (itemPropertyDescriptors == null) {
+			super.getPropertyDescriptors(object);
+
+			addMinLevelPropertyDescriptor(object);
+			addSkillPropertyDescriptor(object);
+			addDisplayStringPropertyDescriptor(object);
+		}
+		return itemPropertyDescriptors;
+	}
+
+	/**
+	 * This adds a property descriptor for the Min Level feature.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	protected void addMinLevelPropertyDescriptor(Object object) {
+		itemPropertyDescriptors.add
+			(createItemPropertyDescriptor
+				(((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(),
+				 getResourceLocator(),
+				 getString("_UI_UserTitle_minLevel_feature"),
+				 getString("_UI_PropertyDescriptor_description", "_UI_UserTitle_minLevel_feature", "_UI_UserTitle_type"),
+				 ISkillsPackage.Literals.USER_TITLE__MIN_LEVEL,
+				 true,
+				 false,
+				 false,
+				 ItemPropertyDescriptor.INTEGRAL_VALUE_IMAGE,
+				 null,
+				 null));
+	}
+
+	/**
+	 * This adds a property descriptor for the Skill feature.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	protected void addSkillPropertyDescriptor(Object object) {
+		itemPropertyDescriptors.add
+			(createItemPropertyDescriptor
+				(((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(),
+				 getResourceLocator(),
+				 getString("_UI_UserTitle_skill_feature"),
+				 getString("_UI_PropertyDescriptor_description", "_UI_UserTitle_skill_feature", "_UI_UserTitle_type"),
+				 ISkillsPackage.Literals.USER_TITLE__SKILL,
+				 true,
+				 false,
+				 true,
+				 null,
+				 null,
+				 null));
+	}
+
+	/**
+	 * This adds a property descriptor for the Display String feature.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	protected void addDisplayStringPropertyDescriptor(Object object) {
+		itemPropertyDescriptors.add
+			(createItemPropertyDescriptor
+				(((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(),
+				 getResourceLocator(),
+				 getString("_UI_UserTitle_displayString_feature"),
+				 getString("_UI_PropertyDescriptor_description", "_UI_UserTitle_displayString_feature", "_UI_UserTitle_type"),
+				 ISkillsPackage.Literals.USER_TITLE__DISPLAY_STRING,
+				 true,
+				 false,
+				 false,
+				 ItemPropertyDescriptor.GENERIC_VALUE_IMAGE,
+				 null,
+				 null));
+	}
+
+	/**
+	 * This returns UserTitle.gif.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public Object getImage(Object object) {
+		return overlayImage(object, getResourceLocator().getImage("full/obj16/UserTitle"));
+	}
+
+	/**
+	 * This returns the label text for the adapted class.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public String getText(Object object) {
+		IUserTitle userTitle = (IUserTitle)object;
+		return getString("_UI_UserTitle_type") + " " + userTitle.getMinLevel();
+	}
+
+
+	/**
+	 * This handles model notifications by calling {@link #updateChildren} to update any cached
+	 * children and by creating a viewer notification, which it passes to {@link #fireNotifyChanged}.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public void notifyChanged(Notification notification) {
+		updateChildren(notification);
+
+		switch (notification.getFeatureID(IUserTitle.class)) {
+			case ISkillsPackage.USER_TITLE__MIN_LEVEL:
+			case ISkillsPackage.USER_TITLE__DISPLAY_STRING:
+				fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), false, true));
+				return;
+		}
+		super.notifyChanged(notification);
+	}
+
+	/**
+	 * This adds {@link org.eclipse.emf.edit.command.CommandParameter}s describing the children
+	 * that can be created under this object.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	protected void collectNewChildDescriptors(Collection<Object> newChildDescriptors, Object object) {
+		super.collectNewChildDescriptors(newChildDescriptors, object);
+	}
+
+	/**
+	 * Return the resource locator for this item provider's resources.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public ResourceLocator getResourceLocator() {
+		return SkillsEditPlugin.INSTANCE;
+	}
+
+}
diff --git a/plugins/org.eclipse.skills/model/Skills.ecore b/plugins/org.eclipse.skills/model/Skills.ecore
index 9fe373d..4821ea8 100644
--- a/plugins/org.eclipse.skills/model/Skills.ecore
+++ b/plugins/org.eclipse.skills/model/Skills.ecore
@@ -65,6 +65,7 @@
         eType="#//Skill" containment="true"/>
     <eStructuralFeatures xsi:type="ecore:EReference" name="badges" upperBound="-1"
         eType="#//Badge" containment="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="title" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
   </eClassifiers>
   <eClassifiers xsi:type="ecore:EClass" name="Skill">
     <eOperations name="addExperience">
@@ -99,6 +100,8 @@
     <eStructuralFeatures xsi:type="ecore:EReference" name="skills" upperBound="-1"
         eType="#//Skill" containment="true"/>
     <eStructuralFeatures xsi:type="ecore:EAttribute" name="title" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EReference" name="userTitles" upperBound="-1"
+        eType="#//UserTitle" containment="true"/>
   </eClassifiers>
   <eClassifiers xsi:type="ecore:EClass" name="Dependency" abstract="true">
     <eOperations name="activate"/>
@@ -188,4 +191,11 @@
     <eStructuralFeatures xsi:type="ecore:EAttribute" name="xpFactor" lowerBound="1"
         eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble" defaultValueLiteral="2.4"/>
   </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="UserTitle">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="minLevel" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
+    <eStructuralFeatures xsi:type="ecore:EReference" name="skill" eType="#//Skill"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="displayString" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+  </eClassifiers>
 </ecore:EPackage>
diff --git a/plugins/org.eclipse.skills/model/Skills.genmodel b/plugins/org.eclipse.skills/model/Skills.genmodel
index 27be37f..43cd964 100644
--- a/plugins/org.eclipse.skills/model/Skills.genmodel
+++ b/plugins/org.eclipse.skills/model/Skills.genmodel
@@ -55,6 +55,7 @@
       <genFeatures createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//User/imageLocation"/>
       <genFeatures children="true" createChild="true" propertySortChoices="true" ecoreFeature="ecore:EReference Skills.ecore#//User/experience"/>
       <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Skills.ecore#//User/badges"/>
+      <genFeatures property="Readonly" notify="false" createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//User/title"/>
       <genOperations ecoreOperation="Skills.ecore#//User/addTask">
         <genParameters ecoreParameter="Skills.ecore#//User/addTask/task"/>
       </genOperations>
@@ -95,6 +96,7 @@
       <genFeatures children="true" createChild="true" propertySortChoices="true" ecoreFeature="ecore:EReference Skills.ecore#//Quest/tasks"/>
       <genFeatures children="true" createChild="true" propertySortChoices="true" ecoreFeature="ecore:EReference Skills.ecore#//Quest/skills"/>
       <genFeatures createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//Quest/title"/>
+      <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Skills.ecore#//Quest/userTitles"/>
     </genClasses>
     <genClasses image="false" ecoreClass="Skills.ecore#//Dependency">
       <genFeatures createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//Dependency/fulfilled"/>
@@ -162,5 +164,10 @@
       <genFeatures createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//FactorProgression/baseXpNeeded"/>
       <genFeatures createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//FactorProgression/xpFactor"/>
     </genClasses>
+    <genClasses ecoreClass="Skills.ecore#//UserTitle">
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//UserTitle/minLevel"/>
+      <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference Skills.ecore#//UserTitle/skill"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute Skills.ecore#//UserTitle/displayString"/>
+    </genClasses>
   </genPackages>
 </genmodel:GenModel>
diff --git a/plugins/org.eclipse.skills/model/model.aird b/plugins/org.eclipse.skills/model/model.aird
index d98cfc8..4004d1f 100644
--- a/plugins/org.eclipse.skills/model/model.aird
+++ b/plugins/org.eclipse.skills/model/model.aird
@@ -5,7 +5,7 @@
     <semanticResources>http://eclipse.org/skills/1.0.0</semanticResources>
     <ownedViews xmi:type="viewpoint:DView" uid="_m68ZsEwrEeqwHIzbUoB2lQ">
       <viewpoint xmi:type="description:Viewpoint" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']"/>
-      <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_n6l40EwrEeqwHIzbUoB2lQ" name="Skills Model" repPath="#_n4mUwEwrEeqwHIzbUoB2lQ" changeId="4a45e48a-3991-46f2-b065-bb6f40324f4b">
+      <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_n6l40EwrEeqwHIzbUoB2lQ" name="Skills Model" repPath="#_n4mUwEwrEeqwHIzbUoB2lQ" changeId="81b72874-5cd2-4495-8f09-33c59b878780">
         <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/>
         <target xmi:type="ecore:EPackage" href="Skills.ecore#/"/>
       </ownedRepresentationDescriptors>
@@ -177,6 +177,10 @@
               <styles xmi:type="notation:FontStyle" xmi:id="_rInSVNrpEeq_CZJuNlb8eA" fontName="Cantarell" fontHeight="8"/>
               <layoutConstraint xmi:type="notation:Location" xmi:id="_rInSVdrpEeq_CZJuNlb8eA"/>
             </children>
+            <children xmi:type="notation:Node" xmi:id="_qwq4gBhXEeuGKdtTSc3Y2w" type="3010" element="_qvAEkBhXEeuGKdtTSc3Y2w">
+              <styles xmi:type="notation:FontStyle" xmi:id="_qwq4gRhXEeuGKdtTSc3Y2w" fontColor="2697711" fontName="Cantarell" fontHeight="8"/>
+              <layoutConstraint xmi:type="notation:Location" xmi:id="_qwq4ghhXEeuGKdtTSc3Y2w"/>
+            </children>
             <children xmi:type="notation:Node" xmi:id="_rInSVtrpEeq_CZJuNlb8eA" type="3010" element="_rHZxYtrpEeq_CZJuNlb8eA">
               <styles xmi:type="notation:FontStyle" xmi:id="_rInSV9rpEeq_CZJuNlb8eA" fontName="Cantarell" fontHeight="8"/>
               <layoutConstraint xmi:type="notation:Location" xmi:id="_rInSWNrpEeq_CZJuNlb8eA"/>
@@ -589,6 +593,23 @@
           <styles xmi:type="notation:ShapeStyle" xmi:id="_RpU0sQIeEeuURJHBrP3sSA" fontName="Cantarell" fontHeight="8"/>
           <layoutConstraint xmi:type="notation:Bounds" xmi:id="_RpU0sgIeEeuURJHBrP3sSA" x="85" y="650" width="198" height="100"/>
         </children>
+        <children xmi:type="notation:Node" xmi:id="_nmEUIBR6EeuFZNgytReO9g" type="2003" element="_nlVUUBR6EeuFZNgytReO9g">
+          <children xmi:type="notation:Node" xmi:id="_nmIlkBR6EeuFZNgytReO9g" type="5007"/>
+          <children xmi:type="notation:Node" xmi:id="_nmJMoBR6EeuFZNgytReO9g" type="7004">
+            <children xmi:type="notation:Node" xmi:id="_DysiIBSgEeubdpURgF_Ofg" type="3010" element="_Dx87QBSgEeubdpURgF_Ofg">
+              <styles xmi:type="notation:FontStyle" xmi:id="_DysiIRSgEeubdpURgF_Ofg" fontColor="2697711" fontName="Cantarell" fontHeight="8"/>
+              <layoutConstraint xmi:type="notation:Location" xmi:id="_DysiIhSgEeubdpURgF_Ofg"/>
+            </children>
+            <children xmi:type="notation:Node" xmi:id="_VzMTIBSgEeubdpURgF_Ofg" type="3010" element="_Vyz4oBSgEeubdpURgF_Ofg">
+              <styles xmi:type="notation:FontStyle" xmi:id="_VzMTIRSgEeubdpURgF_Ofg" fontColor="2697711" fontName="Cantarell" fontHeight="8"/>
+              <layoutConstraint xmi:type="notation:Location" xmi:id="_VzMTIhSgEeubdpURgF_Ofg"/>
+            </children>
+            <styles xmi:type="notation:SortingStyle" xmi:id="_nmJMoRR6EeuFZNgytReO9g"/>
+            <styles xmi:type="notation:FilteringStyle" xmi:id="_nmJMohR6EeuFZNgytReO9g"/>
+          </children>
+          <styles xmi:type="notation:ShapeStyle" xmi:id="_nmEUIRR6EeuFZNgytReO9g" fontName="Cantarell" fontHeight="8"/>
+          <layoutConstraint xmi:type="notation:Bounds" xmi:id="_nmEUIhR6EeuFZNgytReO9g" x="399" y="150" width="168" height="94"/>
+        </children>
         <styles xmi:type="notation:DiagramStyle" xmi:id="_n6t0okwrEeqwHIzbUoB2lQ"/>
         <edges xmi:type="notation:Edge" xmi:id="_rIxDUNrpEeq_CZJuNlb8eA" type="4001" element="_rHvIkNrpEeq_CZJuNlb8eA" source="_rIUXYNrpEeq_CZJuNlb8eA" target="_rIaeANrpEeq_CZJuNlb8eA">
           <children xmi:type="notation:Node" xmi:id="_rIyRcNrpEeq_CZJuNlb8eA" type="6001">
@@ -1214,6 +1235,38 @@
           <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_okvnkAIeEeuURJHBrP3sSA" id="(0.5561224489795918,0.01020408163265306)"/>
           <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_okvnkQIeEeuURJHBrP3sSA" id="(0.5,0.5)"/>
         </edges>
+        <edges xmi:type="notation:Edge" xmi:id="_Jw93YBSgEeubdpURgF_Ofg" type="4001" element="_JwhyjBSgEeubdpURgF_Ofg" source="_nmEUIBR6EeuFZNgytReO9g" target="_rIc6Q9rpEeq_CZJuNlb8eA">
+          <children xmi:type="notation:Node" xmi:id="_Jw-ecBSgEeubdpURgF_Ofg" type="6001">
+            <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Jw-ecRSgEeubdpURgF_Ofg" x="1" y="-10"/>
+          </children>
+          <children xmi:type="notation:Node" xmi:id="_Jw-echSgEeubdpURgF_Ofg" type="6002">
+            <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Jw-ecxSgEeubdpURgF_Ofg" x="4" y="10"/>
+          </children>
+          <children xmi:type="notation:Node" xmi:id="_Jw-edBSgEeubdpURgF_Ofg" type="6003">
+            <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Jw-edRSgEeubdpURgF_Ofg" x="-11" y="10"/>
+          </children>
+          <styles xmi:type="notation:ConnectorStyle" xmi:id="_Jw93YRSgEeubdpURgF_Ofg" routing="Rectilinear"/>
+          <styles xmi:type="notation:FontStyle" xmi:id="_Jw93YhSgEeubdpURgF_Ofg" fontColor="7490599" fontName="Cantarell" fontHeight="8"/>
+          <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_Jw93YxSgEeubdpURgF_Ofg" points="[0, 22, 99, -33]$[-84, 22, 15, -33]$[-84, 55, 15, 0]"/>
+          <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_Jw-edhSgEeubdpURgF_Ofg" id="(0.0,0.21739130434782608)"/>
+          <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_Jw-edxSgEeubdpURgF_Ofg" id="(0.7142857142857143,0.0)"/>
+        </edges>
+        <edges xmi:type="notation:Edge" xmi:id="_NZ8JMBSgEeubdpURgF_Ofg" type="4001" element="_NZe2MBSgEeubdpURgF_Ofg" source="_rIevcNrpEeq_CZJuNlb8eA" target="_nmEUIBR6EeuFZNgytReO9g">
+          <children xmi:type="notation:Node" xmi:id="_NZ8JNBSgEeubdpURgF_Ofg" type="6001">
+            <layoutConstraint xmi:type="notation:Bounds" xmi:id="_NZ8JNRSgEeubdpURgF_Ofg" x="-18" y="-10"/>
+          </children>
+          <children xmi:type="notation:Node" xmi:id="_NZ8JNhSgEeubdpURgF_Ofg" type="6002">
+            <layoutConstraint xmi:type="notation:Bounds" xmi:id="_NZ8JNxSgEeubdpURgF_Ofg" x="-5" y="10"/>
+          </children>
+          <children xmi:type="notation:Node" xmi:id="_NZ8JOBSgEeubdpURgF_Ofg" type="6003">
+            <layoutConstraint xmi:type="notation:Bounds" xmi:id="_NZ8JORSgEeubdpURgF_Ofg" x="-28" y="-13"/>
+          </children>
+          <styles xmi:type="notation:ConnectorStyle" xmi:id="_NZ8JMRSgEeubdpURgF_Ofg" routing="Rectilinear"/>
+          <styles xmi:type="notation:FontStyle" xmi:id="_NZ8JMhSgEeubdpURgF_Ofg" fontColor="7490599" fontName="Cantarell" fontHeight="8"/>
+          <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_NZ8JMxSgEeubdpURgF_Ofg" points="[0, 0, 148, 70]$[-56, 0, 92, 70]$[-56, -70, 92, 0]$[-100, -70, 48, 0]"/>
+          <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_NZ8wQBSgEeubdpURgF_Ofg" id="(0.0,0.12244897959183673)"/>
+          <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_NZ8wQRSgEeubdpURgF_Ofg" id="(0.7108433734939759,0.10869565217391304)"/>
+        </edges>
       </data>
     </ownedAnnotationEntries>
     <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_rGcIENrpEeq_CZJuNlb8eA" name="Task" tooltipText="" outgoingEdges="_rHvIkNrpEeq_CZJuNlb8eA _rH2dUNrpEeq_CZJuNlb8eA _rH3EZtrpEeq_CZJuNlb8eA _rH3rdtrpEeq_CZJuNlb8eA _rH4ShtrpEeq_CZJuNlb8eA _qKPzVtruEeq_CZJuNlb8eA" incomingEdges="_rH4ShtrpEeq_CZJuNlb8eA _rH9LBtrpEeq_CZJuNlb8eA _rH-ZINrpEeq_CZJuNlb8eA _v_ZPldrzEeq_CZJuNlb8eA" width="12" height="10">
@@ -1380,7 +1433,7 @@
       <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
       <arrangeConstraints>KEEP_SIZE</arrangeConstraints>
       <arrangeConstraints>KEEP_RATIO</arrangeConstraints>
-      <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_YjOlSQiAEeuvnuckpM_LRQ" borderSize="1" borderSizeComputationExpression="1" backgroundStyle="Liquid" foregroundColor="255,252,216">
+      <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_rYWjlRhXEeuGKdtTSc3Y2w" borderSize="1" borderSizeComputationExpression="1" backgroundStyle="Liquid" foregroundColor="255,252,216">
         <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@style"/>
       </ownedStyle>
       <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']"/>
@@ -1400,6 +1453,14 @@
         </ownedStyle>
         <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/>
       </ownedElements>
+      <ownedElements xmi:type="diagram:DNodeListElement" uid="_qvAEkBhXEeuGKdtTSc3Y2w" name="title : EString" tooltipText="">
+        <target xmi:type="ecore:EAttribute" href="Skills.ecore#//User/title"/>
+        <semanticElements xmi:type="ecore:EAttribute" href="Skills.ecore#//User/title"/>
+        <ownedStyle xmi:type="diagram:BundledImage" uid="_syoJNhhXEeuGKdtTSc3Y2w" labelAlignment="LEFT" description="_paxGE-wkEeq5FfcjywLUwQ">
+          <labelFormat>bold</labelFormat>
+        </ownedStyle>
+        <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/>
+      </ownedElements>
       <ownedElements xmi:type="diagram:DNodeListElement" uid="_rHZxYtrpEeq_CZJuNlb8eA" name="addTask(task Task) : UserTask" tooltipText="addTask(task) : UserTask">
         <target xmi:type="ecore:EOperation" href="Skills.ecore#//User/addTask"/>
         <semanticElements xmi:type="ecore:EOperation" href="Skills.ecore#//User/addTask"/>
@@ -1445,7 +1506,7 @@
         <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='Operation']"/>
       </ownedElements>
     </ownedDiagramElements>
-    <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_rGpjcdrpEeq_CZJuNlb8eA" name="Skill" tooltipText="" outgoingEdges="_rH785trpEeq_CZJuNlb8eA _f_dz6AIeEeuURJHBrP3sSA" incomingEdges="_rH5goNrpEeq_CZJuNlb8eA _rH7V1trpEeq_CZJuNlb8eA _rH_AM9rpEeq_CZJuNlb8eA _rIA1YNrpEeq_CZJuNlb8eA _2Tg-MNyOEeq6k6a2XVHNwA" width="12" height="10">
+    <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_rGpjcdrpEeq_CZJuNlb8eA" name="Skill" tooltipText="" outgoingEdges="_rH785trpEeq_CZJuNlb8eA _f_dz6AIeEeuURJHBrP3sSA" incomingEdges="_rH5goNrpEeq_CZJuNlb8eA _rH7V1trpEeq_CZJuNlb8eA _rH_AM9rpEeq_CZJuNlb8eA _rIA1YNrpEeq_CZJuNlb8eA _2Tg-MNyOEeq6k6a2XVHNwA _JwhyjBSgEeubdpURgF_Ofg" width="12" height="10">
       <target xmi:type="ecore:EClass" href="Skills.ecore#//Skill"/>
       <semanticElements xmi:type="ecore:EClass" href="Skills.ecore#//Skill"/>
       <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
@@ -1572,13 +1633,13 @@
         <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='Operation']"/>
       </ownedElements>
     </ownedDiagramElements>
-    <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_rGt04NrpEeq_CZJuNlb8eA" name="Quest" tooltipText="" outgoingEdges="_rH-ZINrpEeq_CZJuNlb8eA _rH_AM9rpEeq_CZJuNlb8eA" width="12" height="10">
+    <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_rGt04NrpEeq_CZJuNlb8eA" name="Quest" tooltipText="" outgoingEdges="_rH-ZINrpEeq_CZJuNlb8eA _rH_AM9rpEeq_CZJuNlb8eA _NZe2MBSgEeubdpURgF_Ofg" width="12" height="10">
       <target xmi:type="ecore:EClass" href="Skills.ecore#//Quest"/>
       <semanticElements xmi:type="ecore:EClass" href="Skills.ecore#//Quest"/>
       <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
       <arrangeConstraints>KEEP_SIZE</arrangeConstraints>
       <arrangeConstraints>KEEP_RATIO</arrangeConstraints>
-      <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_rGub8NrpEeq_CZJuNlb8eA" borderSize="1" borderSizeComputationExpression="1" backgroundStyle="Liquid" foregroundColor="255,252,216">
+      <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_TfJM5RSgEeubdpURgF_Ofg" borderSize="1" borderSizeComputationExpression="1" backgroundStyle="Liquid" foregroundColor="255,252,216">
         <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@style"/>
       </ownedStyle>
       <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']"/>
@@ -2524,6 +2585,52 @@
       </ownedStyle>
       <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']"/>
     </ownedDiagramElements>
+    <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_nlVUUBR6EeuFZNgytReO9g" name="UserTitle" tooltipText="" outgoingEdges="_JwhyjBSgEeubdpURgF_Ofg" incomingEdges="_NZe2MBSgEeubdpURgF_Ofg" width="12" height="10">
+      <target xmi:type="ecore:EClass" href="Skills.ecore#//UserTitle"/>
+      <semanticElements xmi:type="ecore:EClass" href="Skills.ecore#//UserTitle"/>
+      <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
+      <arrangeConstraints>KEEP_SIZE</arrangeConstraints>
+      <arrangeConstraints>KEEP_RATIO</arrangeConstraints>
+      <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_mWX3cRhXEeuGKdtTSc3Y2w" borderSize="1" borderSizeComputationExpression="1" backgroundStyle="Liquid" foregroundColor="255,252,216">
+        <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@style"/>
+      </ownedStyle>
+      <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']"/>
+      <ownedElements xmi:type="diagram:DNodeListElement" uid="_Dx87QBSgEeubdpURgF_Ofg" name="minLevel : EInt" tooltipText="">
+        <target xmi:type="ecore:EAttribute" href="Skills.ecore#//UserTitle/minLevel"/>
+        <semanticElements xmi:type="ecore:EAttribute" href="Skills.ecore#//UserTitle/minLevel"/>
+        <ownedStyle xmi:type="diagram:BundledImage" uid="_GaTdkRSgEeubdpURgF_Ofg" labelAlignment="LEFT" description="_paxGE-wkEeq5FfcjywLUwQ">
+          <labelFormat>bold</labelFormat>
+        </ownedStyle>
+        <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/>
+      </ownedElements>
+      <ownedElements xmi:type="diagram:DNodeListElement" uid="_Vyz4oBSgEeubdpURgF_Ofg" name="displayString : EString" tooltipText="">
+        <target xmi:type="ecore:EAttribute" href="Skills.ecore#//UserTitle/displayString"/>
+        <semanticElements xmi:type="ecore:EAttribute" href="Skills.ecore#//UserTitle/displayString"/>
+        <ownedStyle xmi:type="diagram:BundledImage" uid="_XMclERSgEeubdpURgF_Ofg" labelAlignment="LEFT" description="_paxGE-wkEeq5FfcjywLUwQ">
+          <labelFormat>bold</labelFormat>
+        </ownedStyle>
+        <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/>
+      </ownedElements>
+    </ownedDiagramElements>
+    <ownedDiagramElements xmi:type="diagram:DEdge" uid="_JwhyjBSgEeubdpURgF_Ofg" name="[0..1] skill" sourceNode="_nlVUUBR6EeuFZNgytReO9g" targetNode="_rGpjcdrpEeq_CZJuNlb8eA">
+      <target xmi:type="ecore:EReference" href="Skills.ecore#//UserTitle/skill"/>
+      <semanticElements xmi:type="ecore:EReference" href="Skills.ecore#//UserTitle/skill"/>
+      <ownedStyle xmi:type="diagram:EdgeStyle" uid="_rIGAZhhfEeuGKdtTSc3Y2w" routingStyle="manhattan" strokeColor="0,0,0">
+        <description xmi:type="style:EdgeStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC_EReference']/@style"/>
+        <centerLabelStyle xmi:type="diagram:CenterLabelStyle" uid="_rIGAaBhfEeuGKdtTSc3Y2w" showIcon="false"/>
+        <endLabelStyle xmi:type="diagram:EndLabelStyle" uid="_rIGAZxhfEeuGKdtTSc3Y2w" labelSize="6" showIcon="false" labelColor="39,76,114"/>
+      </ownedStyle>
+      <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC_EReference']"/>
+    </ownedDiagramElements>
+    <ownedDiagramElements xmi:type="diagram:DEdge" uid="_NZe2MBSgEeubdpURgF_Ofg" name="[0..*] userTitles" sourceNode="_rGt04NrpEeq_CZJuNlb8eA" targetNode="_nlVUUBR6EeuFZNgytReO9g">
+      <target xmi:type="ecore:EReference" href="Skills.ecore#//Quest/userTitles"/>
+      <semanticElements xmi:type="ecore:EReference" href="Skills.ecore#//Quest/userTitles"/>
+      <ownedStyle xmi:type="diagram:EdgeStyle" uid="_QnzMUBSgEeubdpURgF_Ofg" description="_rH45kNrpEeq_CZJuNlb8eA" sourceArrow="FillDiamond" routingStyle="manhattan" strokeColor="0,0,0">
+        <centerLabelStyle xmi:type="diagram:CenterLabelStyle" uid="_QnzMUhSgEeubdpURgF_Ofg" showIcon="false"/>
+        <endLabelStyle xmi:type="diagram:EndLabelStyle" uid="_QnzMURSgEeubdpURgF_Ofg" labelSize="6" showIcon="false" labelColor="39,76,114"/>
+      </ownedStyle>
+      <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC_EReference']"/>
+    </ownedDiagramElements>
     <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/>
     <filterVariableHistory xmi:type="diagram:FilterVariableHistory" uid="_n5CZoEwrEeqwHIzbUoB2lQ"/>
     <activatedLayers xmi:type="description_1:Layer" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer"/>
diff --git a/plugins/org.eclipse.skills/resources/default.skills b/plugins/org.eclipse.skills/resources/default.skills
index 66df940..8db9c34 100644
--- a/plugins/org.eclipse.skills/resources/default.skills
+++ b/plugins/org.eclipse.skills/resources/default.skills
@@ -12,4 +12,19 @@
     <description text="Your knowledge about features of the application."/>
     <progression xsi:type="skills:FactorProgression"/>
   </skills>
+  <userTitles minLevel="1" displayString="the dreadful"/>
+  <userTitles minLevel="3" displayString="untrained user"/>
+  <userTitles minLevel="5" displayString="Apprentice"/>
+  <userTitles minLevel="10" displayString="basic user"/>
+  <userTitles minLevel="15" displayString="the talented"/>
+  <userTitles minLevel="20" displayString="trained user"/>
+  <userTitles minLevel="25" displayString="the capable"/>
+  <userTitles minLevel="30" displayString="competent user"/>
+  <userTitles minLevel="45" displayString="the skilled"/>
+  <userTitles minLevel="50" displayString="Expert"/>
+  <userTitles minLevel="60" displayString="Master"/>
+  <userTitles minLevel="70" displayString="Grand Master"/>
+  <userTitles minLevel="80" displayString="epic user"/>
+  <userTitles minLevel="90" displayString="the legendary"/>
+  <userTitles minLevel="100" displayString="godlike user"/>
 </skills:Quest>
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IQuest.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IQuest.java
index a6168be..4a0d2c7 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IQuest.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IQuest.java
@@ -17,6 +17,7 @@
  *   <li>{@link org.eclipse.skills.model.IQuest#getTasks <em>Tasks</em>}</li>
  *   <li>{@link org.eclipse.skills.model.IQuest#getSkills <em>Skills</em>}</li>
  *   <li>{@link org.eclipse.skills.model.IQuest#getTitle <em>Title</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.IQuest#getUserTitles <em>User Titles</em>}</li>
  * </ul>
  *
  * @see org.eclipse.skills.model.ISkillsPackage#getQuest()
@@ -70,4 +71,16 @@
 	 */
 	void setTitle(String value);
 
+	/**
+	 * Returns the value of the '<em><b>User Titles</b></em>' containment reference list.
+	 * The list contents are of type {@link org.eclipse.skills.model.IUserTitle}.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the value of the '<em>User Titles</em>' containment reference list.
+	 * @see org.eclipse.skills.model.ISkillsPackage#getQuest_UserTitles()
+	 * @model containment="true"
+	 * @generated
+	 */
+	EList<IUserTitle> getUserTitles();
+
 } // IQuest
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsFactory.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsFactory.java
index b8b65ed..38d63de 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsFactory.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsFactory.java
@@ -211,6 +211,15 @@
 	IFactorProgression createFactorProgression();
 
 	/**
+	 * Returns a new object of class '<em>User Title</em>'.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return a new object of class '<em>User Title</em>'.
+	 * @generated
+	 */
+	IUserTitle createUserTitle();
+
+	/**
 	 * Returns the package supported by this factory.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsPackage.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsPackage.java
index 27aadec..0991d24 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsPackage.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/ISkillsPackage.java
@@ -389,13 +389,22 @@
 	int USER__BADGES = 5;
 
 	/**
+	 * The feature id for the '<em><b>Title</b></em>' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 * @ordered
+	 */
+	int USER__TITLE = 6;
+
+	/**
 	 * The number of structural features of the '<em>User</em>' class.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
 	 * @generated
 	 * @ordered
 	 */
-	int USER_FEATURE_COUNT = 6;
+	int USER_FEATURE_COUNT = 7;
 
 	/**
 	 * The operation id for the '<em>Add Task</em>' operation.
@@ -689,13 +698,22 @@
 	int QUEST__TITLE = 2;
 
 	/**
+	 * The feature id for the '<em><b>User Titles</b></em>' containment reference list.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 * @ordered
+	 */
+	int QUEST__USER_TITLES = 3;
+
+	/**
 	 * The number of structural features of the '<em>Quest</em>' class.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
 	 * @generated
 	 * @ordered
 	 */
-	int QUEST_FEATURE_COUNT = 3;
+	int QUEST_FEATURE_COUNT = 4;
 
 	/**
 	 * The number of operations of the '<em>Quest</em>' class.
@@ -1977,6 +1995,61 @@
 	int FACTOR_PROGRESSION_OPERATION_COUNT = LEVEL_PROGRESSION_OPERATION_COUNT + 0;
 
 	/**
+	 * The meta object id for the '{@link org.eclipse.skills.model.impl.MUserTitle <em>User Title</em>}' class.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see org.eclipse.skills.model.impl.MUserTitle
+	 * @see org.eclipse.skills.model.impl.MSkillsPackage#getUserTitle()
+	 * @generated
+	 */
+	int USER_TITLE = 27;
+
+	/**
+	 * The feature id for the '<em><b>Min Level</b></em>' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 * @ordered
+	 */
+	int USER_TITLE__MIN_LEVEL = 0;
+
+	/**
+	 * The feature id for the '<em><b>Skill</b></em>' reference.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 * @ordered
+	 */
+	int USER_TITLE__SKILL = 1;
+
+	/**
+	 * The feature id for the '<em><b>Display String</b></em>' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 * @ordered
+	 */
+	int USER_TITLE__DISPLAY_STRING = 2;
+
+	/**
+	 * The number of structural features of the '<em>User Title</em>' class.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 * @ordered
+	 */
+	int USER_TITLE_FEATURE_COUNT = 3;
+
+	/**
+	 * The number of operations of the '<em>User Title</em>' class.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 * @ordered
+	 */
+	int USER_TITLE_OPERATION_COUNT = 0;
+
+	/**
 	 * The meta object id for the '{@link org.eclipse.skills.model.LevelName <em>Level Name</em>}' enum.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
@@ -1984,7 +2057,7 @@
 	 * @see org.eclipse.skills.model.impl.MSkillsPackage#getLevelName()
 	 * @generated
 	 */
-	int LEVEL_NAME = 27;
+	int LEVEL_NAME = 28;
 
 	/**
 	 * The meta object id for the '<em>Date</em>' data type.
@@ -1994,7 +2067,7 @@
 	 * @see org.eclipse.skills.model.impl.MSkillsPackage#getDate()
 	 * @generated
 	 */
-	int DATE = 28;
+	int DATE = 29;
 
 
 	/**
@@ -2005,7 +2078,7 @@
 	 * @see org.eclipse.skills.model.impl.MSkillsPackage#getCustomDependencyDefinition()
 	 * @generated
 	 */
-	int CUSTOM_DEPENDENCY_DEFINITION = 29;
+	int CUSTOM_DEPENDENCY_DEFINITION = 30;
 
 
 	/**
@@ -2016,7 +2089,7 @@
 	 * @see org.eclipse.skills.model.impl.MSkillsPackage#getImageDescriptor()
 	 * @generated
 	 */
-	int IMAGE_DESCRIPTOR = 30;
+	int IMAGE_DESCRIPTOR = 31;
 
 	/**
 	 * The meta object id for the '<em>ISkill Service</em>' data type.
@@ -2026,7 +2099,7 @@
 	 * @see org.eclipse.skills.model.impl.MSkillsPackage#getISkillService()
 	 * @generated
 	 */
-	int ISKILL_SERVICE = 31;
+	int ISKILL_SERVICE = 32;
 
 
 	/**
@@ -2329,6 +2402,17 @@
 	EReference getUser_Badges();
 
 	/**
+	 * Returns the meta object for the attribute '{@link org.eclipse.skills.model.IUser#getTitle <em>Title</em>}'.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the meta object for the attribute '<em>Title</em>'.
+	 * @see org.eclipse.skills.model.IUser#getTitle()
+	 * @see #getUser()
+	 * @generated
+	 */
+	EAttribute getUser_Title();
+
+	/**
 	 * Returns the meta object for the '{@link org.eclipse.skills.model.IUser#addTask(org.eclipse.skills.model.ITask) <em>Add Task</em>}' operation.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
@@ -2612,6 +2696,17 @@
 	EAttribute getQuest_Title();
 
 	/**
+	 * Returns the meta object for the containment reference list '{@link org.eclipse.skills.model.IQuest#getUserTitles <em>User Titles</em>}'.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the meta object for the containment reference list '<em>User Titles</em>'.
+	 * @see org.eclipse.skills.model.IQuest#getUserTitles()
+	 * @see #getQuest()
+	 * @generated
+	 */
+	EReference getQuest_UserTitles();
+
+	/**
 	 * Returns the meta object for class '{@link org.eclipse.skills.model.IDependency <em>Dependency</em>}'.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
@@ -3082,6 +3177,49 @@
 	EAttribute getFactorProgression_XpFactor();
 
 	/**
+	 * Returns the meta object for class '{@link org.eclipse.skills.model.IUserTitle <em>User Title</em>}'.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the meta object for class '<em>User Title</em>'.
+	 * @see org.eclipse.skills.model.IUserTitle
+	 * @generated
+	 */
+	EClass getUserTitle();
+
+	/**
+	 * Returns the meta object for the attribute '{@link org.eclipse.skills.model.IUserTitle#getMinLevel <em>Min Level</em>}'.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the meta object for the attribute '<em>Min Level</em>'.
+	 * @see org.eclipse.skills.model.IUserTitle#getMinLevel()
+	 * @see #getUserTitle()
+	 * @generated
+	 */
+	EAttribute getUserTitle_MinLevel();
+
+	/**
+	 * Returns the meta object for the reference '{@link org.eclipse.skills.model.IUserTitle#getSkill <em>Skill</em>}'.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the meta object for the reference '<em>Skill</em>'.
+	 * @see org.eclipse.skills.model.IUserTitle#getSkill()
+	 * @see #getUserTitle()
+	 * @generated
+	 */
+	EReference getUserTitle_Skill();
+
+	/**
+	 * Returns the meta object for the attribute '{@link org.eclipse.skills.model.IUserTitle#getDisplayString <em>Display String</em>}'.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the meta object for the attribute '<em>Display String</em>'.
+	 * @see org.eclipse.skills.model.IUserTitle#getDisplayString()
+	 * @see #getUserTitle()
+	 * @generated
+	 */
+	EAttribute getUserTitle_DisplayString();
+
+	/**
 	 * Returns the meta object for enum '{@link org.eclipse.skills.model.LevelName <em>Level Name</em>}'.
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
@@ -3393,6 +3531,14 @@
 		EReference USER__BADGES = eINSTANCE.getUser_Badges();
 
 		/**
+		 * The meta object literal for the '<em><b>Title</b></em>' attribute feature.
+		 * <!-- begin-user-doc -->
+		 * <!-- end-user-doc -->
+		 * @generated
+		 */
+		EAttribute USER__TITLE = eINSTANCE.getUser_Title();
+
+		/**
 		 * The meta object literal for the '<em><b>Add Task</b></em>' operation.
 		 * <!-- begin-user-doc -->
 		 * <!-- end-user-doc -->
@@ -3615,6 +3761,14 @@
 		EAttribute QUEST__TITLE = eINSTANCE.getQuest_Title();
 
 		/**
+		 * The meta object literal for the '<em><b>User Titles</b></em>' containment reference list feature.
+		 * <!-- begin-user-doc -->
+		 * <!-- end-user-doc -->
+		 * @generated
+		 */
+		EReference QUEST__USER_TITLES = eINSTANCE.getQuest_UserTitles();
+
+		/**
 		 * The meta object literal for the '{@link org.eclipse.skills.model.impl.MDependency <em>Dependency</em>}' class.
 		 * <!-- begin-user-doc -->
 		 * <!-- end-user-doc -->
@@ -4013,6 +4167,40 @@
 		EAttribute FACTOR_PROGRESSION__XP_FACTOR = eINSTANCE.getFactorProgression_XpFactor();
 
 		/**
+		 * The meta object literal for the '{@link org.eclipse.skills.model.impl.MUserTitle <em>User Title</em>}' class.
+		 * <!-- begin-user-doc -->
+		 * <!-- end-user-doc -->
+		 * @see org.eclipse.skills.model.impl.MUserTitle
+		 * @see org.eclipse.skills.model.impl.MSkillsPackage#getUserTitle()
+		 * @generated
+		 */
+		EClass USER_TITLE = eINSTANCE.getUserTitle();
+
+		/**
+		 * The meta object literal for the '<em><b>Min Level</b></em>' attribute feature.
+		 * <!-- begin-user-doc -->
+		 * <!-- end-user-doc -->
+		 * @generated
+		 */
+		EAttribute USER_TITLE__MIN_LEVEL = eINSTANCE.getUserTitle_MinLevel();
+
+		/**
+		 * The meta object literal for the '<em><b>Skill</b></em>' reference feature.
+		 * <!-- begin-user-doc -->
+		 * <!-- end-user-doc -->
+		 * @generated
+		 */
+		EReference USER_TITLE__SKILL = eINSTANCE.getUserTitle_Skill();
+
+		/**
+		 * The meta object literal for the '<em><b>Display String</b></em>' attribute feature.
+		 * <!-- begin-user-doc -->
+		 * <!-- end-user-doc -->
+		 * @generated
+		 */
+		EAttribute USER_TITLE__DISPLAY_STRING = eINSTANCE.getUserTitle_DisplayString();
+
+		/**
 		 * The meta object literal for the '{@link org.eclipse.skills.model.LevelName <em>Level Name</em>}' enum.
 		 * <!-- begin-user-doc -->
 		 * <!-- end-user-doc -->
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IUser.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IUser.java
index 786adf9..ad1a41c 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IUser.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IUser.java
@@ -22,6 +22,7 @@
  *   <li>{@link org.eclipse.skills.model.IUser#getImageLocation <em>Image Location</em>}</li>
  *   <li>{@link org.eclipse.skills.model.IUser#getExperience <em>Experience</em>}</li>
  *   <li>{@link org.eclipse.skills.model.IUser#getBadges <em>Badges</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.IUser#getTitle <em>Title</em>}</li>
  * </ul>
  *
  * @see org.eclipse.skills.model.ISkillsPackage#getUser()
@@ -132,6 +133,28 @@
 	EList<IBadge> getBadges();
 
 	/**
+	 * Returns the value of the '<em><b>Title</b></em>' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the value of the '<em>Title</em>' attribute.
+	 * @see #setTitle(String)
+	 * @see org.eclipse.skills.model.ISkillsPackage#getUser_Title()
+	 * @model required="true"
+	 * @generated
+	 */
+	String getTitle();
+
+	/**
+	 * Sets the value of the '{@link org.eclipse.skills.model.IUser#getTitle <em>Title</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @param value the new value of the '<em>Title</em>' attribute.
+	 * @see #getTitle()
+	 * @generated
+	 */
+	void setTitle(String value);
+
+	/**
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
 	 * @model
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IUserTitle.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IUserTitle.java
new file mode 100644
index 0000000..c6eefed
--- /dev/null
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/IUserTitle.java
@@ -0,0 +1,92 @@
+/**
+ */
+package org.eclipse.skills.model;
+
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * <!-- begin-user-doc -->
+ * A representation of the model object '<em><b>User Title</b></em>'.
+ * <!-- end-user-doc -->
+ *
+ * <p>
+ * The following features are supported:
+ * </p>
+ * <ul>
+ *   <li>{@link org.eclipse.skills.model.IUserTitle#getMinLevel <em>Min Level</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.IUserTitle#getSkill <em>Skill</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.IUserTitle#getDisplayString <em>Display String</em>}</li>
+ * </ul>
+ *
+ * @see org.eclipse.skills.model.ISkillsPackage#getUserTitle()
+ * @model
+ * @generated
+ */
+public interface IUserTitle extends EObject {
+	/**
+	 * Returns the value of the '<em><b>Min Level</b></em>' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the value of the '<em>Min Level</em>' attribute.
+	 * @see #setMinLevel(int)
+	 * @see org.eclipse.skills.model.ISkillsPackage#getUserTitle_MinLevel()
+	 * @model required="true"
+	 * @generated
+	 */
+	int getMinLevel();
+
+	/**
+	 * Sets the value of the '{@link org.eclipse.skills.model.IUserTitle#getMinLevel <em>Min Level</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @param value the new value of the '<em>Min Level</em>' attribute.
+	 * @see #getMinLevel()
+	 * @generated
+	 */
+	void setMinLevel(int value);
+
+	/**
+	 * Returns the value of the '<em><b>Skill</b></em>' reference.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the value of the '<em>Skill</em>' reference.
+	 * @see #setSkill(ISkill)
+	 * @see org.eclipse.skills.model.ISkillsPackage#getUserTitle_Skill()
+	 * @model
+	 * @generated
+	 */
+	ISkill getSkill();
+
+	/**
+	 * Sets the value of the '{@link org.eclipse.skills.model.IUserTitle#getSkill <em>Skill</em>}' reference.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @param value the new value of the '<em>Skill</em>' reference.
+	 * @see #getSkill()
+	 * @generated
+	 */
+	void setSkill(ISkill value);
+
+	/**
+	 * Returns the value of the '<em><b>Display String</b></em>' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @return the value of the '<em>Display String</em>' attribute.
+	 * @see #setDisplayString(String)
+	 * @see org.eclipse.skills.model.ISkillsPackage#getUserTitle_DisplayString()
+	 * @model required="true"
+	 * @generated
+	 */
+	String getDisplayString();
+
+	/**
+	 * Sets the value of the '{@link org.eclipse.skills.model.IUserTitle#getDisplayString <em>Display String</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @param value the new value of the '<em>Display String</em>' attribute.
+	 * @see #getDisplayString()
+	 * @generated
+	 */
+	void setDisplayString(String value);
+
+} // IUserTitle
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadge.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadge.java
index 232338d..7764783 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadge.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadge.java
@@ -22,16 +22,16 @@
  * The following features are implemented:
  * </p>
  * <ul>
- * <li>{@link org.eclipse.skills.model.impl.MBadge#getTitle <em>Title</em>}</li>
- * <li>{@link org.eclipse.skills.model.impl.MBadge#getImageURL <em>Image URL</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.impl.MBadge#getTitle <em>Title</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.impl.MBadge#getImageURL <em>Image URL</em>}</li>
  * </ul>
  *
  * @generated
  */
 public class MBadge extends MinimalEObjectImpl.Container implements IBadge {
 	/**
-	 * The default value of the '{@link #getTitle() <em>Title</em>}' attribute. <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
+	 * The default value of the '{@link #getTitle() <em>Title</em>}' attribute.
+	 * <!-- begin-user-doc --> <!-- end-user-doc -->
 	 * @see #getTitle()
 	 * @generated
 	 * @ordered
@@ -39,8 +39,8 @@
 	protected static final String TITLE_EDEFAULT = null;
 
 	/**
-	 * The cached value of the '{@link #getTitle() <em>Title</em>}' attribute. <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
+	 * The cached value of the '{@link #getTitle() <em>Title</em>}' attribute.
+	 * <!-- begin-user-doc --> <!-- end-user-doc -->
 	 * @see #getTitle()
 	 * @generated
 	 * @ordered
@@ -48,8 +48,8 @@
 	protected String title = TITLE_EDEFAULT;
 
 	/**
-	 * The default value of the '{@link #getImageURL() <em>Image URL</em>}' attribute. <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
+	 * The default value of the '{@link #getImageURL() <em>Image URL</em>}' attribute.
+	 * <!-- begin-user-doc --> <!-- end-user-doc -->
 	 * @see #getImageURL()
 	 * @generated
 	 * @ordered
@@ -57,8 +57,8 @@
 	protected static final String IMAGE_URL_EDEFAULT = null;
 
 	/**
-	 * The cached value of the '{@link #getImageURL() <em>Image URL</em>}' attribute. <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
+	 * The cached value of the '{@link #getImageURL() <em>Image URL</em>}' attribute.
+	 * <!-- begin-user-doc --> <!-- end-user-doc -->
 	 * @see #getImageURL()
 	 * @generated
 	 * @ordered
@@ -67,7 +67,6 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	protected MBadge() {
@@ -76,7 +75,6 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
@@ -86,7 +84,6 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
@@ -96,12 +93,11 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public void setTitle(String newTitle) {
-		final String oldTitle = title;
+		String oldTitle = title;
 		title = newTitle;
 		if (eNotificationRequired())
 			eNotify(new ENotificationImpl(this, Notification.SET, ISkillsPackage.BADGE__TITLE, oldTitle, title));
@@ -109,7 +105,6 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
@@ -119,12 +114,11 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public void setImageURL(String newImageURL) {
-		final String oldImageURL = imageURL;
+		String oldImageURL = imageURL;
 		imageURL = newImageURL;
 		if (eNotificationRequired())
 			eNotify(new ENotificationImpl(this, Notification.SET, ISkillsPackage.BADGE__IMAGE_URL, oldImageURL, imageURL));
@@ -147,97 +141,90 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public Object eGet(int featureID, boolean resolve, boolean coreType) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE__TITLE:
-			return getTitle();
-		case ISkillsPackage.BADGE__IMAGE_URL:
-			return getImageURL();
+			case ISkillsPackage.BADGE__TITLE:
+				return getTitle();
+			case ISkillsPackage.BADGE__IMAGE_URL:
+				return getImageURL();
 		}
 		return super.eGet(featureID, resolve, coreType);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public void eSet(int featureID, Object newValue) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE__TITLE:
-			setTitle((String) newValue);
-			return;
-		case ISkillsPackage.BADGE__IMAGE_URL:
-			setImageURL((String) newValue);
-			return;
+			case ISkillsPackage.BADGE__TITLE:
+				setTitle((String)newValue);
+				return;
+			case ISkillsPackage.BADGE__IMAGE_URL:
+				setImageURL((String)newValue);
+				return;
 		}
 		super.eSet(featureID, newValue);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public void eUnset(int featureID) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE__TITLE:
-			setTitle(TITLE_EDEFAULT);
-			return;
-		case ISkillsPackage.BADGE__IMAGE_URL:
-			setImageURL(IMAGE_URL_EDEFAULT);
-			return;
+			case ISkillsPackage.BADGE__TITLE:
+				setTitle(TITLE_EDEFAULT);
+				return;
+			case ISkillsPackage.BADGE__IMAGE_URL:
+				setImageURL(IMAGE_URL_EDEFAULT);
+				return;
 		}
 		super.eUnset(featureID);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public boolean eIsSet(int featureID) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE__TITLE:
-			return TITLE_EDEFAULT == null ? title != null : !TITLE_EDEFAULT.equals(title);
-		case ISkillsPackage.BADGE__IMAGE_URL:
-			return IMAGE_URL_EDEFAULT == null ? imageURL != null : !IMAGE_URL_EDEFAULT.equals(imageURL);
+			case ISkillsPackage.BADGE__TITLE:
+				return TITLE_EDEFAULT == null ? title != null : !TITLE_EDEFAULT.equals(title);
+			case ISkillsPackage.BADGE__IMAGE_URL:
+				return IMAGE_URL_EDEFAULT == null ? imageURL != null : !IMAGE_URL_EDEFAULT.equals(imageURL);
 		}
 		return super.eIsSet(featureID);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public Object eInvoke(int operationID, EList<?> arguments) throws InvocationTargetException {
 		switch (operationID) {
-		case ISkillsPackage.BADGE___GET_IMAGE_DESCRIPTOR:
-			return getImageDescriptor();
+			case ISkillsPackage.BADGE___GET_IMAGE_DESCRIPTOR:
+				return getImageDescriptor();
 		}
 		return super.eInvoke(operationID, arguments);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public String toString() {
-		if (eIsProxy())
-			return super.toString();
+		if (eIsProxy()) return super.toString();
 
-		final StringBuilder result = new StringBuilder(super.toString());
+		StringBuilder result = new StringBuilder(super.toString());
 		result.append(" (title: ");
 		result.append(title);
 		result.append(", imageURL: ");
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadgeReward.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadgeReward.java
index c70ec0d..daf489a 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadgeReward.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MBadgeReward.java
@@ -27,15 +27,15 @@
  * The following features are implemented:
  * </p>
  * <ul>
- * <li>{@link org.eclipse.skills.model.impl.MBadgeReward#getBadge <em>Badge</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.impl.MBadgeReward#getBadge <em>Badge</em>}</li>
  * </ul>
  *
  * @generated
  */
 public class MBadgeReward extends MinimalEObjectImpl.Container implements IBadgeReward {
 	/**
-	 * The cached value of the '{@link #getBadge() <em>Badge</em>}' containment reference. <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
+	 * The cached value of the '{@link #getBadge() <em>Badge</em>}' containment reference.
+	 * <!-- begin-user-doc --> <!-- end-user-doc -->
 	 * @see #getBadge()
 	 * @generated
 	 * @ordered
@@ -44,7 +44,6 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	protected MBadgeReward() {
@@ -53,7 +52,6 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
@@ -63,7 +61,6 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
@@ -73,25 +70,20 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	public NotificationChain basicSetBadge(IBadge newBadge, NotificationChain msgs) {
-		final IBadge oldBadge = badge;
+		IBadge oldBadge = badge;
 		badge = newBadge;
 		if (eNotificationRequired()) {
-			final ENotificationImpl notification = new ENotificationImpl(this, Notification.SET, ISkillsPackage.BADGE_REWARD__BADGE, oldBadge, newBadge);
-			if (msgs == null)
-				msgs = notification;
-			else
-				msgs.add(notification);
+			ENotificationImpl notification = new ENotificationImpl(this, Notification.SET, ISkillsPackage.BADGE_REWARD__BADGE, oldBadge, newBadge);
+			if (msgs == null) msgs = notification; else msgs.add(notification);
 		}
 		return msgs;
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
@@ -99,13 +91,13 @@
 		if (newBadge != badge) {
 			NotificationChain msgs = null;
 			if (badge != null)
-				msgs = ((InternalEObject) badge).eInverseRemove(this, EOPPOSITE_FEATURE_BASE - ISkillsPackage.BADGE_REWARD__BADGE, null, msgs);
+				msgs = ((InternalEObject)badge).eInverseRemove(this, EOPPOSITE_FEATURE_BASE - ISkillsPackage.BADGE_REWARD__BADGE, null, msgs);
 			if (newBadge != null)
-				msgs = ((InternalEObject) newBadge).eInverseAdd(this, EOPPOSITE_FEATURE_BASE - ISkillsPackage.BADGE_REWARD__BADGE, null, msgs);
+				msgs = ((InternalEObject)newBadge).eInverseAdd(this, EOPPOSITE_FEATURE_BASE - ISkillsPackage.BADGE_REWARD__BADGE, null, msgs);
 			msgs = basicSetBadge(newBadge, msgs);
-			if (msgs != null)
-				msgs.dispatch();
-		} else if (eNotificationRequired())
+			if (msgs != null) msgs.dispatch();
+		}
+		else if (eNotificationRequired())
 			eNotify(new ENotificationImpl(this, Notification.SET, ISkillsPackage.BADGE_REWARD__BADGE, newBadge, newBadge));
 	}
 
@@ -131,87 +123,81 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public NotificationChain eInverseRemove(InternalEObject otherEnd, int featureID, NotificationChain msgs) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE_REWARD__BADGE:
-			return basicSetBadge(null, msgs);
+			case ISkillsPackage.BADGE_REWARD__BADGE:
+				return basicSetBadge(null, msgs);
 		}
 		return super.eInverseRemove(otherEnd, featureID, msgs);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public Object eGet(int featureID, boolean resolve, boolean coreType) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE_REWARD__BADGE:
-			return getBadge();
+			case ISkillsPackage.BADGE_REWARD__BADGE:
+				return getBadge();
 		}
 		return super.eGet(featureID, resolve, coreType);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public void eSet(int featureID, Object newValue) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE_REWARD__BADGE:
-			setBadge((IBadge) newValue);
-			return;
+			case ISkillsPackage.BADGE_REWARD__BADGE:
+				setBadge((IBadge)newValue);
+				return;
 		}
 		super.eSet(featureID, newValue);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public void eUnset(int featureID) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE_REWARD__BADGE:
-			setBadge((IBadge) null);
-			return;
+			case ISkillsPackage.BADGE_REWARD__BADGE:
+				setBadge((IBadge)null);
+				return;
 		}
 		super.eUnset(featureID);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public boolean eIsSet(int featureID) {
 		switch (featureID) {
-		case ISkillsPackage.BADGE_REWARD__BADGE:
-			return badge != null;
+			case ISkillsPackage.BADGE_REWARD__BADGE:
+				return badge != null;
 		}
 		return super.eIsSet(featureID);
 	}
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
-	 *
 	 * @generated
 	 */
 	@Override
 	public Object eInvoke(int operationID, EList<?> arguments) throws InvocationTargetException {
 		switch (operationID) {
-		case ISkillsPackage.BADGE_REWARD___PAY_OUT__IUSER:
-			payOut((IUser) arguments.get(0));
-			return null;
+			case ISkillsPackage.BADGE_REWARD___PAY_OUT__IUSER:
+				payOut((IUser)arguments.get(0));
+				return null;
 		}
 		return super.eInvoke(operationID, arguments);
 	}
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MQuest.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MQuest.java
index 129cd25..842cb1c 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MQuest.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MQuest.java
@@ -18,6 +18,7 @@
 import org.eclipse.skills.model.ISkill;
 import org.eclipse.skills.model.ISkillsPackage;
 import org.eclipse.skills.model.ITask;
+import org.eclipse.skills.model.IUserTitle;
 
 /**
  * <!-- begin-user-doc -->
@@ -30,6 +31,7 @@
  *   <li>{@link org.eclipse.skills.model.impl.MQuest#getTasks <em>Tasks</em>}</li>
  *   <li>{@link org.eclipse.skills.model.impl.MQuest#getSkills <em>Skills</em>}</li>
  *   <li>{@link org.eclipse.skills.model.impl.MQuest#getTitle <em>Title</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.impl.MQuest#getUserTitles <em>User Titles</em>}</li>
  * </ul>
  *
  * @generated
@@ -76,6 +78,16 @@
 	protected String title = TITLE_EDEFAULT;
 
 	/**
+	 * The cached value of the '{@link #getUserTitles() <em>User Titles</em>}' containment reference list.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getUserTitles()
+	 * @generated
+	 * @ordered
+	 */
+	protected EList<IUserTitle> userTitles;
+
+	/**
 	 * <!-- begin-user-doc -->
 	 * <!-- end-user-doc -->
 	 * @generated
@@ -149,12 +161,27 @@
 	 * @generated
 	 */
 	@Override
+	public EList<IUserTitle> getUserTitles() {
+		if (userTitles == null) {
+			userTitles = new EObjectContainmentEList<IUserTitle>(IUserTitle.class, this, ISkillsPackage.QUEST__USER_TITLES);
+		}
+		return userTitles;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
 	public NotificationChain eInverseRemove(InternalEObject otherEnd, int featureID, NotificationChain msgs) {
 		switch (featureID) {
 			case ISkillsPackage.QUEST__TASKS:
 				return ((InternalEList<?>)getTasks()).basicRemove(otherEnd, msgs);
 			case ISkillsPackage.QUEST__SKILLS:
 				return ((InternalEList<?>)getSkills()).basicRemove(otherEnd, msgs);
+			case ISkillsPackage.QUEST__USER_TITLES:
+				return ((InternalEList<?>)getUserTitles()).basicRemove(otherEnd, msgs);
 		}
 		return super.eInverseRemove(otherEnd, featureID, msgs);
 	}
@@ -173,6 +200,8 @@
 				return getSkills();
 			case ISkillsPackage.QUEST__TITLE:
 				return getTitle();
+			case ISkillsPackage.QUEST__USER_TITLES:
+				return getUserTitles();
 		}
 		return super.eGet(featureID, resolve, coreType);
 	}
@@ -197,6 +226,10 @@
 			case ISkillsPackage.QUEST__TITLE:
 				setTitle((String)newValue);
 				return;
+			case ISkillsPackage.QUEST__USER_TITLES:
+				getUserTitles().clear();
+				getUserTitles().addAll((Collection<? extends IUserTitle>)newValue);
+				return;
 		}
 		super.eSet(featureID, newValue);
 	}
@@ -218,6 +251,9 @@
 			case ISkillsPackage.QUEST__TITLE:
 				setTitle(TITLE_EDEFAULT);
 				return;
+			case ISkillsPackage.QUEST__USER_TITLES:
+				getUserTitles().clear();
+				return;
 		}
 		super.eUnset(featureID);
 	}
@@ -236,6 +272,8 @@
 				return skills != null && !skills.isEmpty();
 			case ISkillsPackage.QUEST__TITLE:
 				return TITLE_EDEFAULT == null ? title != null : !TITLE_EDEFAULT.equals(title);
+			case ISkillsPackage.QUEST__USER_TITLES:
+				return userTitles != null && !userTitles.isEmpty();
 		}
 		return super.eIsSet(featureID);
 	}
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsFactory.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsFactory.java
index b644860..ed3ca6d 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsFactory.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsFactory.java
@@ -82,6 +82,7 @@
 			case ISkillsPackage.SKILL_DEPENDENCY: return createSkillDependency();
 			case ISkillsPackage.HINT: return createHint();
 			case ISkillsPackage.FACTOR_PROGRESSION: return createFactorProgression();
+			case ISkillsPackage.USER_TITLE: return createUserTitle();
 			default:
 				throw new IllegalArgumentException("The class '" + eClass.getName() + "' is not a valid classifier");
 		}
@@ -365,6 +366,17 @@
 	 * <!-- end-user-doc -->
 	 * @generated
 	 */
+	@Override
+	public IUserTitle createUserTitle() {
+		MUserTitle userTitle = new MUserTitle();
+		return userTitle;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
 	public LevelName createLevelNameFromString(EDataType eDataType, String initialValue) {
 		LevelName result = LevelName.get(initialValue);
 		if (result == null) throw new IllegalArgumentException("The value '" + initialValue + "' is not a valid enumerator of '" + eDataType.getName() + "'");
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsPackage.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsPackage.java
index 5689bf8..44b4d48 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsPackage.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MSkillsPackage.java
@@ -45,6 +45,7 @@
 import org.eclipse.skills.model.IUser;
 import org.eclipse.skills.model.IUserDependency;
 import org.eclipse.skills.model.IUserTask;
+import org.eclipse.skills.model.IUserTitle;
 import org.eclipse.skills.model.LevelName;
 import org.eclipse.skills.service.ISkillService;
 
@@ -249,6 +250,13 @@
 	 * <!-- end-user-doc -->
 	 * @generated
 	 */
+	private EClass userTitleEClass = null;
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
 	private EEnum levelNameEEnum = null;
 
 	/**
@@ -626,6 +634,16 @@
 	 * @generated
 	 */
 	@Override
+	public EAttribute getUser_Title() {
+		return (EAttribute)userEClass.getEStructuralFeatures().get(6);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
 	public EOperation getUser__AddTask__ITask() {
 		return userEClass.getEOperations().get(0);
 	}
@@ -896,6 +914,16 @@
 	 * @generated
 	 */
 	@Override
+	public EReference getQuest_UserTitles() {
+		return (EReference)questEClass.getEStructuralFeatures().get(3);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
 	public EClass getDependency() {
 		return dependencyEClass;
 	}
@@ -1346,6 +1374,46 @@
 	 * @generated
 	 */
 	@Override
+	public EClass getUserTitle() {
+		return userTitleEClass;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public EAttribute getUserTitle_MinLevel() {
+		return (EAttribute)userTitleEClass.getEStructuralFeatures().get(0);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public EReference getUserTitle_Skill() {
+		return (EReference)userTitleEClass.getEStructuralFeatures().get(1);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public EAttribute getUserTitle_DisplayString() {
+		return (EAttribute)userTitleEClass.getEStructuralFeatures().get(2);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
 	public EEnum getLevelName() {
 		return levelNameEEnum;
 	}
@@ -1451,6 +1519,7 @@
 		createEAttribute(userEClass, USER__IMAGE_LOCATION);
 		createEReference(userEClass, USER__EXPERIENCE);
 		createEReference(userEClass, USER__BADGES);
+		createEAttribute(userEClass, USER__TITLE);
 		createEOperation(userEClass, USER___ADD_TASK__ITASK);
 		createEOperation(userEClass, USER___GET_SKILL__ISKILL);
 		createEOperation(userEClass, USER___CONSUME__IREWARD);
@@ -1481,6 +1550,7 @@
 		createEReference(questEClass, QUEST__TASKS);
 		createEReference(questEClass, QUEST__SKILLS);
 		createEAttribute(questEClass, QUEST__TITLE);
+		createEReference(questEClass, QUEST__USER_TITLES);
 
 		dependencyEClass = createEClass(DEPENDENCY);
 		createEAttribute(dependencyEClass, DEPENDENCY__FULFILLED);
@@ -1546,6 +1616,11 @@
 		createEAttribute(factorProgressionEClass, FACTOR_PROGRESSION__BASE_XP_NEEDED);
 		createEAttribute(factorProgressionEClass, FACTOR_PROGRESSION__XP_FACTOR);
 
+		userTitleEClass = createEClass(USER_TITLE);
+		createEAttribute(userTitleEClass, USER_TITLE__MIN_LEVEL);
+		createEReference(userTitleEClass, USER_TITLE__SKILL);
+		createEAttribute(userTitleEClass, USER_TITLE__DISPLAY_STRING);
+
 		// Create enums
 		levelNameEEnum = createEEnum(LEVEL_NAME);
 
@@ -1641,6 +1716,7 @@
 		initEAttribute(getUser_ImageLocation(), ecorePackage.getEString(), "imageLocation", null, 0, 1, IUser.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 		initEReference(getUser_Experience(), this.getSkill(), null, "experience", null, 1, 1, IUser.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 		initEReference(getUser_Badges(), this.getBadge(), null, "badges", null, 0, -1, IUser.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
+		initEAttribute(getUser_Title(), ecorePackage.getEString(), "title", null, 1, 1, IUser.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 
 		op = initEOperation(getUser__AddTask__ITask(), this.getUserTask(), "addTask", 0, 1, IS_UNIQUE, IS_ORDERED);
 		addEParameter(op, this.getTask(), "task", 0, 1, IS_UNIQUE, IS_ORDERED);
@@ -1687,6 +1763,7 @@
 		initEReference(getQuest_Tasks(), this.getTask(), null, "tasks", null, 0, -1, IQuest.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 		initEReference(getQuest_Skills(), this.getSkill(), null, "skills", null, 0, -1, IQuest.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 		initEAttribute(getQuest_Title(), ecorePackage.getEString(), "title", null, 0, 1, IQuest.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
+		initEReference(getQuest_UserTitles(), this.getUserTitle(), null, "userTitles", null, 0, -1, IQuest.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 
 		initEClass(dependencyEClass, IDependency.class, "Dependency", IS_ABSTRACT, !IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS);
 		initEAttribute(getDependency_Fulfilled(), ecorePackage.getEBoolean(), "fulfilled", "false", 0, 1, IDependency.class, IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
@@ -1761,6 +1838,11 @@
 		initEAttribute(getFactorProgression_BaseXpNeeded(), ecorePackage.getEInt(), "baseXpNeeded", "100", 1, 1, IFactorProgression.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 		initEAttribute(getFactorProgression_XpFactor(), ecorePackage.getEDouble(), "xpFactor", "2.4", 1, 1, IFactorProgression.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
 
+		initEClass(userTitleEClass, IUserTitle.class, "UserTitle", !IS_ABSTRACT, !IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS);
+		initEAttribute(getUserTitle_MinLevel(), ecorePackage.getEInt(), "minLevel", null, 1, 1, IUserTitle.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
+		initEReference(getUserTitle_Skill(), this.getSkill(), null, "skill", null, 0, 1, IUserTitle.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_COMPOSITE, IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
+		initEAttribute(getUserTitle_DisplayString(), ecorePackage.getEString(), "displayString", null, 1, 1, IUserTitle.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED);
+
 		// Initialize enums and add enum literals
 		initEEnum(levelNameEEnum, LevelName.class, "LevelName");
 		addEEnumLiteral(levelNameEEnum, LevelName.APPRENTICE);
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MTask.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MTask.java
index ede4e64..4785b71 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MTask.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MTask.java
@@ -16,6 +16,7 @@
 import org.eclipse.emf.ecore.impl.MinimalEObjectImpl;
 import org.eclipse.emf.ecore.util.EObjectContainmentEList;
 import org.eclipse.emf.ecore.util.InternalEList;
+import org.eclipse.skills.BrokerTools;
 import org.eclipse.skills.model.IDependency;
 import org.eclipse.skills.model.IDescription;
 import org.eclipse.skills.model.IHint;
@@ -348,7 +349,7 @@
 						if (getRequirement().isFulfilled()) {
 							getRequirement().eAdapters().remove(this);
 
-							getSkillService().reportTaskReady(MTask.this);
+							BrokerTools.post(ISkillService.EVENT_TASK_READY, MTask.this);
 						}
 					}
 				}
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MUser.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MUser.java
index 32462c1..87328e7 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MUser.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MUser.java
@@ -47,6 +47,7 @@
  *   <li>{@link org.eclipse.skills.model.impl.MUser#getImageLocation <em>Image Location</em>}</li>
  *   <li>{@link org.eclipse.skills.model.impl.MUser#getExperience <em>Experience</em>}</li>
  *   <li>{@link org.eclipse.skills.model.impl.MUser#getBadges <em>Badges</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.impl.MUser#getTitle <em>Title</em>}</li>
  * </ul>
  *
  * @generated
@@ -125,6 +126,26 @@
 	protected EList<IBadge> badges;
 
 	/**
+	 * The default value of the '{@link #getTitle() <em>Title</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getTitle()
+	 * @generated
+	 * @ordered
+	 */
+	protected static final String TITLE_EDEFAULT = null;
+
+	/**
+	 * The cached value of the '{@link #getTitle() <em>Title</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getTitle()
+	 * @generated
+	 * @ordered
+	 */
+	protected String title = TITLE_EDEFAULT;
+
+	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
 	 * @generated
 	 */
@@ -263,6 +284,28 @@
 
 	/**
 	 * <!-- begin-user-doc --> <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public String getTitle() {
+		return title;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public void setTitle(String newTitle) {
+		String oldTitle = title;
+		title = newTitle;
+		if (eNotificationRequired())
+			eNotify(new ENotificationImpl(this, Notification.SET, ISkillsPackage.USER__TITLE, oldTitle, title));
+	}
+
+	/**
+	 * <!-- begin-user-doc --> <!-- end-user-doc -->
 	 *
 	 * @generated NOT
 	 */
@@ -388,6 +431,8 @@
 				return getExperience();
 			case ISkillsPackage.USER__BADGES:
 				return getBadges();
+			case ISkillsPackage.USER__TITLE:
+				return getTitle();
 		}
 		return super.eGet(featureID, resolve, coreType);
 	}
@@ -421,6 +466,9 @@
 				getBadges().clear();
 				getBadges().addAll((Collection<? extends IBadge>)newValue);
 				return;
+			case ISkillsPackage.USER__TITLE:
+				setTitle((String)newValue);
+				return;
 		}
 		super.eSet(featureID, newValue);
 	}
@@ -450,6 +498,9 @@
 			case ISkillsPackage.USER__BADGES:
 				getBadges().clear();
 				return;
+			case ISkillsPackage.USER__TITLE:
+				setTitle(TITLE_EDEFAULT);
+				return;
 		}
 		super.eUnset(featureID);
 	}
@@ -473,6 +524,8 @@
 				return experience != null;
 			case ISkillsPackage.USER__BADGES:
 				return badges != null && !badges.isEmpty();
+			case ISkillsPackage.USER__TITLE:
+				return TITLE_EDEFAULT == null ? title != null : !TITLE_EDEFAULT.equals(title);
 		}
 		return super.eIsSet(featureID);
 	}
@@ -512,6 +565,8 @@
 		result.append(name);
 		result.append(", imageLocation: ");
 		result.append(imageLocation);
+		result.append(", title: ");
+		result.append(title);
 		result.append(')');
 		return result.toString();
 	}
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MUserTitle.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MUserTitle.java
new file mode 100644
index 0000000..be76218
--- /dev/null
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/impl/MUserTitle.java
@@ -0,0 +1,285 @@
+/**
+ */
+package org.eclipse.skills.model.impl;
+
+import org.eclipse.emf.common.notify.Notification;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.InternalEObject;
+
+import org.eclipse.emf.ecore.impl.ENotificationImpl;
+import org.eclipse.emf.ecore.impl.MinimalEObjectImpl;
+
+import org.eclipse.skills.model.ISkill;
+import org.eclipse.skills.model.ISkillsPackage;
+import org.eclipse.skills.model.IUserTitle;
+
+/**
+ * <!-- begin-user-doc -->
+ * An implementation of the model object '<em><b>User Title</b></em>'.
+ * <!-- end-user-doc -->
+ * <p>
+ * The following features are implemented:
+ * </p>
+ * <ul>
+ *   <li>{@link org.eclipse.skills.model.impl.MUserTitle#getMinLevel <em>Min Level</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.impl.MUserTitle#getSkill <em>Skill</em>}</li>
+ *   <li>{@link org.eclipse.skills.model.impl.MUserTitle#getDisplayString <em>Display String</em>}</li>
+ * </ul>
+ *
+ * @generated
+ */
+public class MUserTitle extends MinimalEObjectImpl.Container implements IUserTitle {
+	/**
+	 * The default value of the '{@link #getMinLevel() <em>Min Level</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getMinLevel()
+	 * @generated
+	 * @ordered
+	 */
+	protected static final int MIN_LEVEL_EDEFAULT = 0;
+
+	/**
+	 * The cached value of the '{@link #getMinLevel() <em>Min Level</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getMinLevel()
+	 * @generated
+	 * @ordered
+	 */
+	protected int minLevel = MIN_LEVEL_EDEFAULT;
+
+	/**
+	 * The cached value of the '{@link #getSkill() <em>Skill</em>}' reference.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getSkill()
+	 * @generated
+	 * @ordered
+	 */
+	protected ISkill skill;
+
+	/**
+	 * The default value of the '{@link #getDisplayString() <em>Display String</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getDisplayString()
+	 * @generated
+	 * @ordered
+	 */
+	protected static final String DISPLAY_STRING_EDEFAULT = null;
+
+	/**
+	 * The cached value of the '{@link #getDisplayString() <em>Display String</em>}' attribute.
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @see #getDisplayString()
+	 * @generated
+	 * @ordered
+	 */
+	protected String displayString = DISPLAY_STRING_EDEFAULT;
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	protected MUserTitle() {
+		super();
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	protected EClass eStaticClass() {
+		return ISkillsPackage.Literals.USER_TITLE;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public int getMinLevel() {
+		return minLevel;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public void setMinLevel(int newMinLevel) {
+		int oldMinLevel = minLevel;
+		minLevel = newMinLevel;
+		if (eNotificationRequired())
+			eNotify(new ENotificationImpl(this, Notification.SET, ISkillsPackage.USER_TITLE__MIN_LEVEL, oldMinLevel, minLevel));
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public ISkill getSkill() {
+		if (skill != null && skill.eIsProxy()) {
+			InternalEObject oldSkill = (InternalEObject)skill;
+			skill = (ISkill)eResolveProxy(oldSkill);
+			if (skill != oldSkill) {
+				if (eNotificationRequired())
+					eNotify(new ENotificationImpl(this, Notification.RESOLVE, ISkillsPackage.USER_TITLE__SKILL, oldSkill, skill));
+			}
+		}
+		return skill;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	public ISkill basicGetSkill() {
+		return skill;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public void setSkill(ISkill newSkill) {
+		ISkill oldSkill = skill;
+		skill = newSkill;
+		if (eNotificationRequired())
+			eNotify(new ENotificationImpl(this, Notification.SET, ISkillsPackage.USER_TITLE__SKILL, oldSkill, skill));
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public String getDisplayString() {
+		return displayString;
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public void setDisplayString(String newDisplayString) {
+		String oldDisplayString = displayString;
+		displayString = newDisplayString;
+		if (eNotificationRequired())
+			eNotify(new ENotificationImpl(this, Notification.SET, ISkillsPackage.USER_TITLE__DISPLAY_STRING, oldDisplayString, displayString));
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public Object eGet(int featureID, boolean resolve, boolean coreType) {
+		switch (featureID) {
+			case ISkillsPackage.USER_TITLE__MIN_LEVEL:
+				return getMinLevel();
+			case ISkillsPackage.USER_TITLE__SKILL:
+				if (resolve) return getSkill();
+				return basicGetSkill();
+			case ISkillsPackage.USER_TITLE__DISPLAY_STRING:
+				return getDisplayString();
+		}
+		return super.eGet(featureID, resolve, coreType);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public void eSet(int featureID, Object newValue) {
+		switch (featureID) {
+			case ISkillsPackage.USER_TITLE__MIN_LEVEL:
+				setMinLevel((Integer)newValue);
+				return;
+			case ISkillsPackage.USER_TITLE__SKILL:
+				setSkill((ISkill)newValue);
+				return;
+			case ISkillsPackage.USER_TITLE__DISPLAY_STRING:
+				setDisplayString((String)newValue);
+				return;
+		}
+		super.eSet(featureID, newValue);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public void eUnset(int featureID) {
+		switch (featureID) {
+			case ISkillsPackage.USER_TITLE__MIN_LEVEL:
+				setMinLevel(MIN_LEVEL_EDEFAULT);
+				return;
+			case ISkillsPackage.USER_TITLE__SKILL:
+				setSkill((ISkill)null);
+				return;
+			case ISkillsPackage.USER_TITLE__DISPLAY_STRING:
+				setDisplayString(DISPLAY_STRING_EDEFAULT);
+				return;
+		}
+		super.eUnset(featureID);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public boolean eIsSet(int featureID) {
+		switch (featureID) {
+			case ISkillsPackage.USER_TITLE__MIN_LEVEL:
+				return minLevel != MIN_LEVEL_EDEFAULT;
+			case ISkillsPackage.USER_TITLE__SKILL:
+				return skill != null;
+			case ISkillsPackage.USER_TITLE__DISPLAY_STRING:
+				return DISPLAY_STRING_EDEFAULT == null ? displayString != null : !DISPLAY_STRING_EDEFAULT.equals(displayString);
+		}
+		return super.eIsSet(featureID);
+	}
+
+	/**
+	 * <!-- begin-user-doc -->
+	 * <!-- end-user-doc -->
+	 * @generated
+	 */
+	@Override
+	public String toString() {
+		if (eIsProxy()) return super.toString();
+
+		StringBuilder result = new StringBuilder(super.toString());
+		result.append(" (minLevel: ");
+		result.append(minLevel);
+		result.append(", displayString: ");
+		result.append(displayString);
+		result.append(')');
+		return result.toString();
+	}
+
+} //MUserTitle
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsAdapterFactory.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsAdapterFactory.java
index dec2cec..55cde7d 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsAdapterFactory.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsAdapterFactory.java
@@ -176,6 +176,10 @@
 				return createFactorProgressionAdapter();
 			}
 			@Override
+			public Adapter caseUserTitle(IUserTitle object) {
+				return createUserTitleAdapter();
+			}
+			@Override
 			public Adapter defaultCase(EObject object) {
 				return createEObjectAdapter();
 			}
@@ -574,6 +578,20 @@
 	}
 
 	/**
+	 * Creates a new adapter for an object of class '{@link org.eclipse.skills.model.IUserTitle <em>User Title</em>}'.
+	 * <!-- begin-user-doc -->
+	 * This default implementation returns null so that we can easily ignore cases;
+	 * it's useful to ignore a case when inheritance will catch all the cases anyway.
+	 * <!-- end-user-doc -->
+	 * @return the new adapter.
+	 * @see org.eclipse.skills.model.IUserTitle
+	 * @generated
+	 */
+	public Adapter createUserTitleAdapter() {
+		return null;
+	}
+
+	/**
 	 * Creates a new adapter for the default case.
 	 * <!-- begin-user-doc -->
 	 * This default implementation returns null.
diff --git a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsSwitch.java b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsSwitch.java
index 6d6737c..2629238 100644
--- a/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsSwitch.java
+++ b/plugins/org.eclipse.skills/src-gen/org/eclipse/skills/model/util/SkillsSwitch.java
@@ -255,6 +255,12 @@
 				if (result == null) result = defaultCase(theEObject);
 				return result;
 			}
+			case ISkillsPackage.USER_TITLE: {
+				IUserTitle userTitle = (IUserTitle)theEObject;
+				T result = caseUserTitle(userTitle);
+				if (result == null) result = defaultCase(theEObject);
+				return result;
+			}
 			default: return defaultCase(theEObject);
 		}
 	}
@@ -665,6 +671,21 @@
 	}
 
 	/**
+	 * Returns the result of interpreting the object as an instance of '<em>User Title</em>'.
+	 * <!-- begin-user-doc -->
+	 * This implementation returns null;
+	 * returning a non-null result will terminate the switch.
+	 * <!-- end-user-doc -->
+	 * @param object the target of the switch.
+	 * @return the result of interpreting the object as an instance of '<em>User Title</em>'.
+	 * @see #doSwitch(org.eclipse.emf.ecore.EObject) doSwitch(EObject)
+	 * @generated
+	 */
+	public T caseUserTitle(IUserTitle object) {
+		return null;
+	}
+
+	/**
 	 * Returns the result of interpreting the object as an instance of '<em>EObject</em>'.
 	 * <!-- begin-user-doc -->
 	 * This implementation returns null;
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/BrokerTools.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/BrokerTools.java
index 1148bc9..178a93f 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/BrokerTools.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/BrokerTools.java
@@ -19,7 +19,22 @@
 
 public class BrokerTools {
 
+	private static IEventBroker fCustomBroker = null;
+
+	/**
+	 * Set a custom broker instance used in case the default broker is not available. Typically used for headless unittest execution.
+	 *
+	 * @param customBroker
+	 *            custom broker instance (<code>null</code> to use default broker)
+	 */
+	public static void setCustomBroker(IEventBroker customBroker) {
+		fCustomBroker = customBroker;
+	}
+
 	public static IEventBroker getBroker() {
+		if (fCustomBroker != null)
+			return fCustomBroker;
+
 		if (PlatformUI.isWorkbenchRunning())
 			return PlatformUI.getWorkbench().getService(IEventBroker.class);
 
@@ -35,15 +50,9 @@
 	 *            message data
 	 */
 	public static void post(String topic, Object data) {
-		try {
-			if (PlatformUI.isWorkbenchRunning()) {
-				final IEventBroker broker = PlatformUI.getWorkbench().getService(IEventBroker.class);
-				if (broker != null)
-					broker.post(topic, data);
-			}
-		} catch (final Throwable e) {
-			// silently swallow
-		}
+		final IEventBroker broker = getBroker();
+		if (broker != null)
+			broker.post(topic, data);
 	}
 
 	/**
@@ -55,15 +64,9 @@
 	 *            message data
 	 */
 	public static void send(String topic, Object data) {
-		try {
-			if (PlatformUI.isWorkbenchRunning()) {
-				final IEventBroker broker = PlatformUI.getWorkbench().getService(IEventBroker.class);
-				if (broker != null)
-					broker.send(topic, data);
-			}
-		} catch (final Throwable e) {
-			// silently swallow
-		}
+		final IEventBroker broker = getBroker();
+		if (broker != null)
+			broker.send(topic, data);
 	}
 
 	/**
@@ -78,7 +81,6 @@
 		final IEventBroker broker = getBroker();
 		if (broker != null)
 			broker.subscribe(topic, eventHandler);
-
 	}
 
 	/**
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/ISkillService.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/ISkillService.java
index b410419..57f5607 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/ISkillService.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/ISkillService.java
@@ -20,6 +20,7 @@
 import org.eclipse.skills.model.ITask;
 import org.eclipse.skills.model.IUser;
 import org.eclipse.skills.model.IUserTask;
+import org.eclipse.skills.service.questprovider.IQuestProvider;
 import org.eclipse.skills.service.storage.IDataStorage;
 
 /**
@@ -37,8 +38,10 @@
 
 	String EVENT_BASE = "org/eclipse/skills";
 
+	String EVENT_TASK_READY = EVENT_BASE + "/Task/ready";
 	String EVENT_TASK_STARTED = EVENT_BASE + "/Task/started";
 	String EVENT_TASK_COMPLETED = EVENT_BASE + "/Task/completed";
+
 	String EVENT_USER_UPDATE = EVENT_BASE + "/User/update";
 
 	String EVENT_DEPENDENCY_FULFILLED = EVENT_BASE + "/Dependency/fulfilled";
@@ -61,8 +64,11 @@
 	 *
 	 * @param task
 	 *            task being ready
+	 * @return usertask instance once task got assigned to the current user
 	 */
-	void reportTaskReady(ITask task);
+	IUserTask notifyTaskReady(ITask task);
+
+	void notifyTaskCompleted(IUserTask userTask);
 
 	Collection<ITask> getOpenTasks();
 
@@ -75,4 +81,5 @@
 	void startTask(IUserTask userTask);
 
 	void setStorage(IDataStorage storage);
+
 }
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/SkillService.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/SkillService.java
index 5f0dd6f..633a0e7 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/SkillService.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/SkillService.java
@@ -35,6 +35,8 @@
 import org.eclipse.skills.model.ITask;
 import org.eclipse.skills.model.IUser;
 import org.eclipse.skills.model.IUserTask;
+import org.eclipse.skills.service.questprovider.ExtensionPointQuestProvider;
+import org.eclipse.skills.service.questprovider.IQuestProvider;
 import org.eclipse.skills.service.storage.DataStorageProxy;
 import org.eclipse.skills.service.storage.WorkspaceDataStorage;
 import org.eclipse.ui.PlatformUI;
@@ -122,19 +124,58 @@
 
 	private void unregisterFromEvents() {
 		BrokerTools.unsubscribe(this);
-		getNotificationService().dispose();
+		if (hasNotificationService())
+			getNotificationService().dispose();
 	}
 
 	@Override
 	public void handleEvent(Event event) {
 		if (ISkillService.EVENT_TASK_COMPLETED.equals(event.getTopic())) {
-			final IUserTask userTask = (IUserTask) event.getProperty(IEventBroker.DATA);
+			final Object data = event.getProperty(IEventBroker.DATA);
+			if (data instanceof IUserTask)
+				notifyTaskCompleted((IUserTask) data);
+			else
+				Logger.warning(Activator.PLUGIN_ID, "Task completed event from broker with invalid data type detected",
+						new IllegalArgumentException("Unknown event type: " + data));
 
-			for (final IReward reward : userTask.getTask().getRewards())
-				getUser().consume(reward);
+		} else if (ISkillService.EVENT_TASK_READY.equals(event.getTopic())) {
+			final Object data = event.getProperty(IEventBroker.DATA);
+			if (data instanceof ITask)
+				notifyTaskReady((ITask) data);
+			else
+				Logger.warning(Activator.PLUGIN_ID, "Task ready event from broker with invalid data type detected",
+						new IllegalArgumentException("Unknown event type: " + data));
 		}
+	}
+
+	@Override
+	public IUserTask notifyTaskReady(ITask task) {
+		final IUserTask userTask = getUser().addTask(task);
+		if (task.isAutoActivation())
+			startTask(userTask);
 
 		storeUser();
+
+		return userTask;
+	}
+
+	@Override
+	public void notifyTaskCompleted(IUserTask userTask) {
+		for (final IReward reward : userTask.getTask().getRewards())
+			getUser().consume(reward);
+
+		updateUserTitle();
+
+		storeUser();
+	}
+
+	private void updateUserTitle() {
+		getUser().setTitle(getUserTitle());
+	}
+
+	private String getUserTitle() {
+		final UserTitleGenerator userTitleGenerator = new UserTitleGenerator(getUser(), getQuestProvider());
+		return userTitleGenerator.createUserTitle();
 	}
 
 	@Override
@@ -158,7 +199,7 @@
 
 	private UserStorage getUserStorage() {
 		if (fUserStorage == null)
-			fUserStorage = new UserStorage(getStorage());
+			fUserStorage = new UserStorage(this);
 
 		return fUserStorage;
 	}
@@ -167,6 +208,10 @@
 		fNotificationService = new UINotificationService();
 	}
 
+	private boolean hasNotificationService() {
+		return fNotificationService != null;
+	}
+
 	private UINotificationService getNotificationService() {
 		if (fNotificationService == null)
 			createNotificationService();
@@ -234,13 +279,6 @@
 	}
 
 	@Override
-	public void reportTaskReady(ITask task) {
-		final IUserTask userTask = getUser().addTask(task);
-		if (task.isAutoActivation())
-			startTask(userTask);
-	}
-
-	@Override
 	public void startTask(final IUserTask userTask) {
 		userTask.setStarted(new Date());
 
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/StringReplacer.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/StringReplacer.java
new file mode 100644
index 0000000..701cf54
--- /dev/null
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/StringReplacer.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.skills.service;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class StringReplacer {
+
+	private static Pattern WILDCARD_PATTERN = Pattern.compile("\\$\\{(.*?)\\}");
+
+	public static String replace(String data, Map<String, String> replacements) {
+		if (data != null) {
+			final Matcher matcher = WILDCARD_PATTERN.matcher(data);
+			data = matcher.replaceAll(matchResult -> getReplacement(matchResult.group(1), replacements));
+
+			return data.trim();
+		}
+
+		return data;
+	}
+
+	private static String getReplacement(String key, Map<String, String> replacements) {
+		final String candidate = (replacements != null) ? replacements.get(key) : "";
+		return (candidate != null) ? candidate : "";
+	}
+}
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UINotificationService.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UINotificationService.java
index 0f089a5..6eb89cc 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UINotificationService.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UINotificationService.java
@@ -39,7 +39,7 @@
 
 	private final List<Event> fScheduledEvents = new ArrayList<>();
 
-	private final Throttler fDisplayThrottler = new Throttler(Display.getDefault(), Duration.ofSeconds(1), this::displayNotification);
+	private Throttler fDisplayThrottler = null;
 
 	public UINotificationService() {
 		BrokerTools.subscribe(ISkillService.EVENT_TASK_COMPLETED, this);
@@ -58,7 +58,14 @@
 			fScheduledEvents.add(event);
 		}
 
-		fDisplayThrottler.throttledExec();
+		getDisplayThrottler().throttledExec();
+	}
+
+	private Throttler getDisplayThrottler() {
+		if (fDisplayThrottler == null)
+			fDisplayThrottler = new Throttler(Display.getDefault(), Duration.ofSeconds(1), this::displayNotification);
+
+		return fDisplayThrottler;
 	}
 
 	public void displayNotification() {
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UserFactory.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UserFactory.java
index 1191129..aaa1bee 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UserFactory.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UserFactory.java
@@ -71,6 +71,8 @@
 		for (final ISkill skill : getDefaultSkills())
 			user.getSkills().add(EcoreUtil.copy(skill));
 
+		user.setTitle("");
+
 		return user;
 	}
 
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UserTitleGenerator.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UserTitleGenerator.java
new file mode 100644
index 0000000..6b7f460
--- /dev/null
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/UserTitleGenerator.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.skills.service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.skills.model.IQuest;
+import org.eclipse.skills.model.ISkill;
+import org.eclipse.skills.model.IUser;
+import org.eclipse.skills.model.IUserTitle;
+import org.eclipse.skills.service.questprovider.IQuestProvider;
+
+public class UserTitleGenerator {
+
+	private static IUser getServiceUser() {
+		return SkillService.getInstance().getUser();
+	}
+
+	private static IQuestProvider getServiceQuestProvider() {
+		return SkillService.getInstance().getQuestProvider();
+	}
+
+	private final IUser fUser;
+	private final IQuestProvider fQuestProvider;
+
+	public UserTitleGenerator(IUser user, IQuestProvider questProvider) {
+		if (user == null)
+			throw new IllegalArgumentException("user cannot be null");
+
+		if (questProvider == null)
+			throw new IllegalArgumentException("questProvider cannot be null");
+
+		fUser = user;
+		fQuestProvider = questProvider;
+	}
+
+	public UserTitleGenerator() {
+		this(getServiceUser(), getServiceQuestProvider());
+	}
+
+	public String createUserTitle() {
+
+		final IUserTitle userTitle = getBestMatchingUserTitle();
+
+		if (userTitle != null)
+			return StringReplacer.replace(userTitle.getDisplayString(), getReplacementList(userTitle.getSkill()));
+
+		return "";
+	}
+
+	private Map<String, String> getReplacementList(ISkill skill) {
+		final Map<String, String> replacementList = new HashMap<>();
+
+		replacementList.put("skill.name", (skill != null) ? skill.getName() : "");
+
+		return replacementList;
+	}
+
+	private IUserTitle getBestMatchingUserTitle() {
+		final List<IUserTitle> userTitles = getUserTitles();
+
+		Collections.sort(userTitles, (o1, o2) -> o2.getMinLevel() - o1.getMinLevel());
+
+		for (final IUserTitle userTitle : userTitles) {
+			final ISkill userSkill = getUserSkill(userTitle.getSkill());
+			if (userSkill != null) {
+				if (userTitle.getMinLevel() <= userSkill.getProgression().getLevel(userSkill.getExperience()))
+					return userTitle;
+			}
+		}
+
+		return null;
+	}
+
+	private ISkill getUserSkill(ISkill skill) {
+		if (skill != null)
+			return fUser.getSkill(skill.getName());
+
+		return fUser.getExperience();
+	}
+
+	private List<IUserTitle> getUserTitles() {
+		final List<IUserTitle> userTitles = new ArrayList<>();
+		for (final IQuest quest : fQuestProvider.getQuests())
+			userTitles.addAll(quest.getUserTitles());
+
+		return userTitles;
+	}
+}
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/ExtensionPointQuestProvider.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/ExtensionPointQuestProvider.java
similarity index 63%
rename from plugins/org.eclipse.skills/src/org/eclipse/skills/service/ExtensionPointQuestProvider.java
rename to plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/ExtensionPointQuestProvider.java
index 32b86c2..8129754 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/ExtensionPointQuestProvider.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/ExtensionPointQuestProvider.java
@@ -11,21 +11,15 @@
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
 
-package org.eclipse.skills.service;
+package org.eclipse.skills.service.questprovider;
 
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.Map;
 
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.resource.ResourceSet;
-import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
-import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
 import org.eclipse.skills.model.IQuest;
-import org.eclipse.skills.model.ISkillsPackage;
 
 public class ExtensionPointQuestProvider implements IQuestProvider {
 
@@ -42,18 +36,8 @@
 				if (uri.isRelative())
 					uri = URI.createURI("platform:/plugin/" + e.getContributor().getName() + "/" + location);
 
-				// initialize the model
-				ISkillsPackage.eINSTANCE.eClass();
-
-				final Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
-				final Map<String, Object> extensionsMap = reg.getExtensionToFactoryMap();
-				extensionsMap.put("skills", new XMIResourceFactoryImpl());
-
-				final ResourceSet resourceSet = new ResourceSetImpl();
-				final Resource resource = resourceSet.getResource(uri, true);
-
-				final IQuest quest = (IQuest) resource.getContents().get(0);
-				quests.add(quest);
+				final URIQuestProvider questProvider = new URIQuestProvider(uri);
+				quests.addAll(questProvider.getQuests());
 			}
 		}
 
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/IQuestProvider.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/IQuestProvider.java
similarity index 93%
rename from plugins/org.eclipse.skills/src/org/eclipse/skills/service/IQuestProvider.java
rename to plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/IQuestProvider.java
index ea206fa..cc31662 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/IQuestProvider.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/IQuestProvider.java
@@ -11,7 +11,7 @@
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
 
-package org.eclipse.skills.service;
+package org.eclipse.skills.service.questprovider;
 
 import java.util.Collection;
 
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/URIQuestProvider.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/URIQuestProvider.java
new file mode 100644
index 0000000..e1c9fa9
--- /dev/null
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/service/questprovider/URIQuestProvider.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.skills.service.questprovider;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
+import org.eclipse.skills.model.IQuest;
+import org.eclipse.skills.model.ISkillsPackage;
+
+public class URIQuestProvider implements IQuestProvider {
+
+	private final URI fQuestDataUri;
+
+	public URIQuestProvider(URI questDataUri) {
+		fQuestDataUri = questDataUri;
+	}
+
+	public URIQuestProvider(String questDataUri) {
+		this(URI.createURI(questDataUri));
+	}
+
+	@Override
+	public Collection<IQuest> getQuests() {
+		final Collection<IQuest> quests = new HashSet<>();
+
+		// initialize the model
+		ISkillsPackage.eINSTANCE.eClass();
+
+		final Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
+		final Map<String, Object> extensionsMap = reg.getExtensionToFactoryMap();
+		extensionsMap.put("skills", new XMIResourceFactoryImpl());
+
+		final ResourceSet resourceSet = new ResourceSetImpl();
+		final Resource resource = resourceSet.getResource(fQuestDataUri, true);
+
+		final IQuest quest = (IQuest) resource.getContents().get(0);
+		quests.add(quest);
+
+		return quests;
+	}
+}
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/CharacterView.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/CharacterView.java
index 6634627..630f2b4 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/CharacterView.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/CharacterView.java
@@ -21,10 +21,8 @@
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.part.ViewPart;
-import org.eclipse.wb.swt.SWTResourceManager;
 
 public class CharacterView extends ViewPart {
 
@@ -36,9 +34,6 @@
 		return null;
 	}
 
-	private StatsComposite fStatsComposite;
-	private LocalResourceManager fResourceManager;
-
 	public CharacterView() {
 	}
 
@@ -48,26 +43,16 @@
 		final Composite composite = new Composite(parent, SWT.NONE);
 		composite.setLayout(new GridLayout(2, false));
 
-		fResourceManager = new LocalResourceManager(JFaceResources.getResources(), composite);
+		final LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources(), composite);
 
-		final Label lblTitle = addTitle(composite);
+		final TitleComposite titleComposite = new TitleComposite(composite, SWT.NONE, resourceManager);
+		titleComposite.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).indent(20, 0).create());
 
-		final CharacterComposite characterComposite = new CharacterComposite(composite, SWT.NONE, fResourceManager);
+		final CharacterComposite characterComposite = new CharacterComposite(composite, SWT.NONE, resourceManager);
 		characterComposite.setLayoutData(GridDataFactory.fillDefaults().grab(false, true).create());
 
-		fStatsComposite = new StatsComposite(composite, SWT.NONE, fResourceManager);
-		fStatsComposite.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
-	}
-
-	private Label addTitle(final Composite parent) {
-		// FIXME use default font for headers
-		final Label lblTitle = new Label(parent, SWT.NONE);
-		lblTitle.setFont(SWTResourceManager.getFont("Cantarell", 24, SWT.BOLD));
-		lblTitle.setForeground(SWTResourceManager.getColor(SWT.COLOR_TITLE_BACKGROUND));
-		lblTitle.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).indent(20, 0).create());
-		lblTitle.setText(getUser().getName() + ", an expert (TODO)");
-
-		return lblTitle;
+		final StatsComposite statsComposite = new StatsComposite(composite, SWT.NONE, resourceManager);
+		statsComposite.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
 	}
 
 	@Override
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/SkillsComposite.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/SkillsComposite.java
index f19afb7..415040f 100644
--- a/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/SkillsComposite.java
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/SkillsComposite.java
@@ -22,6 +22,7 @@
 import org.eclipse.emf.common.notify.impl.AdapterImpl;
 import org.eclipse.jface.layout.GridDataFactory;
 import org.eclipse.jface.util.Throttler;
+import org.eclipse.skills.model.IDescription;
 import org.eclipse.skills.model.ISkill;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.GridData;
@@ -55,7 +56,7 @@
 	public void setSkills(Collection<ISkill> skills) {
 
 		for (final ISkill skill : skills) {
-			createSkillElement(this, skill);
+			createSkillElement(skill);
 			skill.eAdapters().add(fSkillAdapter);
 		}
 
@@ -63,26 +64,26 @@
 		getParent().layout();
 	}
 
-	private void createSkillElement(Composite parent, ISkill skill) {
-		final Label lblTitle = new Label(parent, SWT.NONE);
+	private void createSkillElement(ISkill skill) {
+		final Label lblTitle = new Label(this, SWT.NONE);
 		lblTitle.setText(skill.getName());
-		lblTitle.setToolTipText(skill.getDescription().getText());
+		lblTitle.setToolTipText(getSkillDescription(skill));
 		lblTitle.setLayoutData(GridDataFactory.fillDefaults().create());
 
 		if (fShowProgress) {
-			final ProgressBar progressBar = new ProgressBar(parent, SWT.NONE);
+			final ProgressBar progressBar = new ProgressBar(this, SWT.NONE);
 			progressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
 			progressBar.setData(skill);
 			updateControl(progressBar, skill);
 
-			final Label lblLevel = new Label(parent, SWT.NONE);
+			final Label lblLevel = new Label(this, SWT.NONE);
 			lblLevel.setData(skill);
 			lblLevel.setData(FLAG_WITH_PROGRESSBAR, true);
 			updateControl(lblLevel, skill);
 
 		} else {
 
-			final Label lblLevel = new Label(parent, SWT.NONE);
+			final Label lblLevel = new Label(this, SWT.NONE);
 			lblLevel.setData(skill);
 			lblLevel.setData(FLAG_WITH_PROGRESSBAR, false);
 			lblLevel.setLayoutData(GridDataFactory.fillDefaults().indent(20, 0).create());
@@ -90,6 +91,17 @@
 		}
 	}
 
+	private String getSkillDescription(ISkill skill) {
+		final IDescription description = skill.getDescription();
+		if (description != null) {
+			final String text = description.getText();
+			if (text != null)
+				return text;
+		}
+
+		return "";
+	}
+
 	public void updateControls() {
 		for (final Control control : getChildren()) {
 			if (control.getData() instanceof ISkill)
diff --git a/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/TitleComposite.java b/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/TitleComposite.java
new file mode 100644
index 0000000..d840ad6
--- /dev/null
+++ b/plugins/org.eclipse.skills/src/org/eclipse/skills/ui/views/character/TitleComposite.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.skills.ui.views.character;
+
+import java.time.Duration;
+import java.util.Objects;
+
+import org.eclipse.emf.common.notify.Notification;
+import org.eclipse.emf.common.notify.impl.AdapterImpl;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.jface.util.Throttler;
+import org.eclipse.skills.model.ISkillsPackage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+
+public class TitleComposite extends Composite {
+
+	private final Throttler fUiUpdateThrottler = new Throttler(Display.getDefault(), Duration.ofMillis(500), this::updateTitle);
+
+	private final Label fLblName;
+	private final Label fLblTitle;
+
+	public TitleComposite(Composite parent, int style, ResourceManager resourceManager) {
+		super(parent, style);
+
+		setLayout(new GridLayout(2, false));
+
+		fLblName = createNameLabel(resourceManager);
+		fLblTitle = createTitleLabel(resourceManager);
+
+		CharacterView.getUser().eAdapters().add(new UserTitleAdapter());
+	}
+
+	private Label createNameLabel(ResourceManager resourceManager) {
+		FontDescriptor fontDescriptor = JFaceResources.getHeaderFontDescriptor();
+		fontDescriptor = fontDescriptor.setHeight(28);
+
+		final Label lblName = new Label(this, SWT.NONE);
+		lblName.setFont(resourceManager.createFont(fontDescriptor));
+		lblName.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_TITLE_BACKGROUND));
+		lblName.setLayoutData(GridDataFactory.fillDefaults().create());
+
+		lblName.setText(CharacterView.getUser().getName());
+
+		return lblName;
+	}
+
+	private Label createTitleLabel(ResourceManager resourceManager) {
+		final FontDescriptor fontDescriptor = JFaceResources.getHeaderFontDescriptor();
+
+		final Label lblTitle = new Label(this, SWT.NONE);
+		lblTitle.setFont(resourceManager.createFont(fontDescriptor));
+		lblTitle.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_TITLE_BACKGROUND));
+		lblTitle.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
+
+		lblTitle.setText(CharacterView.getUser().getTitle());
+
+		return lblTitle;
+	}
+
+	private void updateTitle() {
+		if (!fLblName.isDisposed())
+			fLblName.setText(CharacterView.getUser().getName());
+
+		if (!fLblTitle.isDisposed())
+			fLblTitle.setText(CharacterView.getUser().getTitle());
+
+		layout();
+	}
+
+	private class UserTitleAdapter extends AdapterImpl {
+		@Override
+		public void notifyChanged(Notification msg) {
+			if (affectsTitle(msg))
+				fUiUpdateThrottler.throttledExec();
+		}
+
+		private boolean affectsTitle(Notification msg) {
+			return (Objects.equals(msg.getFeature(), ISkillsPackage.eINSTANCE.getUser_Title()))
+					|| (Objects.equals(msg.getFeature(), ISkillsPackage.eINSTANCE.getUser_Name()));
+		}
+	}
+}
diff --git a/tests/org.eclipse.skills.test/fragment.xml b/tests/org.eclipse.skills.test/fragment.xml
index f484227..5a7aca1 100644
--- a/tests/org.eclipse.skills.test/fragment.xml
+++ b/tests/org.eclipse.skills.test/fragment.xml
@@ -4,7 +4,7 @@
    <extension
          point="org.eclipse.skills.quest">
       <resource
-            URI="resources/UnitTest.skills">
+            URI="resources/SkillServiceTest_HierarchicalTasks.skills">
       </resource>
    </extension>
 
diff --git a/tests/org.eclipse.skills.test/resources/SkillServiceTest_HierarchicalTasks.skills b/tests/org.eclipse.skills.test/resources/SkillServiceTest_HierarchicalTasks.skills
new file mode 100644
index 0000000..b460cac
--- /dev/null
+++ b/tests/org.eclipse.skills.test/resources/SkillServiceTest_HierarchicalTasks.skills
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<skills:Quest xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:skills="http://eclipse.org/skills/1.0.0" title="UnitTest Quest">
+  <tasks title="First task" autoActivation="false">
+    <description text="Simple first task"/>
+    <goal xsi:type="skills:AndDependency"/>
+    <requirement xsi:type="skills:AndDependency"/>
+    <tasks title="inner task">
+      <description text="Subtask of first task"/>
+      <goal xsi:type="skills:AndDependency"/>
+      <requirement xsi:type="skills:AndDependency"/>
+    </tasks>
+  </tasks>
+  <tasks title="Second Task">
+    <description text="Second dummy task"/>
+    <goal xsi:type="skills:AndDependency"/>
+    <requirement xsi:type="skills:AndDependency"/>
+  </tasks>
+</skills:Quest>
diff --git a/tests/org.eclipse.skills.test/resources/SkillServiceTest_Rewards.skills b/tests/org.eclipse.skills.test/resources/SkillServiceTest_Rewards.skills
new file mode 100644
index 0000000..9f09dc6
--- /dev/null
+++ b/tests/org.eclipse.skills.test/resources/SkillServiceTest_Rewards.skills
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<skills:Quest xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:skills="http://eclipse.org/skills/1.0.0" title="UnitTest Quest">
+  <tasks title="First task" autoActivation="false">
+    <description text="Simple first task"/>
+    <goal xsi:type="skills:AndDependency"/>
+    <rewards xsi:type="skills:ExperienceReward" experience="100"/>
+    <requirement xsi:type="skills:AndDependency"/>
+  </tasks>
+  <tasks title="Second Task">
+    <goal xsi:type="skills:AndDependency"/>
+    <rewards xsi:type="skills:SkillReward" experience="100" skill="TestSkill"/>
+    <requirement xsi:type="skills:AndDependency"/>
+  </tasks>
+  <skills name="TestSkill">
+    <description text="Second dummy task"/>
+    <progression xsi:type="skills:FactorProgression"/>
+  </skills>
+  <userTitles displayString="Apprentice"/>
+</skills:Quest>
diff --git a/tests/org.eclipse.skills.test/resources/URIQuestProviderTest.skills b/tests/org.eclipse.skills.test/resources/URIQuestProviderTest.skills
new file mode 100644
index 0000000..120c45b
--- /dev/null
+++ b/tests/org.eclipse.skills.test/resources/URIQuestProviderTest.skills
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<skills:Quest xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:skills="http://eclipse.org/skills/1.0.0" title="UnitTest Quest">
+  <tasks title="First task"/>
+  <skills name="Testing">
+    <progression xsi:type="skills:FactorProgression"/>
+  </skills>
+  <userTitles displayString="${user.name}"/>
+</skills:Quest>
diff --git a/tests/org.eclipse.skills.test/resources/UnitTest.skills b/tests/org.eclipse.skills.test/resources/UnitTest.skills
deleted file mode 100644
index 4735aea..0000000
--- a/tests/org.eclipse.skills.test/resources/UnitTest.skills
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<skills:Quest xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:skills="http://eclipse.org/skills/1.0.0" title="UnitTest Quest">
-  <tasks title="First task" autoActivation="false">
-    <description text="Simple first task"/>
-    <goal xsi:type="skills:AndDependency">
-      <dependencies xsi:type="skills:CompleteIncludedTasksDependency"/>
-    </goal>
-    <requirement xsi:type="skills:AndDependency"/>
-    <tasks title="inner task">
-      <description text="Subtask of first task"/>
-      <goal xsi:type="skills:AndDependency">
-        <dependencies xsi:type="skills:SkillDependency" minExperience="100" skill="//@skills.0"/>
-      </goal>
-      <requirement xsi:type="skills:AndDependency">
-        <dependencies xsi:type="skills:TaskDependency" task="//@tasks.0" completed="false"/>
-      </requirement>
-    </tasks>
-  </tasks>
-  <tasks title="Second Task">
-    <description text="Second dummy task"/>
-    <goal xsi:type="skills:AndDependency">
-      <dependencies xsi:type="skills:NotDependency"/>
-    </goal>
-    <requirement xsi:type="skills:AndDependency">
-      <dependencies xsi:type="skills:TaskDependency" task="//@tasks.0"/>
-    </requirement>
-  </tasks>
-  <skills name="TestSkill">
-    <description text="Unittest dummy skill"/>
-  </skills>
-</skills:Quest>
diff --git a/tests/org.eclipse.skills.test/resources/UserStorageTest.skills b/tests/org.eclipse.skills.test/resources/UserStorageTest.skills
new file mode 100644
index 0000000..8f80e60
--- /dev/null
+++ b/tests/org.eclipse.skills.test/resources/UserStorageTest.skills
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<skills:Quest xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:skills="http://eclipse.org/skills/1.0.0" title="UserStorageTest Quest">
+  <tasks title="First task" autoActivation="false">
+    <description text="Simple first task"/>
+    <goal xsi:type="skills:AndDependency"/>
+    <requirement xsi:type="skills:AndDependency"/>
+    <tasks title="inner task">
+      <description text="Subtask of first task"/>
+      <goal xsi:type="skills:AndDependency"/>
+      <requirement xsi:type="skills:AndDependency"/>
+    </tasks>
+  </tasks>
+  <tasks title="Second Task">
+    <description text="Second dummy task"/>
+    <goal xsi:type="skills:AndDependency"/>
+    <requirement xsi:type="skills:AndDependency"/>
+  </tasks>
+</skills:Quest>
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/TaskTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/TaskTest.java
index cba41fc..9111824 100644
--- a/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/TaskTest.java
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/TaskTest.java
@@ -16,8 +16,11 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import org.eclipse.e4.core.services.events.IEventBroker;
+import org.eclipse.skills.BrokerTools;
 import org.eclipse.skills.service.ISkillService;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -40,15 +43,18 @@
 	}
 
 	@Test
-	@DisplayName("Report to service when requirement is met")
-	public void reportWhenRequirementsAreMet() {
+	@DisplayName("Send broker event when requirement is met")
+	public void sendEventWhenRequirementsAreMet() {
+		final IEventBroker broker = mock(IEventBroker.class);
+		BrokerTools.setCustomBroker(broker);
+
 		final IAndDependency requirement = ISkillsFactory.eINSTANCE.createAndDependency();
 
 		fTask.setSkillService(fSkillService);
 		fTask.setRequirement(requirement);
 		requirement.activate();
 
-		verify(fSkillService).reportTaskReady(fTask);
+		verify(broker, times(1)).post(ISkillService.EVENT_TASK_READY, fTask);
 	}
 
 	@Test
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/UserTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/UserTest.java
index 77d5b9c..c46a19d 100644
--- a/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/UserTest.java
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/model/UserTest.java
@@ -32,6 +32,7 @@
 	}
 
 	@Test
+	@DisplayName("New user does not have any assigned usertasks")
 	public void createUser() {
 		assertNotNull(fUser);
 		assertTrue(fUser.getUsertasks().isEmpty());
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/SkillServiceTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/SkillServiceTest.java
index b89dcce..9d819d1 100644
--- a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/SkillServiceTest.java
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/SkillServiceTest.java
@@ -17,20 +17,35 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
-import java.util.Collection;
-import java.util.HashSet;
+import java.io.IOException;
 
+import org.eclipse.skills.model.IExperienceReward;
 import org.eclipse.skills.model.INotDependency;
 import org.eclipse.skills.model.IQuest;
 import org.eclipse.skills.model.ISkillsFactory;
 import org.eclipse.skills.model.ITask;
+import org.eclipse.skills.model.IUser;
+import org.eclipse.skills.model.IUserTask;
+import org.eclipse.skills.service.questprovider.IQuestProvider;
+import org.eclipse.skills.service.questprovider.URIQuestProvider;
+import org.eclipse.skills.service.storage.IDataStorage;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
 public class SkillServiceTest {
 
+	private static final String HIERARCHICAL_TASKS_CONFIG = "platform:/plugin/org.eclipse.skills.test/resources/SkillServiceTest_HierarchicalTasks.skills";
+	private static final String REWARDS_CONFIG = "platform:/plugin/org.eclipse.skills.test/resources/SkillServiceTest_Rewards.skills";
+
 	private ISkillService fService;
 
 	@BeforeEach
@@ -38,11 +53,17 @@
 		try {
 			fService = SkillService.getInstance();
 			fService.resetProgress();
+
 		} catch (final NullPointerException e) {
 			throw new RuntimeException("These tests need to be launched as JUnit Plug-in Test", e);
 		}
 	}
 
+	@AfterEach
+	public void teardown() {
+		fService.deactivateService();
+	}
+
 	@Test
 	@DisplayName("New user does not have assigned tasks")
 	public void defaultUserHasNoTasks() {
@@ -56,23 +77,44 @@
 	}
 
 	@Test
+	@DisplayName("New user has no title")
+	public void defaultUserHasNoTitle() {
+		assertTrue(fService.getUser().getTitle().isEmpty());
+	}
+
+	@Test
 	@DisplayName("When a task is ready it should be added to the current user tasks")
 	public void reportReadyTaskShallBeAddedToActiveUser() {
+		assertEquals(0, fService.getUser().getUsertasks().size());
+
 		final ITask task = createTask();
-		fService.reportTaskReady(task);
+		final IUserTask usertask = fService.notifyTaskReady(task);
 
 		assertEquals(1, fService.getUser().getUsertasks().size());
+		assertEquals(usertask, fService.getUser().getUsertasks().get(0));
 		assertEquals(task, fService.getUser().getUsertasks().get(0).getTask());
 	}
 
 	@Test
 	@DisplayName("When a task is ready it should be started if flag is set")
-	public void reportReadyTaskShallBeActivated() {
+	public void notifyTaskReadyShallBeActivated() {
 		final ITask task = createTask();
 		task.setAutoActivation(true);
 
-		fService.reportTaskReady(task);
-		assertTrue(fService.getUser().getUsertasks().get(0).isStarted());
+		final IUserTask userTask = fService.notifyTaskReady(task);
+
+		assertTrue(userTask.isStarted());
+	}
+
+	@Test
+	@DisplayName("When a task is ready the user profile gets stored")
+	public void notifyTaskReadyStoresUserProfile() throws IOException {
+		final IDataStorage storage = mock(IDataStorage.class);
+		fService.setStorage(storage);
+
+		fService.notifyTaskReady(createTask());
+
+		verify(storage, times(1)).storeResource(eq(IDataStorage.USER_PROFILE), any());
 	}
 
 	@Test
@@ -81,8 +123,9 @@
 		final ITask task = createTask();
 		task.setAutoActivation(false);
 
-		fService.reportTaskReady(task);
-		assertFalse(fService.getUser().getUsertasks().get(0).isStarted());
+		final IUserTask userTask = fService.notifyTaskReady(task);
+
+		assertFalse(userTask.isStarted());
 	}
 
 	@Test
@@ -94,7 +137,7 @@
 	@Test
 	@DisplayName("Use local quest provider")
 	public void useLocalTestProvider() {
-		final TestQuestProvider questProvider = new TestQuestProvider();
+		final IQuestProvider questProvider = new URIQuestProvider(HIERARCHICAL_TASKS_CONFIG);
 		fService.setQuestProvider(questProvider);
 		assertEquals(questProvider, fService.getQuestProvider());
 	}
@@ -102,7 +145,7 @@
 	@Test
 	@DisplayName("Get all available tasks, including nested ones")
 	public void getAllAvailableTasks() {
-		final TestQuestProvider questProvider = new TestQuestProvider();
+		final IQuestProvider questProvider = new URIQuestProvider(HIERARCHICAL_TASKS_CONFIG);
 		final IQuest quest = questProvider.getQuests().iterator().next();
 
 		fService.setQuestProvider(questProvider);
@@ -113,41 +156,53 @@
 		assertTrue(fService.getAllAvailableTasks().contains(quest.getTasks().get(0).getTasks().get(0)));
 	}
 
-	private ITask createTask() {
-		final ITask task = ISkillsFactory.eINSTANCE.createTask();
-		final INotDependency incomleteGoal = ISkillsFactory.eINSTANCE.createNotDependency();
-		task.setGoal(incomleteGoal);
+	@Test
+	@DisplayName("Completed usertask pays out reward")
+	public void rewardIsPaidWhenTaskIsCompleted() {
+		final IQuestProvider questProvider = new URIQuestProvider(REWARDS_CONFIG);
+		final ITask task = getFirstTaskFromQuestProvider(questProvider);
+		final IExperienceReward reward = (IExperienceReward) task.getRewards().get(0);
 
-		return task;
+		fService.activateService();
+		final IUser user = fService.getUser();
+
+		assertEquals(0, user.getExperience().getExperience());
+
+		final IUserTask userTask = fService.notifyTaskReady(task);
+		fService.notifyTaskCompleted(userTask);
+
+		assertEquals(reward.getExperience(), user.getExperience().getExperience());
 	}
 
-	private class TestQuestProvider implements IQuestProvider {
+	@Test
+	@DisplayName("XP level update changes user title")
+	public void xpLevelUpdateChangesUserTitle() {
+		final IQuestProvider questProvider = new URIQuestProvider(REWARDS_CONFIG);
+		final ITask task = getFirstTaskFromQuestProvider(questProvider);
 
-		private final Collection<IQuest> fQuests;
+		fService.setQuestProvider(questProvider);
+		fService.activateService();
 
-		public TestQuestProvider() {
-			final IQuest quest = ISkillsFactory.eINSTANCE.createQuest();
+		final IUser user = fService.getUser();
 
-			final ITask task1 = ISkillsFactory.eINSTANCE.createTask();
-			final ITask task2 = ISkillsFactory.eINSTANCE.createTask();
-			final ITask task3 = ISkillsFactory.eINSTANCE.createTask();
+		final String oldTitle = user.getTitle();
 
-			task1.setTitle("one");
-			task2.setTitle("two");
-			task3.setTitle("three");
+		final IUserTask userTask = fService.notifyTaskReady(task);
+		fService.notifyTaskCompleted(userTask);
 
-			task1.getTasks().add(task2);
+		assertNotEquals(oldTitle, user.getTitle());
+	}
 
-			quest.getTasks().add(task1);
-			quest.getTasks().add(task3);
+	private static ITask getFirstTaskFromQuestProvider(IQuestProvider questProvider) {
+		final IQuest quest = questProvider.getQuests().iterator().next();
+		return quest.getTasks().get(0);
+	}
 
-			fQuests = new HashSet<>();
-			fQuests.add(quest);
-		}
+	private static ITask createTask() {
+		final ITask task = ISkillsFactory.eINSTANCE.createTask();
+		final INotDependency incompleteGoal = ISkillsFactory.eINSTANCE.createNotDependency();
+		task.setGoal(incompleteGoal);
 
-		@Override
-		public Collection<IQuest> getQuests() {
-			return fQuests;
-		}
+		return task;
 	}
 }
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/StringReplacerTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/StringReplacerTest.java
new file mode 100644
index 0000000..3b7278a
--- /dev/null
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/StringReplacerTest.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.skills.service;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class StringReplacerTest {
+
+	@Test
+	@DisplayName("return null when data == null")
+	public void replaceOnNullReturnsNull() {
+		assertNull(StringReplacer.replace(null, null));
+	}
+
+	@Test
+	@DisplayName("return simple data when replacement == null")
+	public void returnDataWhenReplacementIsNull() {
+		assertEquals("input", StringReplacer.replace("input", null));
+	}
+
+	@Test
+	@DisplayName("trim result from whitespace")
+	public void trimWhitespace() {
+		assertEquals("input", StringReplacer.replace("  input\t", null));
+	}
+
+	@Test
+	@DisplayName("strip wildcards from data when replacement == null")
+	public void returnDataWithoutWildcardsWhenReplacementIsNull() {
+		assertEquals("input", StringReplacer.replace("input${wildcard}", null));
+	}
+
+	@Test
+	@DisplayName("replace wildcards when keys exist")
+	public void replaceWildcardswithExistingKeys() {
+		final Map<String, String> replacements = new HashMap<>();
+		replacements.put("user", "John");
+		replacements.put("city", "Graz");
+
+		assertEquals("Greetings John from Graz", StringReplacer.replace("Greetings ${user} from ${city}", replacements));
+	}
+
+	@Test
+	@DisplayName("replace wildcards when some keys exist")
+	public void replaceWildcardswithPartiallyExistingKeys() {
+		final Map<String, String> replacements = new HashMap<>();
+		replacements.put("user", "John");
+
+		assertEquals("Greetings John from", StringReplacer.replace("Greetings ${user} from ${city}", replacements));
+	}
+
+	@Test
+	@DisplayName("replace wildcards containing regex")
+	public void replaceWildcardsContainingRegex() {
+		final Map<String, String> replacements = new HashMap<>();
+		replacements.put("user.name", "John");
+
+		assertEquals("Greetings John /", StringReplacer.replace("Greetings ${user.name} / ${userXname}", replacements));
+	}
+}
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserFactoryTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserFactoryTest.java
index 5fe78c1..be5b244 100644
--- a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserFactoryTest.java
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserFactoryTest.java
@@ -86,4 +86,12 @@
 		for (final ISkill skill : user.getSkills())
 			assertEquals(0, skill.getExperience());
 	}
+
+	@Test
+	@DisplayName("default user has a title")
+	public void userHasATitle() {
+		final IUser user = new UserFactory().createUser();
+
+		assertNotNull(user.getTitle());
+	}
 }
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserStorageTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserStorageTest.java
index 2ea8c61..870384e 100644
--- a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserStorageTest.java
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserStorageTest.java
@@ -41,7 +41,7 @@
 	private static final byte[] DEFAULT_USER = ("<?xml version=\"1.0\" encoding=\"ASCII\"?>\n"
 			+ "<skills:User xmi:version=\"2.0\" xmlns:xmi=\"http://www.omg.org/XMI\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:skills=\"http://eclipse.org/skills/1.0.0\" name=\"John Doe\">\n"
 			+ "  <usertasks>\n"
-			+ "    <task href=\"platform:/plugin/org.eclipse.skills/resources/UnitTest.skills#UnitTest_Quest_First_task\"/>\n"
+			+ "    <task href=\"platform:/plugin/org.eclipse.skills/resources/UserStorageTest.skills#UserStorageTest_Quest_First_task\"/>\n"
 			+ "  </usertasks>\n"
 			+ "  <experience name=\"Experience\" experience=\"30\">\n"
 			+ "    <progression xsi:type=\"skills:FactorProgression\"/>\n"
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserTitleGeneratorTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserTitleGeneratorTest.java
new file mode 100644
index 0000000..bf004e0
--- /dev/null
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/UserTitleGeneratorTest.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.skills.service;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.skills.model.IQuest;
+import org.eclipse.skills.model.ISkill;
+import org.eclipse.skills.model.ISkillsFactory;
+import org.eclipse.skills.model.IUser;
+import org.eclipse.skills.model.IUserTitle;
+import org.eclipse.skills.service.questprovider.IQuestProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class UserTitleGeneratorTest {
+
+	private IUser fUser;
+	private QuestProviderMock fQuestProvider;
+	private UserTitleGenerator fTitleGenerator;
+
+	@BeforeEach
+	public void setup() {
+		fUser = new UserFactory().createUser();
+		fQuestProvider = new QuestProviderMock();
+
+		fTitleGenerator = new UserTitleGenerator(fUser, fQuestProvider);
+	}
+
+	@Test
+	@DisplayName("throw IllegalArgumentexception when user is null")
+	public void throwIfUserIsNull() {
+		assertThrows(IllegalArgumentException.class, () -> new UserTitleGenerator(null, fQuestProvider));
+	}
+
+	@Test
+	@DisplayName("throw IllegalArgumentexception when questProvider is null")
+	public void throwIfQuestProviderIsNull() {
+		assertThrows(IllegalArgumentException.class, () -> new UserTitleGenerator(fUser, null));
+	}
+
+	@Test
+	@DisplayName("empty string  is returned when no titles are defined")
+	public void createDefaultUserTitle() {
+		assertEquals("", fTitleGenerator.createUserTitle());
+	}
+
+	@Test
+	@DisplayName("skill name is replaced in title string")
+	public void replaceSkillName() {
+		addTitle("${skill.name} expert", 0, "mySkill");
+
+		final ISkill skill = ISkillsFactory.eINSTANCE.createSkill();
+		skill.setName("mySkill");
+		fUser.getSkills().add(skill);
+
+		assertEquals("mySkill expert", fTitleGenerator.createUserTitle());
+	}
+
+	@Test
+	@DisplayName("skill name is not replaced when no skill is defined")
+	public void discardSkillName() {
+		addTitle("${skill.name} expert", 0, null);
+
+		assertEquals("expert", fTitleGenerator.createUserTitle());
+	}
+
+	@Test
+	@DisplayName("select title for highest matching skill (level = 1)")
+	public void selectTitleForBestMatchingSkillA() {
+		addTitle("Beginner", 0, null);
+		addTitle("Intermediate", 2, null);
+		addTitle("Advanced", 3, null);
+		addTitle("Expert", 4, null);
+
+		final int xpNeeded = fUser.getExperience().getProgression().getMinimumXpForLevel(2);
+		fUser.getExperience().setExperience(xpNeeded - 1);
+
+		assertEquals("Beginner", fTitleGenerator.createUserTitle());
+	}
+
+	@Test
+	@DisplayName("select title for highest matching skill (level = 2)")
+	public void selectTitleForBestMatchingSkillB() {
+		addTitle("Beginner", 0, null);
+		addTitle("Intermediate", 2, null);
+		addTitle("Advanced", 3, null);
+		addTitle("Expert", 4, null);
+
+		final int xpNeeded = fUser.getExperience().getProgression().getMinimumXpForLevel(2);
+		fUser.getExperience().setExperience(xpNeeded);
+
+		assertEquals("Intermediate", fTitleGenerator.createUserTitle());
+	}
+
+	@Test
+	@DisplayName("select title for highest matching skill (level = 4)")
+	public void selectTitleForBestMatchingSkillC() {
+		addTitle("Beginner", 0, null);
+		addTitle("Intermediate", 2, null);
+		addTitle("Advanced", 3, null);
+		addTitle("Expert", 4, null);
+
+		final int xpNeeded = fUser.getExperience().getProgression().getMinimumXpForLevel(4);
+		fUser.getExperience().setExperience(xpNeeded);
+
+		assertEquals("Expert", fTitleGenerator.createUserTitle());
+	}
+
+	private void addTitle(String displayString, int minLevel, String skillName) {
+
+		final IQuest quest = fQuestProvider.getQuests().iterator().next();
+
+		ISkill skill = null;
+		if (skillName != null) {
+			skill = ISkillsFactory.eINSTANCE.createSkill();
+			skill.setName(skillName);
+
+			quest.getSkills().add(skill);
+		}
+
+		final IUserTitle title = ISkillsFactory.eINSTANCE.createUserTitle();
+		title.setDisplayString(displayString);
+		title.setMinLevel(minLevel);
+		title.setSkill(skill);
+
+		quest.getUserTitles().add(title);
+	}
+
+	private class QuestProviderMock implements IQuestProvider {
+
+		private final List<IQuest> fQuests;
+
+		public QuestProviderMock() {
+			fQuests = Arrays.asList(ISkillsFactory.eINSTANCE.createQuest());
+		}
+
+		@Override
+		public Collection<IQuest> getQuests() {
+			return fQuests;
+		}
+	}
+}
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/ExtensionPointQuestProviderTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/questprovider/ExtensionPointQuestProviderTest.java
similarity index 89%
rename from tests/org.eclipse.skills.test/src/org/eclipse/skills/service/ExtensionPointQuestProviderTest.java
rename to tests/org.eclipse.skills.test/src/org/eclipse/skills/service/questprovider/ExtensionPointQuestProviderTest.java
index 01ec776..0993107 100644
--- a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/ExtensionPointQuestProviderTest.java
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/questprovider/ExtensionPointQuestProviderTest.java
@@ -11,12 +11,14 @@
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
 
-package org.eclipse.skills.service;
+package org.eclipse.skills.service.questprovider;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.eclipse.skills.model.IQuest;
+import org.eclipse.skills.service.questprovider.ExtensionPointQuestProvider;
+import org.eclipse.skills.service.questprovider.IQuestProvider;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
diff --git a/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/questprovider/URIQuestProviderTest.java b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/questprovider/URIQuestProviderTest.java
new file mode 100644
index 0000000..9418f35
--- /dev/null
+++ b/tests/org.eclipse.skills.test/src/org/eclipse/skills/service/questprovider/URIQuestProviderTest.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.skills.service.questprovider;
+
+import static org.junit.Assert.assertFalse;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.skills.model.IQuest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class URIQuestProviderTest {
+
+	@Test
+	@DisplayName("Read quests from URI")
+	public void readQuestsFromURIString() {
+		final IQuestProvider provider = new URIQuestProvider("platform:/plugin/org.eclipse.skills.test/resources/URIQuestProviderTest.skills");
+
+		assertFalse(provider.getQuests().isEmpty());
+
+		final IQuest quest = provider.getQuests().iterator().next();
+		assertFalse(quest.getSkills().isEmpty());
+		assertFalse(quest.getTasks().isEmpty());
+		assertFalse(quest.getUserTitles().isEmpty());
+	}
+
+	@Test
+	@DisplayName("Read quests from URI")
+	public void readQuestsFromURI() {
+		final IQuestProvider provider = new URIQuestProvider(URI.createURI("platform:/plugin/org.eclipse.skills.test/resources/URIQuestProviderTest.skills"));
+
+		assertFalse(provider.getQuests().isEmpty());
+
+		final IQuest quest = provider.getQuests().iterator().next();
+		assertFalse(quest.getSkills().isEmpty());
+		assertFalse(quest.getTasks().isEmpty());
+		assertFalse(quest.getUserTitles().isEmpty());
+	}
+}