Sam Jaffe 8 лет назад
Родитель
Сommit
467d5b9ffe
29 измененных файлов с 785 добавлено и 220 удалено
  1. 105 115
      resources/Potato.json
  2. 89 0
      schema/character.json
  3. 10 0
      schema/common.json
  4. 161 0
      schema/inventory.json
  5. 188 0
      schema/spell.json
  6. 106 0
      schema/spellbook.json
  7. 30 28
      src/main/lombok/org/leumasjaffe/charsheet/model/Ability.java
  8. 7 0
      src/main/lombok/org/leumasjaffe/charsheet/observer/ObserverHelper.java
  9. 3 4
      src/main/lombok/org/leumasjaffe/charsheet/observer/helper/AbilModStringify.java
  10. 20 0
      src/main/lombok/org/leumasjaffe/charsheet/util/AbilityHelper.java
  11. 2 3
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/ChooseSpellsPerDayHeader.java
  12. 3 3
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/PrepareSpellsDialog.java
  13. 3 3
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SelectPreparedSpellsPanel.java
  14. 2 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPanel.java
  15. 2 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPerDayPanel.java
  16. 4 4
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellPanel.java
  17. 2 3
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellsKnownHeader.java
  18. 2 3
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellsPerDayHeader.java
  19. 2 1
      src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLevelUpDialog.java
  20. 7 6
      src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLevelUpLine.java
  21. 6 6
      src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLine.java
  22. 3 3
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/AbilityBox.java
  23. 5 5
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/AbilityLine.java
  24. 4 5
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/AbilityPanel.java
  25. 3 4
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/ArmorLine.java
  26. 3 4
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/AttackLine.java
  27. 5 7
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/InitiativeLine.java
  28. 5 6
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/ResistanceLine.java
  29. 3 3
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/ResistancePanel.java

+ 105 - 115
resources/Potato.json

