ソースを参照

Merge branch 'feat_spell_matcher' into feats

Sam Jaffe 8 年 前
コミット
3c2ebb67e2
21 ファイル変更143 行追加40 行削除
  1. 12 2
      resources/feats/default.json
  2. 1 1
      resources/spells/default.json
  3. 1 1
      src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacter.java
  4. 1 1
      src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacterClass.java
  5. 6 2
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeaturePredicate.java
  6. 1 1
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDPrerequisite.java
  7. 5 2
      src/main/lombok/org/leumasjaffe/charsheet/model/features/DDProperty.java
  8. 10 3
      src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Flat.java
  9. 16 3
      src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/PerSpellLevel.java
  10. 4 1
      src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Simple.java
  11. 37 0
      src/main/lombok/org/leumasjaffe/charsheet/model/features/pred/SpellMatcher.java
  12. 4 2
      src/main/lombok/org/leumasjaffe/charsheet/model/magic/dimension/Effect.java
  13. 1 1
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/PrepareSpellsDialog.java
  14. 3 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SelectPreparedSpellsPanel.java
  15. 15 3
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellInfoPanel.java
  16. 5 4
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPanel.java
  17. 3 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPerDayPanel.java
  18. 3 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLine.java
  19. 3 2
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellMenu.java
  20. 6 5
      src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellPanel.java
  21. 6 0
      src/main/lombok/org/leumasjaffe/function/QuadFunction.java

+ 12 - 2
resources/feats/default.json

@@ -6,7 +6,10 @@
     "prerequisites": [],
     "properties": [
       {
-        "applies": "Character::Initiative",
+        "applies": { 
+          "@c": ".DDFeaturePredicate", 
+          "value": "Character::Initiative"
+        },
         "@c": ".impl.Flat",
         "group": "NONE",
         "value": 4
@@ -25,9 +28,16 @@
     ],
     "properties": [
       {
-        "applies": "Spell::Conjuration::Healing",
+        "applies": {
+          "@c": ".pred.SpellMatcher",
+          "properties": {
+            "school": "Conjuration",
+            "subschool": "Healing"
+          }
+        },
         "@c": ".impl.PerSpellLevel",
         "group": "NONE",
+        "type": "effect",
         "value": 2
       }
     ]

+ 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 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacter.java

@@ -98,7 +98,7 @@ public class DDCharacter extends Observable {
 		return classes.stream().map(DDCharacterClass::getLevel).mapToInt(IntValue::value).sum();
 	}
 	
-	public List<DDProperty> getFeatureBonuses(String appliesScope) {
+	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()))

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

@@ -98,7 +98,7 @@ public class DDCharacterClass extends Observable implements Comparable<DDCharact
 		return getName().compareTo(o.getName());
 	}
 
-	public List<DDProperty> getFeatureBonuses(String appliesScope) {
+	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))

+ 6 - 2
src/main/lombok/org/leumasjaffe/charsheet/model/features/DDFeaturePredicate.java

@@ -1,15 +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(String value);
+	boolean accepts(Object value);
 	
 	@JsonCreator
-	public static DDFeaturePredicate of(@NonNull String name) {
+	public static DDFeaturePredicate of(@JsonProperty("value") @NonNull String name) {
 		return v -> name.equals(v);
 	}
 }

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

@@ -4,6 +4,6 @@ import lombok.AllArgsConstructor;
 
 @AllArgsConstructor
 class DDPrerequisite {
-	DDFeaturePredicate applies;
+	String applies;
 	int value;
 }

+ 5 - 2
src/main/lombok/org/leumasjaffe/charsheet/model/features/DDProperty.java

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

+ 10 - 3
src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Flat.java

@@ -1,5 +1,7 @@
 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;
 
@@ -12,13 +14,18 @@ public class Flat implements DDProperty {
 	int value;
 	
 	@Override
-	public boolean appliesTo(String key) {
+	public boolean appliesTo(Object key) {
 		return applies.accepts(key);
 	}
 
-	@SuppressWarnings("unchecked")
-	@Override
+	@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);
+	}
 }

+ 16 - 3
src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/PerSpellLevel.java

@@ -1,5 +1,8 @@
 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;
 
@@ -9,16 +12,26 @@ import lombok.AllArgsConstructor;
 public class PerSpellLevel implements DDProperty {
 	DDFeaturePredicate applies;
 	Group group;
+	String type;
 	int value;
 	
 	@Override
-	public boolean appliesTo(String key) {
+	public boolean appliesTo(Object key) {
 		return applies.accepts(key);
 	}
 
-	@SuppressWarnings("unchecked")
-	@Override
+	@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);
+	}
 }

+ 4 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/features/impl/Simple.java

@@ -1,5 +1,7 @@
 package org.leumasjaffe.charsheet.model.features.impl;
 
+import java.util.Map;
+
 import org.leumasjaffe.charsheet.model.features.DDProperty;
 
 import lombok.AllArgsConstructor;
@@ -7,6 +9,7 @@ import lombok.AllArgsConstructor;
 @AllArgsConstructor
 public class Simple implements DDProperty {
 	String name;
-	@Override public boolean appliesTo(String key) { return false; }
+	@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());
+	}
+
+}

+ 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 - 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());
 	}

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

@@ -11,6 +11,7 @@ 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;
@@ -29,7 +30,7 @@ class SpellLevelPanel extends JPanel {
 	
 	IndirectObservableListener<JPanel, DDCharacterClass> listener;
 
-	protected SpellLevelPanel(JPanel header, DDCharacterClass dclass, int 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);
 
@@ -65,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, DDSpellbook::spellsKnownAtLevel);
+	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; }

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

@@ -1,6 +1,7 @@
 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;
 
@@ -15,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, DDSpellbook::spellsPreparedAtLevel);
+	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();
 		}

+ 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);
+}