Bläddra i källkod

Merge branch 'feats'

Sam Jaffe 8 år sedan
förälder
incheckning
2f8dec07b4
34 ändrade filer med 407 tillägg och 72 borttagningar
  1. 7 2
      resources/Potato.json
  2. 5 5
      resources/classes/Bard.json
  3. 1 0
      resources/classes/Cleric.json
  4. 45 0
      resources/feats/default.json
  5. 1 1
      resources/spells/default.json
  6. 1 0
      src/main/lombok/org/leumasjaffe/charsheet/config/Constants.java
  7. 18 6
      src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacter.java
  8. 11 0
      src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacterClass.java
  9. 6 1
      src/main/lombok/org/leumasjaffe/charsheet/model/DDClass.java
  10. 0 14
      src/main/lombok/org/leumasjaffe/charsheet/model/DDFeature.java
  11. 22 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeat.java
  12. 49 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeatFactory.java
  13. 19 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeaturePredicate.java
  14. 9 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDPrerequisite.java
  15. 16 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDProperty.java
  16. 31 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Flat.java
  17. 37 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/PerSpellLevel.java
  18. 15 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Simple.java
  19. 37 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/pred/SpellMatcher.java
  20. 1 6
      src/main/lombok/org/leumasjaffe/charsheet/model/magic/DDSpellFactory.java
  21. 1 6
      src/main/lombok/org/leumasjaffe/charsheet/model/magic/DDSpellList.java
  22. 2 1
      src/main/lombok/org/leumasjaffe/charsheet/model/magic/DDSpellbook.java
  23. 4 2
      src/main/lombok/org/leumasjaffe/charsheet/model/magic/dimension/Effect.java
  24. 1 3
      src/main/lombok/org/leumasjaffe/charsheet/model/magic/impl/Prepared.java
  25. 1 1
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/PrepareSpellsDialog.java
  26. 3 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SelectPreparedSpellsPanel.java
  27. 15 3
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellInfoPanel.java
  28. 10 6
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPanel.java
  29. 4 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPerDayPanel.java
  30. 3 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLine.java
  31. 3 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellMenu.java
  32. 6 5
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellPanel.java
  33. 17 2
      src/main/lombok/org/leumasjaffe/charsheet/view/summary/InitiativeLine.java
  34. 6 0
      src/main/lombok/org/leumasjaffe/function/QuadFunction.java

+ 7 - 2
resources/Potato.json