@@ -1,54 +1,54 @@
 {
-  "name":"Potato",
-  "player":"Corinne Kennedy",
-  "classes":[
+  "name": "Potato",
+  "player": "Corinne Kennedy",
+  "classes": [
     {
-      "level":3,
-      "name":"Cleric",
-      "spellBook":{
-        "@class":"org.leumasjaffe.charsheet.model.magic.impl.Inspired",
-        "classRef":"Cleric",
-        "spellInfo":{
-          "0":{
-            "spellsPerDay":4,
-            "spellsPrepared":[
+      "level": 3,
+      "name": "Cleric",
+      "spellBook": {
+        "@class": "org.leumasjaffe.charsheet.model.magic.impl.Inspired",
+        "classRef": "Cleric",
+        "spellInfo": {
+          "0": {
+            "spellsPerDay": 4,
+            "spellsPrepared": [
               "Create Water"
             ],
-            "spellsPreparedPreviously":[
+            "spellsPreparedPreviously": [
               "Create Water",
               "Create Water",
               "Create Water",
               "Create Water"
             ]
           },
-          "1":{
-            "spellsPerDay":2,
-            "spellsPrepared":[
+          "1": {
+            "spellsPerDay": 2,
+            "spellsPrepared": [
               "Cure Light Wounds"
             ],
-            "spellsPreparedPreviously":[
+            "spellsPreparedPreviously": [
               "Cure Light Wounds",
               "Cure Light Wounds"
             ]
           },
-          "2":{
-            "spellsPerDay":1,
-            "spellsPrepared":[],
-            "spellsPreparedPreviously":[]
+          "2": {
+            "spellsPerDay": 1,
+            "spellsPrepared": [],
+            "spellsPreparedPreviously": []
           }
         }
       }
     },
     {
-      "level":2,
-      "name":"Bard",
-      "spellBook":{
-        "@class":"org.leumasjaffe.charsheet.model.magic.impl.Spontaneous",
-        "spellInfo":{
-          "0":{
-            "spellsPerDay":3,
-            "spellsPerDayRemaining":3,
-            "spellsKnown":[
+      "level": 2,
+      "name": "Bard",
+      "spellBook": {
+        "@class": "org.leumasjaffe.charsheet.model.magic.impl.Spontaneous",
+        "spellInfo": {
+          "0": {
+            "spellsPerDay": 3,
+            "spellsPerDayRemaining": 3,
+            "spellsKnown": [
               "Know Direction",
               "Know Direction",
               "Know Direction",
@@ -56,110 +56,100 @@
               "Know Direction"
             ]
           },
-          "1":{
-            "spellsPerDay":0,
-            "spellsPerDayRemaining":0,
-            "spellsKnown":[]
+          "1": {
+            "spellsPerDay": 0,
+            "spellsPerDayRemaining": 0,
+            "spellsKnown": []
           }
         }
       }
     }
   ],
-  "race":"Half-Elemental (E)",
-  "alignment":"TN",
-  "deity":"Obad-Hai",
-  "size":"M",
-  "age":"25",
-  "gender":"F",
-  "height":"5'0\"",
-  "weight":200,
-  "eyes":"Green",
-  "hair":"Plants",
-  "skin":"Earthy",
+  "race": "Half-Elemental (E)",
+  "alignment": "TN",
+  "deity": "Obad-Hai",
+  "size": "M",
+  "age": "25",
+  "gender": "F",
+  "height": "5'0\"",
+  "weight": 200,
+  "eyes": "Green",
+  "hair": "Plants",
+  "skin": "Earthy",
   
-  "experience":10000,
+  "experience": 10000,
   
-  "health":{
-    "total":30,
-    "rolled":20,
-    "current":30,
-    "temp":0,
-    "nonlethal":0
+  "health": {
+    "total": 30,
+    "rolled": 20,
+    "current": 30,
+    "temp": 0,
+    "nonlethal": 0
   },
   
-  "abilities":{
-    "base":{
-      "str":14,
-      "dex":10,
-      "con":15,
-      "int":12,
-      "wis":17,
-      "cha":11
-    },
-    "temp":{
-      "str":-1,
-      "dex":-1,
-      "con":-1,
-      "int":-1,
-      "wis":-1,
-      "cha":-1
-    }
+  "abilities": {
+    "str": {"base": 14, "temp": -1},
+    "dex": {"base": 10, "temp": -1},
+    "con": {"base": 15, "temp": -1},
+    "int": {"base": 12, "temp": -1},
+    "wis": {"base": 17, "temp": -1},
+    "cha": {"base": 11, "temp": -1}
   },
   
-  "skills":[
-    {"name":"Concentration","ranks":3,"pointsSpent":3},
-    {"name":"Craft (blacksmithing)","ranks":4,"pointsSpent":4},
-    {"name":"Diplomacy","ranks":1,"pointsSpent":1},
-    {"name":"Heal","ranks":4,"pointsSpent":4},
-    {"name":"Knowledge (arcana)","ranks":6,"pointsSpent":6},
-    {"name":"Knowledge (religion)","ranks":5,"pointsSpent":5},
-    {"name":"Knowledge (nature)","ranks":6,"pointsSpent":6},
-    {"name":"Perform (sing)","ranks":3,"pointsSpent":3}
+  "skills": [
+    {"name": "Concentration", "ranks": 3, "pointsSpent": 3},
+    {"name": "Craft (blacksmithing)", "ranks": 4, "pointsSpent": 4},
+    {"name": "Diplomacy", "ranks": 1, "pointsSpent": 1},
+    {"name": "Heal", "ranks": 4, "pointsSpent": 4},
+    {"name": "Knowledge (arcana)", "ranks": 6, "pointsSpent": 6},
+    {"name": "Knowledge (religion)", "ranks": 5, "pointsSpent": 5},
+    {"name": "Knowledge (nature)", "ranks": 6, "pointsSpent": 6},
+    {"name": "Perform (sing)", "ranks": 3, "pointsSpent": 3}
   ],
   
-  "inventory":{
-    "items":[
+  "inventory": {
+    "items": [
       {
-        "name":"MWK Quarterstaff",
-        "count":1,
-        "countEquipped":1,
-        "value":{"pp":0,"gp":600,"sp":0,"cp":0},
-        "page":"PH116",
-        "slot":"TWO_HANDS",
-        "weight":4,
-        "weapon":{
-          "attackBonus":1,
-          "damageBonus":0,
-          "damage":"1d6",
-          "secondaryDamage":"1d6",
-          "criticalThreat":20,
-          "criticalDamage":2,
-          "range":"Melee",
-          "type":"Bludgeoning"
+        "name": "MWK Quarterstaff",
+        "count": 1,
+        "countEquipped": 1,
+        "value": {"pp": 0, "gp": 600, "sp": 0, "cp": 0},
+        "page": "PH116",
+        "slot": "TWO_HANDS",
+        "weight": 4,
+        "weapon": {
+          "attackBonus": 1,
+          "damageBonus": 0,
+          "damage": "1d6",
+          "secondaryDamage": "1d6",
+          "criticalThreat": 20,
+          "criticalDamage": 2,
+          "range": "Melee",
+          "type": "Bludgeoning"
         }
       },
       {
-        "name":"+1 Full Plate Armor",
-        "count":1,
-        "countEquipped":1,
-        "value":{"pp":0,"gp":2650,"sp":0,"cp":0},
-        "page":"PH123",
-        "slot":"BODY",
-        "weight":50,
-        "armor":{
-          "bonus":9,
-          "type":"Heavy",
-          "maxDex":1,
-          "checkPenalty":-5,
-          "spellFailure":35,
-          "speed":15
+        "name": "+1 Full Plate Armor",
+        "count": 1,
+        "countEquipped": 1,
+        "value": {"pp": 0, "gp": 2650, "sp": 0, "cp": 0},
+        "page": "PH123",
+        "slot": "BODY",
+        "weight": 50,
+        "armor": {
+          "bonus": 9,
+          "type": "Heavy",
+          "maxDex": 1,
+          "checkPenalty": -5,
+          "spellFailure": 35,
+          "speed": 15
         }
       }
     ],
-    "equipment":{
-      "BODY":"+1 Full Plate Armor",
-      "MAIN_HAND":"MWK Quarterstaff",
-      "OFF_HAND":"MWK Quarterstaff"
+    "equipment": {
+      "BODY": "+1 Full Plate Armor",
+      "MAIN_HAND": "MWK Quarterstaff",
+      "OFF_HAND": "MWK Quarterstaff"
     }
   }
-}
+}

+ 89 - 0
schema/character.json

@@ -0,0 +1,89 @@
+{
+  "$schema": "http://json-schema.org/draft-06/schema#",
+  "properties": {
+    "name": {"type": "string"},
+    "player": {"type": "string"},
+    "classes": {
+      "items": {"$ref": "#/definitions/classDetail"},
+      "type": "array"
+    },
+    "race": {"type": "string"},
+    "alignment": {
+      "type": "string",
+      "enum": [
+        "LG", "NG", "CG", "LN", "TN", "CN", "LE", "NE", "CE"
+      ]
+    },
+    "deity": {"type": "string"},
+    "size": {
+      "type": "string",
+      "enum": [
+        "F", "D", "T", "S", "M", "L", "H", "G", "C"
+      ]
+    },
+    "age": {"type": "string"},
+    "gender": {"type": "string", "enum": ["M", "F", "N"]},
+    "height": {"type": "string"},
+    "weight": {"type": "number"},
+    "eyes": {"type": "string"},
+    "hair": {"type": "string"},
+    "skin": {"type": "string"},
+    "experience": {"type": "integer"},
+    "health": {"$ref": "#/definitions/hitpoints"},
+    "abilities": {
+      "propertyNames": {
+        "type": "string",
+        "enum": ["str", "dex", "con", "int", "wis", "cha"]
+      },
+      "additionalProperties": {
+        "properties": {
+          "base": {"$ref": "#/definitions/abilityScore"},
+          "temp": {"$ref": "#/definitions/abilityScore"}
+        },
+        "additionalProperties": false,
+        "type": "object"
+      }
+    },
+    "skills": {
+      "items": {"$ref": "#/definitions/skill"},
+      "type": "array"
+    },
+    "inventory": {"$ref": "file:inventory.json#"}
+  },
+  "type": "object",
+  "definitions": {
+    "abilityScore": {
+      "type": "integer",
+      "oneOf": [
+        {"const": -1},
+        {"exclusiveMinimum": 0}
+      ]
+    },
+    "classDetail": {
+      "properties": {
+        "level": {"type": "integer", "minimum": 1},
+        "name": {"type": "string"},
+        "spellBook": {"$ref": "file:spellbook.json#"}
+      },
+      "type": "object"
+    },
+    "hitpoints": {
+      "properties": {
+        "total": {"type": "integer", "minimum": 1},
+        "rolled": {"type": "integer", "minimum": 1},
+        "current": {"type": "integer", "minimum": -10},
+        "temp": {"type": "integer", "minimum": 0},
+        "nonlethal": {"type": "integer", "minimum": 0}
+      },
+      "type":"object"
+    },
+    "skill": {
+      "properties": {
+        "name": {"type": "string"},
+        "ranks": {"type": "integer"},
+        "pointsSpent": {"type": "integer"}
+      },
+      "type": "object"
+    }
+  }
+}

+ 10 - 0
schema/common.json

@@ -0,0 +1,10 @@
+{
+  "$schema": "http://json-schema.org/draft-06/schema#",
+  "definitions": {
+    "distance": {
+      "type": "integer",
+      "exclusiveMinimum": 0,
+      "multipleOf": 5
+    }
+  }
+}

+ 161 - 0
schema/inventory.json

@@ -0,0 +1,161 @@
+{
+  "$schema": "http://json-schema.org/draft-06/schema#",
+  "properties": {
+    "items": {
+      "items": {
+        "properties": {
+          "name": {"type": "string"},
+          "count": {"type": "integer", "minimum": 1},
+          "countEquipped": {"type": "integer", "minimum": 0},
+          "value": {"$ref": "#/definitions/money"},
+          "page": {"type": "string"},
+          "slot": {
+            "allOf":[
+              {"$ref": "#/definitions/slot"},
+              {"not": {"enum": ["RING1", "RING2"]}}
+            ]
+          },
+          "weight": {"type": "number"}
+        },
+        "oneOf": [
+          {
+            "properties": {
+              "weapon": {"$ref": "#/definitions/weapon"},
+              "armor": false
+            }
+          },
+          {
+            "properties": {
+              "weapon": false,
+              "armor": {"$ref": "#/definitions/armor"}
+            }
+          },
+          {
+            "properties": {"weapon": false, "armor": false}
+          }
+        ],
+        "required": [
+          "name",
+          "count",
+          "value",
+          "weight"
+        ],
+        "type": "object"
+      },
+      "type": "array"
+    },
+    "equipment": {"$ref": "#/definitions/loadout"},
+    "favorites": {
+      "additionalProperties": {
+        "$ref": "#/definitions/loadout"
+      }
+    },
+    "wealth": {"$ref": "#/definitions/money"}
+  },
+  "definitions": {
+    "loadout": {
+      "propertyNames": {"$ref": "#/definitions/slot"},
+      "properties": {
+        "RING": false,
+        "ONE_HAND": false,
+        "TWO_HANDS": false
+      },
+      "additionalProperties": {"type": "string"},
+      "type": "object"
+    },
+    "slot": {
+      "type": "string",
+      "enum": [
+        "HEAD",
+        "FACE",
+        "NECK",
+        "TORSO",
+        "BODY",
+        "WAIST",
+        "SHOULDER",
+        "ARM",
+        "HAND",
+        "RING",
+        "RING1",
+        "RING2",
+        "FEET",
+        "MAIN_HAND",
+        "OFF_HAND",
+        "ONE_HAND",
+        "TWO_HANDS"
+      ]
+    },
+    "weapon": {
+      "dependencies":{
+        "secondaryCriticalDamage": ["secondaryDamage"]
+      },
+      "properties": {
+        "attackBonus": {"type": "integer"},
+        "damageBonus": {"type": "integer"},
+        "damage": {"type": "string"},
+        "secondaryDamage": {"type": "string"},
+        "criticalThreat": {"type": "integer", "maximum": 20},
+        "criticalDamage": {"type": "integer", "minimum": 2},
+        "secondaryCriticalDamage": {"type": "integer", "minimum": 2},
+        "range": {
+          "oneOf": [
+            {"type": "string", "const":"Melee"},
+            {"$ref": "file:common.json#/definitions/distance"}
+          ]
+        },
+        "type": {
+          "type": "string", 
+          "enum": [
+            "Slashing",
+            "Piercing",
+            "Bludgeoning"
+          ]
+        }
+      },
+      "required": [
+        "damage",
+        "type",
+        "range",
+        "criticalThreat",
+        "criticalDamage"
+      ],
+      "type": "object",
+      "additionalProperties": false
+    },
+    "armor": {
+      "dependencies": {
+        "speed": {"properties": {"type": {"not": {"const": "Shield"}}}}
+      },
+      "properties": {
+        "bonus": {"type": "integer", "minimum": 0},
+        "type": {
+          "type": "string", 
+          "enum": ["Light", "Medium", "Heavy", "Shield"]
+        },
+        "maxDex": {"type": "integer", "minimum": 0},
+        "checkPenalty": {"type": "integer", "maximum": 0},
+        "spellFailure": {"type": "integer", "minimum": 0, "maximum": 100},
+        "speed": {"type": "integer"}
+      },
+      "required": [
+        "bonus",
+        "type",
+        "checkPenalty",
+        "spellFailure"
+      ],
+      "type": "object",
+      "additionalProperties": false
+    },
+    "money": {
+      "properties": {
+        "pp": {"type": "integer", "minimum": 0},
+        "gp": {"type": "integer", "minimum": 0},
+        "sp": {"type": "integer", "minimum": 0},
+        "cp": {"type": "integer", "minimum": 0}
+      },
+      "type": "object"
+    }
+  },
+  "type": "object",
+  "additionalProperties": false
+}

+ 188 - 0
schema/spell.json

@@ -0,0 +1,188 @@
+{
+  "$schema": "http://json-schema.org/draft-06/schema#",
+  "properties": {
+    "allowsSpellResistance": {
+      "type": "boolean"
+    },
+    "area": {
+      "oneOf":[
+        {"$ref": "#/definitions/area/Line"},
+        {"$ref": "#/definitions/area/Emission"},
+        {"$ref": "#/definitions/area/Sphere"},
+        {"$ref": "#/definitions/area/Cylinder"}
+      ]
+    },
+    "castingTime": {
+      "type": "string",
+      "enum": ["Free", "Swift", "Move", "Standard", "Full"]
+    },
+    "classToLevel": {
+      "patternProperties": {
+        "^.*$": {"type": "integer", "minimum":0, "maximum":9}
+      },
+      "type": "object"
+    },
+    "components": {
+      "items": { 
+        "enum": ["V", "S", "M", "F", "DF", "M_DF", "F_DF", "XP"]
+      },
+      "type": "array"
+    },
+    "description": {
+      "type": "string"
+    },
+    "duration": {
+      "oneOf":[
+        {"type": "string"},
+        {"$ref": "#/definitions/duration"}
+      ]
+    },
+    "effect": {"$ref": "#/definitions/effect"},
+    "keywords": {
+      "items": {"type": "string"},
+      "type": "array"
+    },
+    "name": {
+      "type": "string"
+    },
+    "range": {
+      "oneOf": [
+        {"enum": ["Touch", "Close", "Medium", "Long", "Unlimited"]},
+        {"$ref": "#/definitions/range"}
+      ]
+    },
+    "savingThrow": {
+      "type": "string"
+    },
+    "school": {
+      "type": "string"
+    },
+    "subSchool": {
+      "type": "string"
+    },
+    "target": {
+      "oneOf":[
+        {"type": "string"},
+        {"$ref": "#/definitions/target"}
+      ]
+    }
+  },
+  "required": [
+    "school",
+    "name",
+    "description",
+    "castingTime",
+    "effect",
+    "classToLevel",
+    "duration",
+    "range",
+    "components",
+    "keywords",
+    "savingThrow"
+  ],
+  "type": "object",
+  "definitions": {
+    "area": {
+      "Line": {
+        "properties":{
+          "@type": {"const": "Line"},
+          "distance": {"$ref": "file:common.json#/definitions/distance"}
+        },
+        "additionalProperties": false
+      },
+      "Emission": {
+        "properties":{
+          "@type": {"const": "Emission"},
+          "emit": {"enum": ["BURST", "EMANATION", "SPREAD", "NONE"]}
+        },
+        "additionalProperties": false
+      },
+      "Sphere": {
+        "properties":{
+          "@type": {"const": "Sphere"},
+          "radius": {
+            "oneOf": [
+              {"const": 0},
+              {"$ref": "file:common.json#/definitions/distance"}
+            ]
+          },
+          "emit": {"enum": ["BURST", "EMANATION", "SPREAD", "NONE"]},
+          "aroundYou": {"type": "boolean"}
+        },
+        "additionalProperties": false
+      },
+      "Cylinder": {
+        "properties":{
+          "@type": {"const": "Cylinder"},
+          "radius": {"$ref": "file:common.json#/definitions/distance"},
+          "height": {"$ref": "file:common.json#/definitions/distance"}
+        },
+        "additionalProperties": false
+      }
+    },
+    "duration": {
+      "dependencies":{
+        "per": ["step"]
+      },
+      "properties":{
+        "duration": {"type": "integer", "exclusiveMinimum": 0},
+        "per": {"type": "integer", "exclusiveMinimum": 0},
+        "step": {"type": "integer", "exclusiveMinimum": 0},
+        "unit":{
+          "enum": ["round", "minute", "hour", "day"]
+        }
+      },
+      "required": ["unit", "duration"],
+      "type": "object",
+      "additionalProperties": false
+    },
+    "effect": {
+      "dependencies":{
+        "beyond":["per", "resolved"],
+        "count":["resolved"],
+        "per":["resolved"],
+        "step":["per", "resolved"],
+        "upto":["per", "resolved"]
+      },
+      "properties": {
+        "beyond": {"type": "integer", "exclusiveMinimum": 0},
+        "count": {"type": "integer", "exclusiveMinimum": 0},
+        "format": {"type": "string"},
+        "per": {"type": "integer", "exclusiveMinimum": 0},
+        "resolved": {"type": "string"},
+        "step": {"type": "integer", "exclusiveMinimum": 0},
+        "upto": {"type": "integer", "exclusiveMinimum": 0}
+      },
+      "required": ["format"],
+      "type": "object",
+      "additionalProperties": false
+    },
+    "range": {
+      "dependencies": {
+        "per": ["step"]
+      },
+      "properties": {
+        "name": {"type": "string"},
+        "range": {"$ref": "file:common.json#/definitions/distance"},
+        "per": {"$ref": "file:common.json#/definitions/distance"},
+        "step": {"type": "integer", "exclusiveMinimum": 0}
+      },
+      "type": "object",
+      "required": ["name", "range"],
+      "additionalProperties": false
+    },
+    "target": {
+      "properties": {
+        "count": {"type": "integer", "exclusiveMinimum": 0},
+        "format": {"type": "string"},
+        "per": {"type": "integer", "exclusiveMinimum": 0},
+        "resolved": {"type": "string"},
+        "step": {"type": "integer", "exclusiveMinimum": 0}
+      },
+      "required": ["format", "resolved", "count", "per", "step"],
+      "type": "object",
+      "additionalProperties": false
+    }
+  }
+}
+

+ 106 - 0
schema/spellbook.json

@@ -0,0 +1,106 @@
+{
+  "$schema": "http://json-schema.org/draft-06/schema#",
+  "properties": {
+    "@class": {"type": "string"},
+    "classRef": {"type": "string"},
+    "spellInfo": {
+      "dependencies":{
+        "2":["1"],
+        "3":["2"],
+        "4":["3"],
+        "5":["4"],
+        "6":["5"],
+        "7":["6"],
+        "8":["7"],
+        "9":["8"]
+      },
+      "propertyNames": {
+        "enum": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+      },
+      "additionalProperties": {
+        "spellsPerDay": {"type": "integer"},
+        "spellsPerDayRemaining": {"type": "integer"},
+        "spellsKnown": {"$ref": "#/definitions/names"},
+        "spellsPrepared": {"$ref": "#/definitions/names"},
+        "spellsPreparedPreviously": {"$ref": "#/definitions/names"}
+      },
+      "type": "object"
+    }
+  },
+  "oneOf": [
+    {
+      "properties": {
+        "@class": {"const": "org.leumasjaffe.charsheet.model.magic.impl.Inspired"},
+        "spellInfo": {"$ref": "#/definitions/castingType/Inspired"}
+      },
+      "required": ["classRef"]
+    },
+    {
+      "properties": {
+        "@class": {"const": "org.leumasjaffe.charsheet.model.magic.impl.Spontaneous"},
+        "spellInfo": {"$ref": "#/definitions/castingType/Spontaneous"}
+      }
+    },
+    {
+      "properties": {
+        "@class": {"const": "org.leumasjaffe.charsheet.model.magic.impl.Researched"},
+        "spellInfo": {"$ref": "#/definitions/castingType/Researched"}
+      }
+    },
+    {
+      "properties": {
+        "@class": {"const": "org.leumasjaffe.charsheet.model.magic.impl.Retrieved"},
+        "spellInfo": {"$ref": "#/definitions/castingType/Retrieved"}
+      },
+      "required": ["classRef"]
+    }
+  ],
+  "required": [
+    "@class",
+    "spellInfo"
+  ],
+  "type": "object",
+  "additionalProperties": false,
+  "definitions": {
+    "names": {
+      "items": {"type": "string"},
+      "type": "array"
+    },
+    "castingType": {
+      "Inspired": {
+        "additionalProperties": {
+          "required": ["spellsPerDay", "spellsPrepared", "spellsPreparedPreviously"],
+          "properties": {
+            "spellsPerDayRemaining": false,
+            "spellsKnown": false
+          }
+        }
+      },
+      "Spontaneous": {
+        "additionalProperties": {
+          "required": ["spellsPerDay", "spellsPerDayRemaining", "spellsKnown"],
+          "properties": {
+            "spellsPrepared": false,
+            "spellsPreparedPreviously": false
+          }
+        }
+      },
+      "Researched": {
+        "additionalProperties": {
+          "required": ["spellsPerDay", "spellsKnown", "spellsPrepared", "spellsPreparedPreviously"],
+          "properties": {
+            "spellsPerDayRemaining": false
+          }
+        }
+      },
+      "Retrieved": {
+        "additionalProperties": {
+          "required": ["spellsPerDay", "spellsPerDayRemaining", "spellsPrepared", "spellsPreparedPreviously"],
+          "properties": {
+            "spellsKnown": false
+          }
+        }
+      }
+    }
+  }
+}

+ 30 - 28
src/main/lombok/org/leumasjaffe/charsheet/model/Ability.java

@@ -6,6 +6,7 @@ import java.util.Map;
 import java.util.function.Function;
 
 import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.observer.Observable;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -13,58 +14,59 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
 import lombok.Data;
-import lombok.NonNull;
+import lombok.EqualsAndHashCode;
 import lombok.experimental.FieldDefaults;
 
 @Data
 @AllArgsConstructor
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class Ability {
-	public static final Map<String, Function<Scores, IntValue>> fields;
+	public static final Map<String, Function<Ability, Scores>> fields;
 	
 	static {
-		Map<String, Function<Scores, IntValue>> tmp = new HashMap<>();
-		tmp.put("STR", Scores::getStr);
-		tmp.put("DEX", Scores::getDex);
-		tmp.put("CON", Scores::getCon);
-		tmp.put("INT", Scores::getInt);
-		tmp.put("WIS", Scores::getWis);
-		tmp.put("CHA", Scores::getCha);
+		Map<String, Function<Ability, Scores>> tmp = new HashMap<>();
+		tmp.put("STR", Ability::getStr);
+		tmp.put("DEX", Ability::getDex);
+		tmp.put("CON", Ability::getCon);
+		tmp.put("INT", Ability::getInt);
+		tmp.put("WIS", Ability::getWis);
+		tmp.put("CHA", Ability::getCha);
 		
 		fields = Collections.unmodifiableMap(tmp);
 	}
 	
 	@Data
 	@AllArgsConstructor
+	@EqualsAndHashCode(callSuper=false)
 	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-	public static class Scores {
-		IntValue str; 
-		IntValue dex; 
-		IntValue con; 
-		@JsonProperty(value="int") IntValue Int; 
-		IntValue wis; 
-		IntValue cha;
+	public static class Scores extends Observable {
+		IntValue base, temp;
 		
 		@JsonCreator
-		Scores() {
-			this.str = new IntValue();
-			this.dex = new IntValue();
-			this.con = new IntValue();
-			this.Int = new IntValue();
-			this.wis = new IntValue();
-			this.cha = new IntValue();
+		public Scores() {
+			this.base = new IntValue(-1);
+			this.temp = new IntValue(-1);
 		}
+		
+		public int baseScore() { return base.value(); }
+		public int score() { return temp.value() == -1 ? base.value() : temp.value(); }
+		public int baseModifier() { return Ability.modifier(baseScore()); }
+		public int modifier() { return Ability.modifier(score()); }
 	}
 	
-	@NonNull Scores base;
-	@NonNull Scores temp;
+	Scores str, dex, con, wis, cha; 
+	@JsonProperty(value="int") Scores Int;
 	
 	public Ability() {
-		this.base = new Scores();
-		this.temp = new Scores();
+		this.str = new Scores();
+		this.dex = new Scores();
+		this.con = new Scores();
+		this.Int = new Scores();
+		this.wis = new Scores();
+		this.cha = new Scores();
 	}
 	
 	public static int modifier(int val) {
-		return val / 2 - 5;
+		return val == -1 ? 0 : val / 2 - 5;
 	}
 }

+ 7 - 0
src/main/lombok/org/leumasjaffe/charsheet/observer/ObserverHelper.java

@@ -1,5 +1,6 @@
 package org.leumasjaffe.charsheet.observer;
 
+import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.observer.ObserverDispatch;
 
@@ -11,4 +12,10 @@ public class ObserverHelper {
 		ObserverDispatch.notifySubscribers(dclass, src);
 		dclass.getSpellBook().ifPresent(sb -> ObserverDispatch.notifySubscribers(sb, src));
 	}
+	
+	public void notifyObservableHierarchy(final Ability.Scores abil, final Object src) {
+		ObserverDispatch.notifySubscribers(abil, src);
+		ObserverDispatch.notifySubscribers(abil.getBase(), src);
+		ObserverDispatch.notifySubscribers(abil.getTemp(), src);
+	}
 }

+ 3 - 4
src/main/lombok/org/leumasjaffe/charsheet/observer/helper/AbilModStringify.java

@@ -5,14 +5,13 @@ import java.util.function.BiConsumer;
 import javax.swing.text.JTextComponent;
 
 import org.leumasjaffe.charsheet.model.Ability;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.util.StringHelper;
 
-public class AbilModStringify implements BiConsumer<JTextComponent, IntValue> {
+public class AbilModStringify implements BiConsumer<JTextComponent, Ability.Scores> {
 
 	@Override
-	public void accept(JTextComponent c, IntValue v) {
-		c.setText(StringHelper.toString(Ability.modifier(v.value())));
+	public void accept(JTextComponent c, Ability.Scores v) {
+		c.setText(StringHelper.toString(v.modifier()));
 	}
 
 }

+ 20 - 0
src/main/lombok/org/leumasjaffe/charsheet/util/AbilityHelper.java

@@ -0,0 +1,20 @@
+package org.leumasjaffe.charsheet.util;
+
+import org.leumasjaffe.charsheet.model.Ability;
+import org.leumasjaffe.charsheet.model.Ability.Scores;
+import org.leumasjaffe.charsheet.model.DDCharacter;
+import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.model.skill.DDSkill;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class AbilityHelper {
+	public Ability.Scores get(final DDCharacter chara, final DDCharacterClass caster) {
+		return Ability.fields.get(caster.getProto().getSpells().get().getAbility()).apply(chara.getAbilities());
+	}
+	
+	public Scores get(DDCharacter chara, DDSkill skill) {
+		return Ability.fields.get(skill.getAbility()).apply(chara.getAbilities());
+	}
+}

+ 2 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/magic/ChooseSpellsPerDayHeader.java

@@ -10,7 +10,6 @@ import javax.swing.JTextField;
 
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.util.StringHelper;
 
 import java.awt.Dimension;
@@ -22,7 +21,7 @@ class ChooseSpellsPerDayHeader extends JPanel {
 	private static final long serialVersionUID = 1L;
 	private JTextField textFieldKnown;
 
-	public ChooseSpellsPerDayHeader(int level, DDSpellbook model, IntValue ability) {
+	public ChooseSpellsPerDayHeader(int level, DDSpellbook model, Ability.Scores ability) {
 		setPreferredSize(new Dimension(450, 20));
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 30, 0, 30, 0, 30, 0, 0, 0, 0};
@@ -57,7 +56,7 @@ class ChooseSpellsPerDayHeader extends JPanel {
 		gbc_lblSaveDc.gridy = 0;
 		add(lblSaveDc, gbc_lblSaveDc);
 		
-		JTextField textFieldSpellSave = new JTextField(Integer.toString(10 + level + Ability.modifier(ability.value())));
+		JTextField textFieldSpellSave = new JTextField(Integer.toString(10 + level + ability.modifier()));
 		textFieldSpellSave.setEditable(false);
 		textFieldSpellSave.setColumns(10);
 		GridBagConstraints gbc_textFieldSpellSave = new GridBagConstraints();

+ 3 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/magic/PrepareSpellsDialog.java

@@ -7,7 +7,7 @@ import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.impl.Prepared;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.charsheet.util.AbilityHelper;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
@@ -78,12 +78,12 @@ public class PrepareSpellsDialog extends JPanel {
 		JPanel panel = new JPanel(new VerticalLayout(5));
 		scrollPane.setViewportView(panel);
 		
-		final IntValue value = Ability.fields.get(dclass.getProto().getSpells().get().getAbility()).apply(chara.getAbilities().getBase());
+		final Ability.Scores score = AbilityHelper.get(chara, dclass);
 
 		final Prepared spellBook = (Prepared) dclass.getSpellBook().get();
 		List<SelectPreparedSpellsPanel> panels = new ArrayList<>();
 		for (int i = 0; i < highestSpellLevel; ++i) {
-			SelectPreparedSpellsPanel lvl = new SelectPreparedSpellsPanel(i, dclass, value);
+			SelectPreparedSpellsPanel lvl = new SelectPreparedSpellsPanel(i, dclass, score);
 			panels.add(lvl);
 			lvl.addPropertyChangeListener(SelectPreparedSpellsPanel.READY, e -> {
 				if ((Boolean) e.getNewValue()) ++ready[0];

+ 3 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SelectPreparedSpellsPanel.java

@@ -9,10 +9,10 @@ import javax.swing.JPopupMenu;
 import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
 
+import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.charsheet.model.magic.impl.Prepared;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.event.SelectTableRowPopupMenuListener;
 
@@ -80,7 +80,7 @@ class SelectPreparedSpellsPanel extends JPanel {
 	
 	SelectSpellModel modelPrepared, modelKnown;
 
-	public SelectPreparedSpellsPanel(int level, DDCharacterClass dclass, IntValue ability) {
+	public SelectPreparedSpellsPanel(int level, DDCharacterClass dclass, Ability.Scores score) {
 		putClientProperty(READY, true);
 		final Prepared spellBook = (Prepared) dclass.getSpellBook().get();
 		this.prepared = new ArrayList<>(spellBook.getSpellsPreparedPreviouslyForLevel(level));
@@ -95,7 +95,7 @@ class SelectPreparedSpellsPanel extends JPanel {
 		gridBagLayout.rowWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
 		setLayout(gridBagLayout);
 		
-		JPanel panel = new ChooseSpellsPerDayHeader(level, spellBook, ability);
+		JPanel panel = new ChooseSpellsPerDayHeader(level, spellBook, score);
 		GridBagConstraints gbc_panel = new GridBagConstraints();
 		gbc_panel.gridwidth = 3;
 		gbc_panel.insets = new Insets(0, 0, 5, 5);

+ 2 - 2
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPanel.java

@@ -9,9 +9,9 @@ import java.awt.Insets;
 import java.util.List;
 
 import org.jdesktop.swingx.VerticalLayout;
+import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.observer.IndirectObservableListener;
 
 import lombok.AccessLevel;
@@ -67,7 +67,7 @@ class SpellLevelPanel extends JPanel {
 		listener.setObserved(dclass, dclass.getSpellBook().get());
 	}
 	
-	public SpellLevelPanel(DDCharacterClass dclass, int level, IntValue ability) {
+	public SpellLevelPanel(DDCharacterClass dclass, int level, Ability.Scores ability) {
 		this(new SpellsKnownHeader(level, dclass.getSpellBook().get(), ability), dclass, level);
 	}
 	

+ 2 - 2
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPerDayPanel.java

@@ -1,7 +1,7 @@
 package org.leumasjaffe.charsheet.view.magic;
 
+import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
@@ -14,7 +14,7 @@ class SpellLevelPerDayPanel extends SpellLevelPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 	
-	public SpellLevelPerDayPanel(DDCharacterClass dclass, int level, IntValue ability) {
+	public SpellLevelPerDayPanel(DDCharacterClass dclass, int level, Ability.Scores ability) {
 		super(new SpellsPerDayHeader(level, dclass.getSpellBook().get(), ability), dclass, level);
 	}
 	

+ 4 - 4
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellPanel.java

@@ -12,7 +12,7 @@ import org.jdesktop.swingx.VerticalLayout;
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.charsheet.util.AbilityHelper;
 import org.leumasjaffe.function.TriFunction;
 import org.leumasjaffe.observer.IndirectObservableListener;
 
@@ -52,7 +52,7 @@ public class SpellPanel extends JPanel {
 		JScrollPane knownPane = new JScrollPane();
 		spellsPane.addTab("Known", null, knownPane, "Spells the player knows for this class");
 		
-		final IntValue ability = Ability.fields.get(dclass.getProto().getSpells().get().getAbility()).apply(chara.getAbilities().getBase());
+		final Ability.Scores ability = AbilityHelper.get(chara, dclass);
 		final JPanel prepared = new JPanel(new VerticalLayout());
 		preparedPane.setViewportView(prepared);
 		final JPanel known = new JPanel(new VerticalLayout());
@@ -70,8 +70,8 @@ public class SpellPanel extends JPanel {
 	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 	private static final class AppendSpellLevelOperation implements BiConsumer<JPanel, DDCharacterClass> {
 		@NonFinal int previousHighestSpellLevel = 0;
-		IntValue ability;
-		TriFunction<DDCharacterClass, Integer, IntValue, JPanel> function;
+		Ability.Scores ability;
+		TriFunction<DDCharacterClass, Integer, Ability.Scores, JPanel> function;
 
 		@Override
 		public void accept(final JPanel root, final DDCharacterClass dclass) {		

+ 2 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellsKnownHeader.java

@@ -10,7 +10,6 @@ import javax.swing.JTextField;
 
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import java.awt.Dimension;
 
@@ -20,7 +19,7 @@ class SpellsKnownHeader extends JPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 
-	public SpellsKnownHeader(int level, DDSpellbook model, IntValue ability) {
+	public SpellsKnownHeader(int level, DDSpellbook model, Ability.Scores ability) {
 		setPreferredSize(new Dimension(350, 20));
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 30, 0, 30, 0, 30, 0, 0};
@@ -57,7 +56,7 @@ class SpellsKnownHeader extends JPanel {
 		gbc_lblSaveDc.gridy = 0;
 		add(lblSaveDc, gbc_lblSaveDc);
 		
-		JTextField textFieldSpellSave = new JTextField(Integer.toString(10 + level + Ability.modifier(ability.value())));
+		JTextField textFieldSpellSave = new JTextField(Integer.toString(10 + level + ability.modifier()));
 		textFieldSpellSave.setPreferredSize(new Dimension(30, 20));
 		textFieldSpellSave.setMaximumSize(new Dimension(30, 20));
 		textFieldSpellSave.setEditable(false);

+ 2 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellsPerDayHeader.java

@@ -10,7 +10,6 @@ import javax.swing.JTextField;
 
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.observer.ObservableListener;
 
 import java.awt.Dimension;
@@ -23,7 +22,7 @@ class SpellsPerDayHeader extends JPanel {
 
 	ObservableListener<JTextField, DDSpellbook> listener;
 
-	public SpellsPerDayHeader(int level, DDSpellbook model, IntValue ability) {
+	public SpellsPerDayHeader(int level, DDSpellbook model, Ability.Scores ability) {
 		setPreferredSize(new Dimension(350, 20));
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 30, 0, 30, 0, 30, 0, 30, 0, 0};
@@ -58,7 +57,7 @@ class SpellsPerDayHeader extends JPanel {
 		gbc_lblSaveDc.gridy = 0;
 		add(lblSaveDc, gbc_lblSaveDc);
 		
-		JTextField textFieldSpellSave = new JTextField(Integer.toString(10 + level + Ability.modifier(ability.value())));
+		JTextField textFieldSpellSave = new JTextField(Integer.toString(10 + level + ability.modifier()));
 		textFieldSpellSave.setEditable(false);
 		textFieldSpellSave.setColumns(10);
 		GridBagConstraints gbc_textFieldSpellSave = new GridBagConstraints();

+ 2 - 1
src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLevelUpDialog.java

@@ -35,7 +35,8 @@ public class SkillLevelUpDialog extends JPanel {
 	private static final long serialVersionUID = 1L;
 	
 	public SkillLevelUpDialog(final DDCharacter chara, final DDCharacterClass cclass) {
-		final IntValue pointsAvaliable = new IntValue(Math.max(1, cclass.getSkillPoints() + Ability.modifier(chara.getAbilities().getBase().getInt().value())));
+		final IntValue pointsAvaliable = new IntValue(Math.max(1, cclass.getSkillPoints() + 
+				Ability.modifier(chara.getAbilities().getInt().baseModifier())));
 		
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0};

+ 7 - 6
src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLevelUpLine.java

@@ -7,6 +7,7 @@ import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.model.skill.DDSkill;
+import org.leumasjaffe.charsheet.util.AbilityHelper;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.observer.IndirectObservableListener;
 import org.leumasjaffe.observer.ObserverDispatch;
@@ -45,7 +46,7 @@ class SkillLevelUpLine extends JPanel {
 	
 	@Value
 	private static final class TotalPacket {
-		Optional<IntValue> ability;
+		Optional<Ability.Scores> ability;
 		DDSkill skill;
 		IntValue points;
 	}
@@ -217,17 +218,17 @@ class SkillLevelUpLine extends JPanel {
 		totalListener = new IndirectObservableListener<>(total,
 				(c, p) -> {
 					final int skillRanks = p.skill.getRanks().value();
-					final int mod = Ability.modifier(p.ability.map(v -> v.value()).orElse(10));
+					final int mod = p.ability.map(v -> v.baseModifier()).orElse(0);
 					c.setText(StringHelper.toString(skillRanks + mod + p.points.value()));
 				});
-		final Optional<IntValue> ability = getAbility(chara, skill);
-		ability.ifPresent(v -> modifier.setText(StringHelper.toString(Ability.modifier(v.value()))));
+		final Optional<Ability.Scores> ability = getAbility(chara, skill);
+		ability.ifPresent(v -> modifier.setText(StringHelper.toString(v.baseModifier())));
 		totalListener.setObserved(new TotalPacket(ability, skill, current), current);
 	}
 
-	private Optional<IntValue> getAbility(final DDCharacter chara, final DDSkill skill) {
+	private Optional<Ability.Scores> getAbility(final DDCharacter chara, final DDSkill skill) {
 		if (skill.getAbility().isEmpty()) { return Optional.empty(); }
-		else { return Optional.of(Ability.fields.get(skill.getAbility()).apply(chara.getAbilities().getBase())); }
+		else { return Optional.of(AbilityHelper.get(chara, skill)); }
 	}
 
 	void applyChange() {

+ 6 - 6
src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLine.java

@@ -6,6 +6,7 @@ import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.model.skill.DDSkill;
+import org.leumasjaffe.charsheet.util.AbilityHelper;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.observer.IndirectObservableListener;
 import org.leumasjaffe.observer.ObservableListener;
@@ -30,7 +31,8 @@ public class SkillLine extends JPanel {
 	 * 
 	 */
 	private static final long serialVersionUID = 1L;
-	ObservableListener<JTextField, IntValue> modifierListener, skillListener;
+	ObservableListener<JTextField, Ability.Scores> modifierListener;
+	ObservableListener<JTextField, IntValue> skillListener;
 	IndirectObservableListener<JTextField, DDCharacter> totalListener;
 
 	public SkillLine(final DDCharacter chara, final DDSkill skill) {
@@ -170,15 +172,13 @@ public class SkillLine extends JPanel {
 			totalListener = new IndirectObservableListener<>(total,
 					(c, v) -> {
 						final float skillRanks = skill.getRanks().value();
-						final int mod = Ability.modifier(Ability.fields.get(skill.getAbility())
-								.apply(chara.getAbilities().getBase()).value());
+						final int mod = AbilityHelper.get(chara, skill).modifier();
 						c.setText(StringHelper.toString(skillRanks + mod));
 					});
 			modifierListener = new ObservableListener<>(modifier, 
-					( c, v ) -> c.setText(StringHelper.toString(Ability.modifier(v.value()))));
+					( c, v ) -> c.setText(StringHelper.toString(v.modifier())));
 			
-			final IntValue abilScore = 	Ability.fields.get(skill.getAbility())
-					.apply(chara.getAbilities().getBase());
+			final Ability.Scores abilScore = AbilityHelper.get(chara, skill);
 			totalListener.setObserved(chara, skill.getRanks(), abilScore);
 			modifierListener.setObserved(abilScore);
 		}

+ 3 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/summary/AbilityBox.java

@@ -91,9 +91,9 @@ public class AbilityBox extends JPanel {
 					StringHelper.toString(modifier(v.value()))));
 	}
 
-	public void setModel(IntValue scores) {
-		valueListener.setObserved(scores);
-		modListener.setObserved(scores);
+	public void setModel(IntValue value) {
+		valueListener.setObserved(value);
+		modListener.setObserved(value);
 	}
 
 }

+ 5 - 5
src/main/lombok/org/leumasjaffe/charsheet/view/summary/AbilityLine.java

@@ -7,7 +7,6 @@ import java.awt.Font;
 import javax.swing.border.LineBorder;
 
 import org.leumasjaffe.charsheet.model.Ability;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
@@ -24,11 +23,11 @@ public class AbilityLine extends JPanel {
 	 * 
 	 */
 	private static final long serialVersionUID = 1L;
-	Function<Ability.Scores, IntValue> access;
+	Function<Ability, Ability.Scores> access;
 	AbilityBox ability;
 	AbilityBox temporary;
 	
-	public AbilityLine(final String name, Function<Ability.Scores, IntValue> func) {
+	public AbilityLine(final String name, Function<Ability, Ability.Scores> func) {
 		this.access = func;
 		
 		setMaximumSize(new Dimension(220, 26));
@@ -75,8 +74,9 @@ public class AbilityLine extends JPanel {
 	}
 
 	public void setModel(Ability model) {
-		this.ability.setModel(access.apply(model.getBase()));
-		this.temporary.setModel(access.apply(model.getTemp()));
+		final Ability.Scores score = access.apply(model);
+		this.ability.setModel(score.getBase());
+		this.temporary.setModel(score.getTemp());
 	}
 
 }

+ 4 - 5
src/main/lombok/org/leumasjaffe/charsheet/view/summary/AbilityPanel.java

@@ -11,7 +11,6 @@ import java.awt.Dimension;
 import java.awt.Color;
 
 import org.leumasjaffe.charsheet.model.Ability;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
@@ -23,9 +22,9 @@ public class AbilityPanel extends JPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 	private static final String[] abils = new String[] { "STR", "DEX", "CON", "INT", "WIS", "CHA" };
-	private static final List<Function<Ability.Scores, IntValue>> funcs = Arrays.asList( 
-		Ability.Scores::getStr, Ability.Scores::getDex, Ability.Scores::getCon,
-		Ability.Scores::getInt, Ability.Scores::getWis, Ability.Scores::getCha
+	private static final List<Function<Ability, Ability.Scores>> funcs = Arrays.asList( 
+		Ability::getStr, Ability::getDex, Ability::getCon,
+		Ability::getInt, Ability::getWis, Ability::getCha
 		);
 	AbilityLine[] lines = new AbilityLine[6];
 	
@@ -46,7 +45,7 @@ public class AbilityPanel extends JPanel {
 		}
 	}
 
-	private AbilityLine addAbility(int y, String name, Function<Ability.Scores, IntValue> func) {
+	private AbilityLine addAbility(int y, String name, Function<Ability, Ability.Scores> func) {
 		AbilityLine abilityLine = new AbilityLine(name, func);
 		abilityLine.setOpaque(false);
 		GridBagConstraints gbc_abilityLine = new GridBagConstraints();

+ 3 - 4
src/main/lombok/org/leumasjaffe/charsheet/view/summary/ArmorLine.java

@@ -12,7 +12,6 @@ import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.inventory.DDInventory;
 import org.leumasjaffe.charsheet.model.inventory.DDItem;
 import org.leumasjaffe.charsheet.model.inventory.EquipmentSlot;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.observer.IndirectObservableListener;
 import org.leumasjaffe.observer.ObservableListener;
@@ -360,7 +359,7 @@ public class ArmorLine extends JPanel {
 			final DDInventory inv = v.getInventory();
 			int iarmor = 0;
 			int ishield = 0;
-			int dex = Ability.modifier(v.getAbilities().getBase().getDex().value());
+			int dex = v.getAbilities().getDex().modifier();
 			int isize = v.getSize().value().modifier;
 			int inatural = 0;
 			int ideflect = 0;
@@ -402,7 +401,7 @@ public class ArmorLine extends JPanel {
 		
 		armorDexObserver = new IndirectObservableListener<>(dexterity, (c, v) -> {
 			final DDInventory inv = v.getInventory();
-			int dex = Ability.modifier(v.getAbilities().getBase().getDex().value());
+			int dex = v.getAbilities().getDex().modifier();
 			{
 				final DDItem body = inv.getEquipment().get(EquipmentSlot.BODY);
 				if ( body != null && body.isArmor() ) {
@@ -415,7 +414,7 @@ public class ArmorLine extends JPanel {
 	
 	public void setModel(final DDCharacter model) {
 		final DDInventory inv = model.getInventory();
-		final IntValue dex = model.getAbilities().getBase().getDex();
+		final Ability.Scores dex = model.getAbilities().getDex();
 		armorTotalObserver.setObserved(model, inv, dex);
 		armorArmorObserver.setObserved(inv);
 		armorShieldObserver.setObserved(inv);

+ 3 - 4
src/main/lombok/org/leumasjaffe/charsheet/view/summary/AttackLine.java

@@ -9,7 +9,6 @@ import javax.swing.border.LineBorder;
 
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.observer.helper.AbilModStringify;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.observer.IndirectObservableListener;
@@ -39,7 +38,7 @@ public class AttackLine extends JPanel {
 	JTextField baseAttack;
 	
 	IndirectObservableListener<JTextField, DDCharacter> gTtlObserver;
-	ObservableListener<JTextField, IntValue> gStrObserver;
+	ObservableListener<JTextField, Ability.Scores> gStrObserver;
 
 	public AttackLine() {
 		setPreferredSize(new Dimension(600, 25));
@@ -226,7 +225,7 @@ public class AttackLine extends JPanel {
 		gTtlObserver = new IndirectObservableListener<>(grappleTtl,
 				(c, v) -> {
 					final int bab = v.getBaseAttack();
-					final int str = Ability.modifier(this.model.getAbilities().getBase().getStr().value());
+					final int str = this.model.getAbilities().getStr().modifier();
 					final int size = v.getSize().value().modifier;
 					final int misc = 0;
 					c.setText(StringHelper.toString(bab + str + size + misc));
@@ -241,7 +240,7 @@ public class AttackLine extends JPanel {
 		final int size = this.model.getSize().value().modifier;
 		final int misc = 0;
 		this.baseAttack.setText(StringHelper.toString(bab));
-		final IntValue str = model.getAbilities().getBase().getStr();
+		final Ability.Scores str = model.getAbilities().getStr();
 		gTtlObserver.setObserved(model, str);
 		gStrObserver.setObserved(str);
 		this.grappleSize.setText(StringHelper.toString(size));

+ 5 - 7
src/main/lombok/org/leumasjaffe/charsheet/view/summary/InitiativeLine.java

@@ -5,13 +5,11 @@ import java.awt.GridBagLayout;
 import javax.swing.JLabel;
 import java.awt.GridBagConstraints;
 
-import static org.leumasjaffe.charsheet.model.Ability.modifier;
-
 import java.awt.Color;
 import javax.swing.border.LineBorder;
 
+import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.observer.helper.AbilModStringify;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.observer.IndirectObservableListener;
@@ -33,7 +31,7 @@ public class InitiativeLine extends JPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 	IndirectObservableListener<JTextField, DDCharacter> ttlObserver;
-	ObservableListener<JTextField, IntValue> dexObserver;
+	ObservableListener<JTextField, Ability.Scores> dexObserver;
 
 	public InitiativeLine() {
 		setOpaque(false);
@@ -127,15 +125,15 @@ public class InitiativeLine extends JPanel {
 		
 		ttlObserver = new IndirectObservableListener<>(total, 
 				(c, v) -> {
-					final int adex = v.getAbilities().getBase().getDex().value();
-					c.setText(StringHelper.toString( modifier(adex) ));
+					final int adex = v.getAbilities().getDex().modifier();
+					c.setText(StringHelper.toString( adex ));
 				});
 		dexObserver = new ObservableListener<>(dex, 
 				new AbilModStringify());
 	}
 
 	public void setModel(DDCharacter model) {
-		final IntValue dex = model.getAbilities().getBase().getDex();
+		final Ability.Scores dex = model.getAbilities().getDex();
 		ttlObserver.setObserved(model, dex);
 		dexObserver.setObserved(dex);
 	}

+ 5 - 6
src/main/lombok/org/leumasjaffe/charsheet/view/summary/ResistanceLine.java

@@ -9,7 +9,6 @@ import javax.swing.border.LineBorder;
 
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.observer.helper.AbilModStringify;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.graphics.NumberTextField;
@@ -34,14 +33,14 @@ public class ResistanceLine extends JPanel {
 	 * 
 	 */
 	private static final long serialVersionUID = 1L;
-	Function<Ability.Scores, IntValue> access;
+	Function<Ability, Ability.Scores> access;
 	
 	IndirectObservableListener<JTextField, DDCharacter> totalObserver;
 	IndirectObservableListener<JTextField, DDCharacter> baseObserver;
-	ObservableListener<JTextField, IntValue> abilObserver;
+	ObservableListener<JTextField, Ability.Scores> abilObserver;
 	
 	public ResistanceLine(final String name, Function<DDCharacter, Integer> save, 
-			Function<Ability.Scores, IntValue> func) {
+			Function<Ability, Ability.Scores> func) {
 		this.access = func;
 		
 		setMinimumSize(new Dimension(400, 25));
@@ -222,7 +221,7 @@ public class ResistanceLine extends JPanel {
 		totalObserver = new IndirectObservableListener<>(totalField,
 				(c, v) -> {
 					final int base = save.apply(v);
-					final int abil = Ability.modifier(access.apply(v.getAbilities().getBase()).value());
+					final int abil = access.apply(v.getAbilities()).modifier();
 					final int magic = 0;
 					final int misc = 0;
 					final int temp = 0;
@@ -235,7 +234,7 @@ public class ResistanceLine extends JPanel {
 	}
 	
 	public void setModel(DDCharacter model) {
-		final IntValue abil = access.apply(model.getAbilities().getBase());
+		final Ability.Scores abil = access.apply(model.getAbilities());
 		totalObserver.setObserved(model, abil);
 		baseObserver.setObserved(model, abil);
 		abilObserver.setObserved(abil);

+ 3 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/summary/ResistancePanel.java

@@ -31,21 +31,21 @@ public class ResistancePanel extends JPanel {
 		gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
 		setLayout(gridBagLayout);
 		
-		fortitude = new ResistanceLine("FORTITUDE", DDCharacter::getFortSave, Ability.Scores::getCon);
+		fortitude = new ResistanceLine("FORTITUDE", DDCharacter::getFortSave, Ability::getCon);
 		GridBagConstraints gbc_fortitude = new GridBagConstraints();
 		gbc_fortitude.fill = GridBagConstraints.BOTH;
 		gbc_fortitude.gridx = 0;
 		gbc_fortitude.gridy = 0;
 		add(fortitude, gbc_fortitude);
 		
-		reflex = new ResistanceLine("REFLEX", DDCharacter::getRefSave, Ability.Scores::getDex);
+		reflex = new ResistanceLine("REFLEX", DDCharacter::getRefSave, Ability::getDex);
 		GridBagConstraints gbc_reflex = new GridBagConstraints();
 		gbc_reflex.fill = GridBagConstraints.BOTH;
 		gbc_reflex.gridx = 0;
 		gbc_reflex.gridy = 1;
 		add(reflex, gbc_reflex);
 		
-		will = new ResistanceLine("WILL", DDCharacter::getWillSave, Ability.Scores::getWis);
+		will = new ResistanceLine("WILL", DDCharacter::getWillSave, Ability::getWis);
 		GridBagConstraints gbc_will = new GridBagConstraints();
 		gbc_will.fill = GridBagConstraints.BOTH;
 		gbc_will.gridx = 0;