@@ -6,7 +6,7 @@
       "level": 3,
       "name": "Cleric",
       "spellBook": {
-        "@class": "org.leumasjaffe.charsheet.model.magic.impl.Inspired",
+        "@c": ".impl.Inspired",
         "classRef": "Cleric",
         "spellInfo": {
           "0": {
@@ -43,7 +43,7 @@
       "level": 2,
       "name": "Bard",
       "spellBook": {
-        "@class": "org.leumasjaffe.charsheet.model.magic.impl.Spontaneous",
+        "@c": ".impl.Spontaneous",
         "spellInfo": {
           "0": {
             "spellsPerDay": 3,
@@ -107,6 +107,11 @@
     {"name": "Perform (sing)", "ranks": 3, "pointsSpent": 3}
   ],
   
+  "feats": [
+    "Improved Initiative",
+    "Augment Healing"
+  ],
+  
   "inventory": {
     "items": [
       {

+ 5 - 5
resources/classes/Bard.json

@@ -6,11 +6,11 @@
   "will":"GOOD",
   "features":[
     [
-      {"name":"Bardic Music"},
-      {"name":"Bardic Knowledge"},
-      {"name":"Countersong"},
-      {"name":"Fascinate"},
-      {"name":"Inspire Courage +1"}
+      {"@c": ".impl.Simple", "name": "Bardic Music"},
+      {"@c": ".impl.Simple", "name": "Bardic Knowledge"},
+      {"@c": ".impl.Simple", "name": "Countersong"},
+      {"@c": ".impl.Simple", "name": "Fascinate"},
+      {"@c": ".impl.Simple", "name": "Inspire Courage +1"}
     ],
     []
   ],

+ 1 - 0
resources/classes/Cleric.json

@@ -7,6 +7,7 @@
   "features":[
     [
       {
+        "@c": ".impl.Simple",
         "name":"Turn or Rebuke Undead"
       }
     ]

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 45 - 0
resources/feats/default.json


+ 1 - 1
resources/spells/default.json

@@ -40,7 +40,7 @@
     "range":"Touch",
     "target":"Creature Touched",
     "effect":{
-      "format":"1d8 + {perlevel} per level (maximum +{upto})",
+      "format":"1d8 + {per} per level (maximum +{upto})",
       "resolved":"1d8 + {atlevel}",
       "per":1,
       "upto":5

+ 1 - 0
src/main/lombok/org/leumasjaffe/charsheet/config/Constants.java

@@ -8,6 +8,7 @@ import lombok.experimental.UtilityClass;
 public final class Constants {
 	public String PREVIOUS_LOADOUT = "$$PREVIOUS";
 	public String NO_FLAT_FOOTED = "Keeps Dexterity When Flat-footed";
+	public String INITIATIVE = "Character::Initiative";
 	
 	public String K_DISTANCE = "Distance Measurement Unit";
 	public enum DistanceMeasurement {

+ 18 - 6
src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacter.java

@@ -1,30 +1,38 @@
 package org.leumasjaffe.charsheet.model;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import org.leumasjaffe.charsheet.model.features.DDFeat;
+import org.leumasjaffe.charsheet.model.features.DDProperty;
 import org.leumasjaffe.charsheet.model.inventory.DDInventory;
 import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.model.observable.ObjectValue;
 import org.leumasjaffe.charsheet.model.skill.DDSkills;
+import org.leumasjaffe.observer.Observable;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
 import lombok.AccessLevel;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.NonNull;
 import lombok.experimental.FieldDefaults;
 
 @NoArgsConstructor
-@Data
+@Data @EqualsAndHashCode(callSuper=false)
 @FieldDefaults(level=AccessLevel.PRIVATE)
 @JsonIgnoreProperties(ignoreUnknown=true)
-public class DDCharacter {
+public class DDCharacter extends Observable {
 	@NonNull String name = "";
 	
 	@NonNull String player = "";
@@ -46,13 +54,10 @@ public class DDCharacter {
 	@NonNull String skin = "";
 	
 	@NonNull IntValue experience = new IntValue();
-	
 	@NonNull HitPoints health = new HitPoints();
-	
 	@NonNull Ability abilities = new Ability();
-	
 	@NonNull DDSkills skills = new DDSkills(Collections.emptyList());
-	
+	@NonNull List<DDFeat> feats = new ArrayList<>();
 	@NonNull DDInventory inventory = new DDInventory();
 
 	public String getClassAndLevelString() {
@@ -92,4 +97,11 @@ public class DDCharacter {
 	public int getLevel() {
 		return classes.stream().map(DDCharacterClass::getLevel).mapToInt(IntValue::value).sum();
 	}
+	
+	public List<DDProperty> getFeatureBonuses(Object appliesScope) {
+		return Stream.concat(feats.stream().flatMap(f -> f.getProperties().stream())
+				.filter(f -> f.appliesTo(appliesScope)),
+				classes.stream().flatMap(cl -> cl.getFeatureBonuses(appliesScope).stream()))
+				.collect(Collectors.toList());
+	}
 }

+ 11 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacterClass.java

@@ -2,7 +2,11 @@ package org.leumasjaffe.charsheet.model;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
+import org.leumasjaffe.charsheet.model.features.DDProperty;
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
 import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.observer.Observable;
@@ -93,4 +97,11 @@ public class DDCharacterClass extends Observable implements Comparable<DDCharact
 	public int compareTo(DDCharacterClass o) {
 		return getName().compareTo(o.getName());
 	}
+
+	public List<DDProperty> getFeatureBonuses(Object appliesScope) {
+		return IntStream.rangeClosed(1, level.value())
+				.mapToObj(level -> name.base.getFeatures(level).stream())
+				.reduce(Stream.empty(), (l, r) -> Stream.concat(l, r))
+				.filter(p -> p.appliesTo(appliesScope)).collect(Collectors.toList());
+	}
 }

+ 6 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/DDClass.java

@@ -9,6 +9,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 
+import org.leumasjaffe.charsheet.model.features.DDProperty;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.charsheet.model.magic.DDSpellList;
 import org.leumasjaffe.charsheet.model.magic.DDSpellList.SpellList;
@@ -36,7 +37,7 @@ public class DDClass {
 	@NonNull SaveQuality ref;
 	@NonNull SaveQuality will;
 	
-	@Getter(AccessLevel.NONE) @NonNull List<List<DDFeature>> features;
+	@Getter(AccessLevel.NONE) @NonNull List<List<DDProperty>> features;
 	
 	@NonNull Set<String> skills;
 	
@@ -65,4 +66,8 @@ public class DDClass {
 		}
 		return skills.contains(skillName);
 	}
+
+	public List<DDProperty> getFeatures(int level) {
+		return features.size() > level ? Collections.unmodifiableList(features.get(level)) : Collections.emptyList();
+	}
 }

+ 0 - 14
src/main/lombok/org/leumasjaffe/charsheet/model/DDFeature.java

@@ -1,14 +0,0 @@
-package org.leumasjaffe.charsheet.model;
-
-import lombok.AccessLevel;
-import lombok.Data;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.experimental.FieldDefaults;
-
-@Data
-@RequiredArgsConstructor
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class DDFeature {
-	@NonNull String name;
-}

+ 22 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeat.java

@@ -0,0 +1,22 @@
+package org.leumasjaffe.charsheet.model.features;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class DDFeat {
+	@NonNull String name, page, benefit;
+	@NonNull List<DDPrerequisite> prerequisites;
+	@NonNull List<DDProperty> properties;
+	
+	@JsonCreator public static DDFeat fromString(String name) {
+		return DDFeatFactory.loadFeat(name);
+	}
+}

+ 49 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeatFactory.java

@@ -0,0 +1,49 @@
+package org.leumasjaffe.charsheet.model.features;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+
+import lombok.AccessLevel;
+import lombok.SneakyThrows;
+import lombok.Synchronized;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+final class DDFeatFactory {
+	Set<String> resourcesLoaded = new HashSet<>();
+	Map<String, DDFeat> featStore = new HashMap<>();
+	
+	ObjectMapper mapper = new ObjectMapper();
+
+	
+	static {
+		mapper.registerModule(new Jdk8Module());
+		loadIfAbsent("resources/feats/default.json");
+	}
+	
+	public DDFeat loadFeat(final String name) {
+		return featStore.get(name);
+	}
+	
+	@Synchronized
+	@SneakyThrows
+	private void loadIfAbsent(final String rname) {
+		final List<DDFeat> temp = mapper.readValue(
+				new File(rname),
+				new TypeReference<ArrayList<DDFeat>>() {
+				});
+		resourcesLoaded.add(rname);
+		temp.forEach(s -> featStore.put(s.getName(), s));
+	}
+}

+ 19 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeaturePredicate.java

@@ -0,0 +1,19 @@
+package org.leumasjaffe.charsheet.model.features;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+import lombok.NonNull;
+
+@FunctionalInterface
+@JsonTypeInfo(use=Id.MINIMAL_CLASS)
+public interface DDFeaturePredicate {
+	boolean accepts(Object value);
+	
+	@JsonCreator
+	public static DDFeaturePredicate of(@JsonProperty("value") @NonNull String name) {
+		return v -> name.equals(v);
+	}
+}

+ 9 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/DDPrerequisite.java

@@ -0,0 +1,9 @@
+package org.leumasjaffe.charsheet.model.features;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+class DDPrerequisite {
+	String applies;
+	int value;
+}

+ 16 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/DDProperty.java

@@ -0,0 +1,16 @@
+package org.leumasjaffe.charsheet.model.features;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+@JsonTypeInfo(use=Id.MINIMAL_CLASS)
+public interface DDProperty {
+	public enum Group {
+		NONE
+	}
+	boolean appliesTo(Object key);
+	@Deprecated <T> T value();
+	void accumulate(Map<String, Object> props);
+}

+ 31 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Flat.java

@@ -0,0 +1,31 @@
+package org.leumasjaffe.charsheet.model.features.impl;
+
+import java.util.Map;
+
+import org.leumasjaffe.charsheet.model.features.DDFeaturePredicate;
+import org.leumasjaffe.charsheet.model.features.DDProperty;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class Flat implements DDProperty {
+	DDFeaturePredicate applies;
+	Group group;
+	int value;
+	
+	@Override
+	public boolean appliesTo(Object key) {
+		return applies.accepts(key);
+	}
+
+	@Override @SuppressWarnings("unchecked")
+	public Object value() {
+		return this.value;
+	}
+
+	@Override
+	public void accumulate(Map<String, Object> props) {
+		// TODO: use groups
+		props.compute("value", (k, old) -> old == null ? value : value + (Integer) old);
+	}
+}

+ 37 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/PerSpellLevel.java

@@ -0,0 +1,37 @@
+package org.leumasjaffe.charsheet.model.features.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.leumasjaffe.charsheet.model.features.DDFeaturePredicate;
+import org.leumasjaffe.charsheet.model.features.DDProperty;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class PerSpellLevel implements DDProperty {
+	DDFeaturePredicate applies;
+	Group group;
+	String type;
+	int value;
+	
+	@Override
+	public boolean appliesTo(Object key) {
+		return applies.accepts(key);
+	}
+
+	@Override @SuppressWarnings("unchecked")
+	public Object value() {
+		return this.value; // TODO consume spell level information
+	}
+
+	@Override @SuppressWarnings("unchecked")
+	public void accumulate(Map<String, Object> props) {
+		// TODO: use groups
+		// TODO: allow multiple things
+		// TODO: consume spell level information
+		props.putIfAbsent("effect", new HashMap<>());
+		((Map<String, Integer>) props.get("effect")).compute("value", 
+				(k, old) -> old == null ? value : value + (Integer) old);
+	}
+}

+ 15 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Simple.java

@@ -0,0 +1,15 @@
+package org.leumasjaffe.charsheet.model.features.impl;
+
+import java.util.Map;
+
+import org.leumasjaffe.charsheet.model.features.DDProperty;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class Simple implements DDProperty {
+	String name;
+	@Override public boolean appliesTo(Object key) { return false; }
+	@Override public <T> T value() { return null; }
+	@Override public void accumulate(Map<String, Object> props) {}
+}

+ 37 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/features/pred/SpellMatcher.java

@@ -0,0 +1,37 @@
+package org.leumasjaffe.charsheet.model.features.pred;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.leumasjaffe.charsheet.model.features.DDFeaturePredicate;
+import org.leumasjaffe.charsheet.model.magic.DDSpell;
+
+import lombok.AllArgsConstructor;
+import lombok.NonNull;
+import lombok.SneakyThrows;
+
+@AllArgsConstructor
+public class SpellMatcher implements DDFeaturePredicate {
+	@NonNull Map<String, Object> properties;
+
+	@Override @SneakyThrows
+	public boolean accepts(Object value) {
+		if (!(value instanceof DDSpell)) return false;
+		DDSpell spell = (DDSpell) value;
+		if (!accepts("school", spell.getSchool())) { return false; }
+		else if (!accepts("subschool", spell.getSubSchool())) { return false; }
+		else if (!accepts("keywords", spell.getKeywords())) { return false; }
+		return true;
+	}
+
+	
+	private boolean accepts(String key, Collection<String> value) {
+		return !properties.containsKey(key) || value.contains(properties.get(key));
+	}
+
+
+	private boolean accepts(String key, Enum<?> value) {
+		return !properties.containsKey(key) || properties.get(key).equals(value.name());
+	}
+
+}

+ 1 - 6
src/main/lombok/org/leumasjaffe/charsheet/model/magic/DDSpellFactory.java

@@ -20,7 +20,7 @@ import lombok.experimental.UtilityClass;
 
 @UtilityClass
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public final class DDSpellFactory {
+final class DDSpellFactory {
 	Set<String> resourcesLoaded = new HashSet<>();
 	Map<String, DDSpell> spellStore = new HashMap<>();
 	
@@ -32,11 +32,6 @@ public final class DDSpellFactory {
 		loadIfAbsent("resources/spells/default.json");
 	}
 	
-	public DDSpell loadSpellWithResource(final String rname, final String name) {
-		loadIfAbsent(rname);
-		return loadSpell(name);
-	}
-	
 	public DDSpell loadSpell(final String name) {
 		return spellStore.get(name);
 	}

+ 1 - 6
src/main/lombok/org/leumasjaffe/charsheet/model/magic/DDSpellList.java

@@ -30,13 +30,8 @@ public class DDSpellList {
 		@JsonCreator
 		public SpellList(List<String> data) {
 			this.spellList = new ArrayList<>(data.size());
-			int idx = -1;
 			for ( final String name : data ) {
-				if ((idx = name.indexOf(':')) != -1) {
-					spellList.add(DDSpellFactory.loadSpellWithResource(name.substring(0, idx), name.substring(idx))); 
-				} else {
-					spellList.add(DDSpellFactory.loadSpell(name));
-				}
+				spellList.add(DDSpellFactory.loadSpell(name));
 			}
 		}
 	} 

+ 2 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/magic/DDSpellbook.java

@@ -6,10 +6,11 @@ import java.util.List;
 import org.leumasjaffe.observer.Observable;
 
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
 
 import lombok.NonNull;
 
-@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
+@JsonTypeInfo(use = Id.MINIMAL_CLASS)
 public abstract class DDSpellbook extends Observable {
 	@NonNull public abstract Collection<DDSpell> spellsKnownAtLevel( int level );
 	@NonNull public abstract List<DDSpell> spellsPreparedAtLevel( int level );

+ 4 - 2
src/main/lombok/org/leumasjaffe/charsheet/model/magic/dimension/Effect.java

@@ -1,5 +1,7 @@
 package org.leumasjaffe.charsheet.model.magic.dimension;
 
+import java.util.Map;
+
 import org.leumasjaffe.format.Named;
 import org.leumasjaffe.format.StringHelper;
 
@@ -19,8 +21,8 @@ public class Effect {
 	int count, per, beyond;
 	@NonFinal @Setter(AccessLevel.PRIVATE) @Getter(AccessLevel.PRIVATE) int step = 1, upto = Integer.MAX_VALUE;
 	
-	public String getResolved(int level) {
-		final int result = count + per * ((Math.min(level, upto)-beyond) / step);
+	public String getResolved(int level, Map<String, Integer> map) {
+		final int result = map.getOrDefault("value", 0) + count + per * ((Math.min(level, upto)-beyond) / step);
 		return resolved == null ? toString() :
 			StringHelper.format(resolved, new Named("atlevel", result));
 	}

+ 1 - 3
src/main/lombok/org/leumasjaffe/charsheet/model/magic/impl/Prepared.java

@@ -8,8 +8,6 @@ import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
 import lombok.NonNull;
 
 public abstract class Prepared extends DDSpellbook {
-	@Override
-	public boolean preparesSpells() { return true; }
-
+	@Override public boolean preparesSpells() { return true; }
 	@NonNull public abstract List<DDSpell> getSpellsPreparedPreviouslyForLevel(int level);	
 }

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

@@ -83,7 +83,7 @@ public class PrepareSpellsDialog extends JPanel {
 		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, score);
+			SelectPreparedSpellsPanel lvl = new SelectPreparedSpellsPanel(chara, i, dclass, score);
 			panels.add(lvl);
 			lvl.addPropertyChangeListener(SelectPreparedSpellsPanel.READY, e -> {
 				if ((Boolean) e.getNewValue()) ++ready[0];

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

@@ -10,6 +10,7 @@ import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
 
 import org.leumasjaffe.charsheet.model.Ability;
+import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.charsheet.model.magic.impl.Prepared;
@@ -80,7 +81,7 @@ class SelectPreparedSpellsPanel extends JPanel {
 	
 	SelectSpellModel modelPrepared, modelKnown;
 
-	public SelectPreparedSpellsPanel(int level, DDCharacterClass dclass, Ability.Scores score) {
+	public SelectPreparedSpellsPanel(DDCharacter chara, int level, DDCharacterClass dclass, Ability.Scores score) {
 		putClientProperty(READY, true);
 		final Prepared spellBook = (Prepared) dclass.getSpellBook().get();
 		this.prepared = new ArrayList<>(spellBook.getSpellsPreparedPreviouslyForLevel(level));
@@ -154,7 +155,7 @@ class SelectPreparedSpellsPanel extends JPanel {
 		mntmInfo.addActionListener( e -> {
 			DDSpell spell = known.get(tableKnown.getSelectedRow());
 			JFrame frame = new JFrame(spell.getName() +  " (" + dclass.getName() + " " + level + ")");
-			frame.add(new SpellInfoPanel(dclass, spell));
+			frame.add(new SpellInfoPanel(chara, dclass, spell));
 			frame.pack();
 			frame.setVisible(true);
 		});

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

@@ -4,12 +4,16 @@ import javax.swing.JPanel;
 import java.awt.GridBagLayout;
 import java.awt.GridBagConstraints;
 import java.awt.Insets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 
 import javax.swing.JTextArea;
 
+import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.charsheet.model.magic.DDSpell.Component;
@@ -32,8 +36,10 @@ class SpellInfoPanel extends JPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 
-	public SpellInfoPanel(DDCharacterClass dclass, final DDSpell spell) {
+	public SpellInfoPanel(DDCharacter chara, DDCharacterClass dclass, final DDSpell spell) {
 		final IntValue classLevel = dclass.getLevel();
+		Map<String, Object> props = new HashMap<>();
+		chara.getFeatureBonuses(spell).forEach(p -> p.accumulate(props));
 		
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0};
@@ -214,7 +220,7 @@ class SpellInfoPanel extends JPanel {
 		gbc_lblTarget.gridy = 4;
 		panel.add(lblTarget, gbc_lblTarget);
 		
-		JTextField target = new JTextField(asString(spell.getTarget()));
+		JTextField target = new JTextField(asString(spell.getTarget().getResolved(classLevel.value())));
 		target.setEditable(false);
 		target.setColumns(10);
 		GridBagConstraints gbc_target = new GridBagConstraints();
@@ -233,7 +239,8 @@ class SpellInfoPanel extends JPanel {
 		gbc_lblEffect.gridy = 4;
 		panel.add(lblEffect, gbc_lblEffect);
 		
-		JTextField effect = new JTextField(asString(spell.getEffect(), e -> e.getResolved(classLevel.value())));
+		JTextField effect = new JTextField(asString(spell.getEffect(), 
+				e -> e.getResolved(classLevel.value(), getSpellBonus("effect", props))));
 		effect.setToolTipText(asString(spell.getEffect()));
 		effect.setEditable(false);
 		effect.setColumns(10);
@@ -337,6 +344,11 @@ class SpellInfoPanel extends JPanel {
 		description.setLineWrap(true);
 	}
 
+	@SuppressWarnings("unchecked")
+	private Map<String, Integer> getSpellBonus(String key, Map<String, Object> props) {
+		return (Map<String, Integer>) props.getOrDefault(key, Collections.emptyMap());
+	}
+
 	private <T> String asString(Optional<T> obj, Function<? super T, String> ts) {
 		return (!obj.isPresent()) ? "" : ts.apply(obj.get());
 	}

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

@@ -6,12 +6,15 @@ import java.awt.Component;
 import javax.swing.Box;
 import java.awt.GridBagConstraints;
 import java.awt.Insets;
-import java.util.List;
+import java.util.Collection;
+import java.util.function.BiFunction;
 
 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.magic.DDSpell;
+import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
 import org.leumasjaffe.observer.IndirectObservableListener;
 import org.leumasjaffe.observer.ObserverDispatch;
 
@@ -27,8 +30,9 @@ class SpellLevelPanel extends JPanel {
 	
 	IndirectObservableListener<JPanel, DDCharacterClass> listener;
 
-	protected SpellLevelPanel(JPanel header, DDCharacterClass dclass, int level) {		
-		final List<DDSpell> spells = dclass.getSpellBook().get().spellsPreparedAtLevel(level);
+	protected SpellLevelPanel(JPanel header, DDCharacter chara, DDCharacterClass dclass, int level, 
+			BiFunction<DDSpellbook, Integer, Collection<DDSpell>> getSpells) {		
+		final Collection<DDSpell> spells = getSpells.apply(dclass.getSpellBook().get(), level);
 
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0, 0};
@@ -62,14 +66,14 @@ class SpellLevelPanel extends JPanel {
 		
 		listener = new IndirectObservableListener<>(panel, (c, v) -> { 
 			c.removeAll();
-			spells.forEach(spell -> c.add(new SpellLine(v, spell, isCastableFromHere())));
+			spells.forEach(spell -> c.add(new SpellLine(chara, v, spell, isCastableFromHere())));
 			c.repaint();
 		});
 		listener.setObserved(dclass, dclass.getSpellBook().get());
 	}
 	
-	public SpellLevelPanel(DDCharacterClass dclass, int level, Ability.Scores ability) {
-		this(new SpellsKnownHeader(level, dclass.getSpellBook().get(), ability), dclass, level);
+	public SpellLevelPanel(DDCharacter chara, DDCharacterClass dclass, int level, Ability.Scores ability) {
+		this(new SpellsKnownHeader(level, dclass.getSpellBook().get(), ability), chara, dclass, level, DDSpellbook::spellsKnownAtLevel);
 	}
 	
 	public boolean isCastableFromHere() { return false; }

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

@@ -1,7 +1,9 @@
 package org.leumasjaffe.charsheet.view.magic;
 
 import org.leumasjaffe.charsheet.model.Ability;
+import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
@@ -14,8 +16,8 @@ class SpellLevelPerDayPanel extends SpellLevelPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 	
-	public SpellLevelPerDayPanel(DDCharacterClass dclass, int level, Ability.Scores ability) {
-		super(new SpellsPerDayHeader(level, dclass.getSpellBook().get(), ability), dclass, level);
+	public SpellLevelPerDayPanel(DDCharacter chara, DDCharacterClass dclass, int level, Ability.Scores ability) {
+		super(new SpellsPerDayHeader(level, dclass.getSpellBook().get(), ability), chara, dclass, level, DDSpellbook::spellsPreparedAtLevel);
 	}
 	
 	public boolean isCastableFromHere() { return true; }

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

@@ -2,6 +2,7 @@ package org.leumasjaffe.charsheet.view.magic;
 
 import javax.swing.JPanel;
 
+import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.event.PopClickListener;
@@ -20,7 +21,7 @@ class SpellLine extends JPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 
-	public SpellLine(final DDCharacterClass dclass, final DDSpell spell, boolean isPrepared) {
+	public SpellLine(DDCharacter chara, final DDCharacterClass dclass, final DDSpell spell, boolean isPrepared) {
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 		gridBagLayout.rowHeights = new int[]{0, 0};
@@ -101,7 +102,7 @@ class SpellLine extends JPanel {
 		gbc_lblAction_1.gridy = 0;
 		add(lblRange, gbc_lblAction_1);
 		
-		addMouseListener(new PopClickListener(new SpellMenu(dclass, spell, isPrepared)));
+		addMouseListener(new PopClickListener(new SpellMenu(chara, dclass, spell, isPrepared)));
 	}
 
 }

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

@@ -2,6 +2,7 @@ package org.leumasjaffe.charsheet.view.magic;
 
 import javax.swing.JPopupMenu;
 
+import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
@@ -18,13 +19,13 @@ class SpellMenu extends JPopupMenu {
 	 */
 	private static final long serialVersionUID = 1L;
 
-	public SpellMenu(final DDCharacterClass dclass, final DDSpell spell, boolean isPrepared) {
+	public SpellMenu(DDCharacter chara, final DDCharacterClass dclass, final DDSpell spell, boolean isPrepared) {
 		final int spellLevel = spell.getClassLevel(dclass.getName());
 		
 		JMenuItem mntmInfo = new JMenuItem("Info");
 		mntmInfo.addActionListener( e -> {
 			JFrame frame = new JFrame(spell.getName() +  " (" + dclass.getName() + " " + spellLevel + ")");
-			frame.add(new SpellInfoPanel(dclass, spell));
+			frame.add(new SpellInfoPanel(chara, dclass, spell));
 			frame.pack();
 			frame.setVisible(true);
 		});

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

@@ -13,7 +13,7 @@ import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.util.AbilityHelper;
-import org.leumasjaffe.function.TriFunction;
+import org.leumasjaffe.function.QuadFunction;
 import org.leumasjaffe.observer.IndirectObservableListener;
 import org.leumasjaffe.observer.ObserverDispatch;
 
@@ -60,10 +60,10 @@ public class SpellPanel extends JPanel {
 		knownPane.setViewportView(known);
 				
 		listenerPerDay = new IndirectObservableListener<>(prepared, 
-				new AppendSpellLevelOperation(ability, SpellLevelPerDayPanel::new));
+				new AppendSpellLevelOperation(chara, ability, SpellLevelPerDayPanel::new));
 		listenerPerDay.setObserved(dclass, ability, dclass.getLevel(), dclass.getSpellBook().get());
 		listenerKnown = new IndirectObservableListener<>(known, 
-				new AppendSpellLevelOperation(ability, SpellLevelPanel::new));
+				new AppendSpellLevelOperation(chara, ability, SpellLevelPanel::new));
 		listenerKnown.setObserved(dclass, ability, dclass.getLevel(), dclass.getSpellBook().get());
 	}
 
@@ -71,14 +71,15 @@ public class SpellPanel extends JPanel {
 	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 	private static final class AppendSpellLevelOperation implements BiConsumer<JPanel, DDCharacterClass> {
 		@NonFinal int previousHighestSpellLevel = 0;
+		DDCharacter chara;
 		Ability.Scores ability;
-		TriFunction<DDCharacterClass, Integer, Ability.Scores, JPanel> function;
+		QuadFunction<DDCharacter, DDCharacterClass, Integer, Ability.Scores, JPanel> function;
 
 		@Override
 		public void accept(final JPanel root, final DDCharacterClass dclass) {		
 			for (int i = previousHighestSpellLevel; i < dclass.getHighestSpellLevel(); ++i) {
 				if (dclass.getSpellBook().get().numSpellsKnownAtLevel(i) == 0) break;
-				root.add(function.apply(dclass, i, ability));
+				root.add(function.apply(chara, dclass, i, ability));
 			}
 			previousHighestSpellLevel = dclass.getHighestSpellLevel();
 		}

+ 17 - 2
src/main/lombok/org/leumasjaffe/charsheet/view/summary/InitiativeLine.java

@@ -8,6 +8,7 @@ import java.awt.GridBagConstraints;
 import java.awt.Color;
 import javax.swing.border.LineBorder;
 
+import org.leumasjaffe.charsheet.config.Constants;
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.observer.helper.AbilModStringify;
@@ -33,6 +34,7 @@ public class InitiativeLine extends JPanel {
 	private static final long serialVersionUID = 1L;
 	IndirectObservableListener<JTextField, DDCharacter> ttlObserver;
 	ObservableListener<JTextField, Ability.Scores> dexObserver;
+	ObservableListener<JTextField, DDCharacter> tempObserver;
 
 	public InitiativeLine() {
 		setOpaque(false);
@@ -127,16 +129,28 @@ public class InitiativeLine extends JPanel {
 		ttlObserver = new IndirectObservableListener<>(total, 
 				(c, v) -> {
 					final int adex = v.getAbilities().getDex().modifier();
-					c.setText(StringHelper.toString( adex ));
+					final int miscValue = getInitiativeBonuses(v);
+					c.setText(StringHelper.toString( adex + miscValue ));
 				});
 		dexObserver = new ObservableListener<>(dex, 
 				new AbilModStringify());
+		tempObserver = new ObservableListener<>(misc, (c, v) -> {
+			final int miscValue = getInitiativeBonuses(v);
+			c.setText(StringHelper.toString(miscValue));
+		});
+	}
+
+	private int getInitiativeBonuses(DDCharacter v) {
+		// FIXME
+		return v.getFeatureBonuses(Constants.INITIATIVE).stream()
+				.mapToInt(f -> (Integer) f.value()).sum();
 	}
 
 	public void setModel(DDCharacter model) {
 		final Ability.Scores dex = model.getAbilities().getDex();
-		ttlObserver.setObserved(model, dex);
+		ttlObserver.setObserved(model, model, dex);
 		dexObserver.setObserved(dex);
+		tempObserver.setObserved(model);
 	}
 
 	@Override
@@ -144,5 +158,6 @@ public class InitiativeLine extends JPanel {
 		super.removeNotify();
 		ObserverDispatch.unsubscribeAll(ttlObserver);
 		ObserverDispatch.unsubscribeAll(dexObserver);
+		ObserverDispatch.unsubscribeAll(tempObserver);
 	}
 }

+ 6 - 0
src/main/lombok/org/leumasjaffe/function/QuadFunction.java

@@ -0,0 +1,6 @@
+package org.leumasjaffe.function;
+
+@FunctionalInterface
+public interface QuadFunction<A, B, C, D, R> {
+	R apply(A arg0, B arg1, C arg2, D arg3);
+}