Bladeren bron

Merge branch 'itemstore'

Sam Jaffe 8 jaren geleden
bovenliggende
commit
7d23ee1865

+ 0 - 20
resources/Potato.json

@@ -118,17 +118,7 @@
         "name": "Quarterstaff",
         "count": 1,
         "countEquipped": 1,
-        "value": {"pp": 0, "gp": 0, "sp": 0, "cp": 0},
-        "page": "PH116",
-        "slot": "TWO_HANDS",
-        "weight": 4,
         "weapon": {
-          "damage": "1d6",
-          "secondaryDamage": "1d6",
-          "criticalThreat": 20,
-          "criticalDamage": 2,
-          "range": "Melee",
-          "type": "Bludgeoning",
           "masterwork": true
         }
       },
@@ -136,17 +126,7 @@
         "name": "Full Plate Armor",
         "count": 1,
         "countEquipped": 1,
-        "value": {"pp": 0, "gp": 1500, "sp": 0, "cp": 0},
-        "page": "PH123",
-        "slot": "BODY",
-        "weight": 50,
         "armor": {
-          "acBonus": 8,
-          "type": "Heavy",
-          "maxDex": 1,
-          "checkPenalty": -6,
-          "spellFailure": 35,
-          "speed": 15,
           "masterwork": true,
           "bonus": "+1"
         }

+ 1 - 0
resources/items/item.json

@@ -0,0 +1 @@
+[]

+ 5 - 5
src/main/lombok/org/leumasjaffe/charsheet/controller/EquipItemController.java

@@ -42,7 +42,7 @@ public class EquipItemController {
 
 		private boolean selectToReplaceAllOf(final EquipmentSlot base,
 				final EquipmentSlot slot1, final EquipmentSlot slot2) {
-			if (inv.get(slot1).getSlot() == base) {
+			if (inv.get(slot1).get().getSlot() == base) {
 				return selectToReplace(slot1);
 			} else if (JOptionPane.showConfirmDialog(null, createDialogTwoSlots(REPLACE_BOTH, slot1, slot2), 
 					QUERY_REPLACE, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
@@ -78,19 +78,19 @@ public class EquipItemController {
 		}
 
 		private void doUnequip(final EquipmentSlot slot) {
-			inv.get(slot).adjustCounEquipped(-1);
+			inv.get(slot).get().adjustCounEquipped(-1);
 			inv.unequip(slot);
 		}		
 
 		private EquipItemDialog createDialogOneSlot(final EquipmentSlot slot) {
-			return new EquipItemDialog(REPLACE_ONE, slot, inv.get(slot));
+			return new EquipItemDialog(REPLACE_ONE, slot, inv.get(slot).get());
 		}
 		
 		private EquipItemDialog createDialogTwoSlots(final String message, 
 				final EquipmentSlot slot1, final EquipmentSlot slot2) {
 			return new EquipItemDialog(message,
-					new EquipItemDialog.Tuple(slot1, inv.get(slot1)),
-					new EquipItemDialog.Tuple(slot2, inv.get(slot2)));
+					new EquipItemDialog.Tuple(slot1, inv.get(slot1).get()),
+					new EquipItemDialog.Tuple(slot2, inv.get(slot2).get()));
 		}
 	}
 }

+ 16 - 10
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDArmor.java

@@ -2,27 +2,33 @@ package org.leumasjaffe.charsheet.model.inventory;
 
 import lombok.AccessLevel;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.experimental.Delegate;
 import lombok.experimental.FieldDefaults;
 
-@Data @EqualsAndHashCode(callSuper=true)
+@Data
 @FieldDefaults(level=AccessLevel.PRIVATE)
-public class DDArmor extends DDEnchantableItem {
+public class DDArmor  {
 	public static enum Type { Light, Medium, Heavy }
-	int acBonus, maxDex, speed, spellFailure, checkPenalty;
-	Type type;
+	@Data @FieldDefaults(level=AccessLevel.PRIVATE)
+	public static class Prototype {
+		int acBonus, maxDex, speed, spellFailure, checkPenalty;
+		Type type;
+	}
+	
+	@Delegate Prototype name = new Prototype();
+	@Delegate DDEnchantedItem enchant = new DDEnchantedItem();
 	
 	public int getActualAcBonus() {
-		return acBonus + bonus.value;
+		return getAcBonus() + getBonus().value;
 	}
 	
 	public int getActualCheckPenalty() {
-		return isMasterwork() ? checkPenalty + 1 : checkPenalty;
+		return isMasterwork() ? getCheckPenalty() + 1 : getCheckPenalty();
 	}
 	
 	public Money getActualValue() {
-		int gp = isMasterwork ? 150 : 0;
-		gp += 1000 * (bonus.value + getEnchantBonus());
-		return Money.fromCopperToGold(100 * gp + getAdHocPrice());
+		int gp = isMasterwork() ? 150 : 0;
+		gp += 1000 * (getBonus().value + enchant.getEnchantBonus());
+		return Money.fromCopperToGold(100 * gp + enchant.getAdHocPrice());
 	}
 }

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

@@ -6,7 +6,7 @@ import java.util.List;
 import lombok.Data;
 
 @Data
-class DDEnchantableItem {
+class DDEnchantedItem {
 	boolean isMasterwork = false;
 	Enhancement bonus = Enhancement.NONE;
 	List<Enchantment> enchantments = new ArrayList<>();

+ 3 - 2
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDEquipment.java

@@ -3,6 +3,7 @@ package org.leumasjaffe.charsheet.model.inventory;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.BiConsumer;
 
@@ -98,8 +99,8 @@ public class DDEquipment extends Observable {
 		return Collections.unmodifiableSet(equipment.keySet());
 	}
 	
-	public DDItem get(EquipmentSlot slot) {
-		return equipment.get(slot);
+	public Optional<DDItem> get(EquipmentSlot slot) {
+		return Optional.ofNullable(equipment.get(slot));
 	}
 	
 	public boolean containsKey(EquipmentSlot slot) {

+ 41 - 39
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDItem.java

@@ -1,56 +1,57 @@
 package org.leumasjaffe.charsheet.model.inventory;
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Optional;
 
 import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.model.observable.StringValue;
 
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
 import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
 import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.Getter;
+import lombok.experimental.Delegate;
 import lombok.experimental.FieldDefaults;
 
-@AllArgsConstructor @NoArgsConstructor
-@Data
 @FieldDefaults(level=AccessLevel.PRIVATE)
 public class DDItem {
-	String name = "";
-	IntValue count = new IntValue(1);
-	IntValue countEquipped = new IntValue(0);
-	float weight = 0.f;
-	Money value = new Money(0, 0, 0, 0);
-	StringValue page = new StringValue();
-	EquipmentSlot slot = EquipmentSlot.NONE;
-	DDWeapon weapon = null;
-	DDArmor armor = null;
-	Map<String, Object> properties = new HashMap<>();
-	
-	public boolean isWeapon() { return weapon != null; }
-	public boolean isArmor() { return armor != null; }
-	
-	@SuppressWarnings("unchecked")
-	public <T> T getProperty(final String key) {
-		return (T) properties.get(key);
+	@Data @FieldDefaults(level=AccessLevel.PRIVATE)
+	public static class Prototype {
+		String name = "";
+		float weight = 0.f;
+		Money value = new Money(0, 0, 0, 0);
+		StringValue page = new StringValue();
+		EquipmentSlot slot = EquipmentSlot.NONE;
+		Optional<DDWeapon> weapon = Optional.empty();
+		Optional<DDArmor> armor = Optional.empty();
 	}
 	
-	@JsonAnySetter 
-	public void setProperty(final String key, final Object prop) {
-		if ( properties == null ) { properties = new HashMap<>(); }
-		properties.put(key, prop);
+	@Delegate Prototype name;
+	@Getter IntValue count = new IntValue(1);
+	@Getter IntValue countEquipped = new IntValue(0);
+
+	@JsonCreator
+	public DDItem(@JsonProperty("name") String name, 
+			@JsonProperty("count") int count, 
+			@JsonProperty("countEquipped") int countEquipped, 
+			@JsonProperty("weapon") Optional<DDEnchantedItem> weapon, 
+			@JsonProperty("armor") Optional<DDEnchantedItem> armor) {
+		this.name = DDItemFactory.loadItem(name);
+		this.count.value(count);
+		this.countEquipped.value(countEquipped);
+		weapon.ifPresent(e -> getWeapon().get().setEnchant(e));
+		armor.ifPresent(e -> getArmor().get().setEnchant(e));
 	}
-	
+		
 	public String getFullName() {
-		return (isWeapon() ? weapon.getNameModifier() : isArmor() ? armor.getNameModifier() : "") + getName(); 
+		return getWeapon().map(DDWeapon::getNameModifier).orElse(
+				getArmor().map(DDArmor::getNameModifier).orElse("")) + getName();
 	}
 	
 	public Money getActualValue() {
-		return isWeapon() ? value.sum(weapon.getActualValue()) : isArmor() ? value.sum(armor.getActualValue()) : value;
+		return getValue().sum(getWeapon().map(DDWeapon::getActualValue).orElse(
+				getArmor().map(DDArmor::getActualValue).orElse(Money.fromCopper(0))));
 	}
 	
 	public void adjustCount(int amt) {
@@ -61,13 +62,14 @@ public class DDItem {
 		this.countEquipped.value(this.countEquipped.value() + amt);
 	}
 	
-	@JsonAnyGetter 
-	private Map<String, Object> getProperties() { 
-		if ( properties == null ) { properties = new HashMap<>(); }
-		return Collections.unmodifiableMap(properties);
-	}
-	
 	public int getUnequippedCount() {
 		return count.value() - countEquipped.value();
 	}
+
+	@JsonProperty("name")
+	private String json_getName() { return getName(); }
+	@JsonProperty("armor")
+	private Optional<DDEnchantedItem> json_getArmor() { return getWeapon().map(DDWeapon::getEnchant); }
+	@JsonProperty("weapon")
+	private Optional<DDEnchantedItem> json_getWeapon() { return getArmor().map(DDArmor::getEnchant); }
 }

+ 51 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDItemFactory.java

@@ -0,0 +1,51 @@
+package org.leumasjaffe.charsheet.model.inventory;
+
+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 DDItemFactory {
+	Set<String> resourcesLoaded = new HashSet<>();
+	Map<String, DDItem.Prototype> itemStore = new HashMap<>();
+	
+	ObjectMapper mapper = new ObjectMapper();
+
+	
+	static {
+		mapper.registerModule(new Jdk8Module());
+		loadIfAbsent("resources/items/item.json");
+		loadIfAbsent("resources/items/armor.json");
+		loadIfAbsent("resources/items/weapon.json");
+	}
+	
+	public DDItem.Prototype loadItem(final String name) {
+		return itemStore.get(name);
+	}
+	
+	@Synchronized
+	@SneakyThrows
+	private void loadIfAbsent(final String rname) {
+		final List<DDItem.Prototype> temp = mapper.readValue(
+				new File(rname),
+				new TypeReference<ArrayList<DDItem.Prototype>>() {
+				});
+		resourcesLoaded.add(rname);
+		temp.forEach(s -> itemStore.put(s.getName(), s));
+	}
+}

+ 23 - 19
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDWeapon.java

@@ -2,44 +2,48 @@ package org.leumasjaffe.charsheet.model.inventory;
 
 import lombok.AccessLevel;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.experimental.Delegate;
 import lombok.experimental.FieldDefaults;
 
-@Data @EqualsAndHashCode(callSuper=true)
-@FieldDefaults(level=AccessLevel.PRIVATE)
-public class DDWeapon extends DDEnchantableItem {
+@Data @FieldDefaults(level=AccessLevel.PRIVATE)
+public class DDWeapon {
 	public static enum Type { Piercing, Bludgeoning, Slashing }
-
-	String damage;
-	String secondaryDamage;
-	int criticalThreat;
-	int criticalDamage;
-	int secondaryCriticalDamage;
+	@Data @FieldDefaults(level=AccessLevel.PRIVATE)
+	public static class Prototype {
+		String damage;
+		String secondaryDamage;
+		int criticalThreat;
+		int criticalDamage;
+		int secondaryCriticalDamage;
+		
+		Range range;
+		Type type;
+	}
 	
-	Range range;
-	Type type;
+	@Delegate Prototype name = new Prototype();
+	@Delegate DDEnchantedItem enchant = new DDEnchantedItem();
 	
 	public Money getActualValue() {
-		int gp = isMasterwork ? 300 : 0;
+		int gp = isMasterwork() ? 300 : 0;
 		if (hasSecondaryAttack()) gp *= 2;
-		gp += 2000 * (bonus.value + getEnchantBonus());
-		return Money.fromCopperToGold(100 * gp + getAdHocPrice());
+		gp += 2000 * (getBonus().value + enchant.getEnchantBonus());
+		return Money.fromCopperToGold(100 * gp + enchant.getAdHocPrice());
 	}
 
 	public boolean hasCriticalThreat() {
-		return criticalThreat != 20 && criticalThreat != 0;
+		return getCriticalThreat() != 20 && getCriticalThreat() != 0;
 	}
 
 	public boolean hasSecondaryAttack() {
-		return secondaryDamage != null && !secondaryDamage.isEmpty();
+		return getSecondaryDamage() != null && !getSecondaryDamage().isEmpty();
 	}
 	
 	public int getDamageBonus() {
-		return bonus.getValue();
+		return getBonus().getValue();
 	}
 
 	public int getAttackBonus() {
-		return bonus.getValue() == 0 && isMasterwork ? 1 : bonus.getValue();
+		return getBonus().getValue() == 0 && isMasterwork() ? 1 : getBonus().getValue();
 	}
 
 }

+ 5 - 2
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/Money.java

@@ -1,14 +1,17 @@
 package org.leumasjaffe.charsheet.model.inventory;
 
+import org.leumasjaffe.observer.Observable;
+
 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.experimental.FieldDefaults;
 
 @AllArgsConstructor
-@Data
+@Data @EqualsAndHashCode(callSuper=false)
 @FieldDefaults(level=AccessLevel.PRIVATE)
-public class Money implements Comparable<Money> {
+public class Money extends Observable implements Comparable<Money> {
 	int pp, gp, sp, cp;
 	
 	public Money assign(final Money other) {

+ 2 - 2
src/main/lombok/org/leumasjaffe/charsheet/view/inventory/ArmorPanel.java

@@ -26,7 +26,7 @@ public class ArmorPanel extends JPanel {
 	private static final long serialVersionUID = 1L;
 
 	public ArmorPanel(DDItem item) {
-		final DDArmor armor = item.getArmor();
+		final DDArmor armor = item.getArmor().get();
 		
 		setPreferredSize(new Dimension(280, 70));
 		GridBagLayout gridBagLayout = new GridBagLayout();
@@ -131,7 +131,7 @@ public class ArmorPanel extends JPanel {
 		panel.add(armorTypeField, gbc_armorTypeField);
 		armorTypeField.setColumns(10);
 		
-		JTextField armorBonusField = new JTextField(StringHelper.toSignedString(item.getArmor().getActualAcBonus()));
+		JTextField armorBonusField = new JTextField(StringHelper.toSignedString(armor.getActualAcBonus()));
 		armorBonusField.setHorizontalAlignment(SwingConstants.CENTER);
 		GridBagConstraints gbc_armorBonusField = new GridBagConstraints();
 		gbc_armorBonusField.insets = new Insets(0, 0, 0, 0);

+ 19 - 20
src/main/lombok/org/leumasjaffe/charsheet/view/inventory/EquipmentPanel.java

@@ -86,56 +86,55 @@ public class EquipmentPanel extends JPanel {
 	private void updateModel(final DDEquipment equip) {
 		final Set<EquipmentSlot> manual = EnumSet.noneOf(EquipmentSlot.class);
 		equipment.removeAll();	
-		final DDItem armor = equip.get(BODY);
-		if (armor != null && armor.isArmor()) {
+		final Optional<DDItem> armor = equip.get(BODY);
+		armor.flatMap(DDItem::getArmor).ifPresent(a -> {
 			manual.add(BODY);
-			createWithRightClickMenu(ArmorPanel::new, equip, BODY, BODY);
-		}
+			createWithRightClickMenu(ArmorPanel::new, equip, armor.get(), BODY);
+		});
 		
-		final DDItem main = equip.get(MAIN_HAND);
-		final DDItem off = equip.get(OFF_HAND);
+		final Optional<DDItem> main = equip.get(MAIN_HAND);
+		final Optional<DDItem> off = equip.get(OFF_HAND);
 	
 		final Optional<Function<DDItem, JPanel>> makeMain = getEquipmentRightClickPanelFactory(main);
 		final Optional<Function<DDItem, JPanel>> makeOff = getEquipmentRightClickPanelFactory(off);
 		
-		if (main == off && !couldDualWieldThis(main)) {
+		if (main.equals(off) && !couldDualWieldThis(main)) {
 			makeMain.ifPresent(f -> {
 				manual.add(MAIN_HAND);
 				manual.add(OFF_HAND);
-				createWithRightClickMenu(f, equip, TWO_HANDS, MAIN_HAND);
+				createWithRightClickMenu(f, equip, main.get(), TWO_HANDS);
 			});
 		} else {
 			makeMain.ifPresent(f -> {
 				manual.add(MAIN_HAND);
-				createWithRightClickMenu(f, equip, MAIN_HAND, MAIN_HAND);
+				createWithRightClickMenu(f, equip, main.get(), MAIN_HAND);
 			});
 			makeOff.ifPresent(f -> {
 				manual.add(OFF_HAND);
-				createWithRightClickMenu(f, equip, OFF_HAND, OFF_HAND);
+				createWithRightClickMenu(f, equip, off.get(), OFF_HAND);
 			});
 		}
 		
-		equip.keySet().stream().filter( slot -> equip.get(slot) != null && !manual.contains(slot) )
+		equip.keySet().stream().filter( slot -> !manual.contains(slot) )
 		.forEach( slot -> {
-			createWithRightClickMenu(null, equip, slot, slot);
+			createWithRightClickMenu(null, equip, equip.get(slot).get(), slot);
 		});
 		repaint();
 	}
 
-	private Optional<Function<DDItem, JPanel>> getEquipmentRightClickPanelFactory(final DDItem item) {
-		if (item == null) { return Optional.empty(); }
-		else if (item.isWeapon()) { return Optional.of(WeaponPanel::new); }
-		else if (item.isArmor()) { return Optional.of(ShieldPanel::new); }
+	private Optional<Function<DDItem, JPanel>> getEquipmentRightClickPanelFactory(final Optional<DDItem> item) {
+		if (!item.isPresent()) { return Optional.empty(); }
+		else if (item.get().getWeapon().isPresent()) { return Optional.of(WeaponPanel::new); }
+		else if (item.get().getArmor().isPresent()) { return Optional.of(ShieldPanel::new); }
 		else { return Optional.empty(); }
 	}
 
-	private boolean couldDualWieldThis(final DDItem item) {
-		return item != null && item.getSlot() == EquipmentSlot.ONE_HAND && item.getCount().value() > 1;
+	private boolean couldDualWieldThis(final Optional<DDItem> item) {
+		return item.map(i -> i.getSlot() == EquipmentSlot.ONE_HAND && i.getCount().value() > 1).orElse(false);
 	}
 	
 	private void createWithRightClickMenu(final Function<DDItem, JPanel> make,
-			final DDEquipment equip, final EquipmentSlot slot, EquipmentSlot get) {
-		final DDItem item = equip.get(get);
+			final DDEquipment equip, final DDItem item, final EquipmentSlot slot) {
 		final JPanel panel = make.apply(item);
 		equipment.add(panel);
 		panel.addMouseListener(new PopClickListener(

+ 158 - 5
src/main/lombok/org/leumasjaffe/charsheet/view/inventory/InventoryPanel.java

@@ -10,6 +10,7 @@ import javax.swing.border.MatteBorder;
 import org.jdesktop.swingx.VerticalLayout;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.inventory.DDInventory;
+import org.leumasjaffe.charsheet.model.inventory.Money;
 import org.leumasjaffe.observer.ObservableListener;
 import org.leumasjaffe.observer.ObserverDispatch;
 
@@ -20,6 +21,10 @@ import java.awt.Color;
 import java.awt.Font;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Dimension;
+
+import javax.swing.JTextField;
 
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class InventoryPanel extends JPanel {
@@ -29,21 +34,160 @@ public class InventoryPanel extends JPanel {
 	private static final long serialVersionUID = 1L;
 	JComponent inventory;
 	ObservableListener<InventoryPanel, DDInventory> inventoryObserver;
+	ObservableListener<JTextField, Money> moneyObserver;
+	private JTextField txtPp;
+	private JTextField txtGp;
+	private JTextField txtSp;
+	private JTextField txtCp;
 	
 	public InventoryPanel() {
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0};
-		gridBagLayout.rowHeights = new int[]{0};
-		gridBagLayout.columnWeights = new double[]{Double.MIN_VALUE};
-		gridBagLayout.rowWeights = new double[]{Double.MIN_VALUE};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{1.0};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
 		setLayout(gridBagLayout);
 		
+		JPanel panel = new JPanel();
+		panel.setPreferredSize(new Dimension(280, 30));
+		GridBagConstraints gbc_panel = new GridBagConstraints();
+		gbc_panel.insets = new Insets(0, 0, 5, 0);
+		gbc_panel.fill = GridBagConstraints.BOTH;
+		gbc_panel.gridx = 0;
+		gbc_panel.gridy = 0;
+		add(panel, gbc_panel);
+		GridBagLayout gbl_panel = new GridBagLayout();
+		gbl_panel.columnWidths = new int[]{100, 0, 0};
+		gbl_panel.rowHeights = new int[]{0, 0};
+		gbl_panel.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
+		gbl_panel.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		panel.setLayout(gbl_panel);
+		
+		JLabel lblMoney = new JLabel("Money");
+		lblMoney.setOpaque(true);
+		lblMoney.setHorizontalAlignment(SwingConstants.CENTER);
+		lblMoney.setForeground(Color.WHITE);
+		lblMoney.setFont(new Font("Tahoma", Font.BOLD, 20));
+		lblMoney.setBorder(new MatteBorder(0, 0, 1, 0, (Color) Color.WHITE));
+		lblMoney.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblMoney = new GridBagConstraints();
+		gbc_lblMoney.fill = GridBagConstraints.BOTH;
+		gbc_lblMoney.insets = new Insets(0, 0, 0, 1);
+		gbc_lblMoney.gridx = 0;
+		gbc_lblMoney.gridy = 0;
+		panel.add(lblMoney, gbc_lblMoney);
+		
+		JPanel panel_1 = new JPanel();
+		GridBagConstraints gbc_panel_1 = new GridBagConstraints();
+		gbc_panel_1.fill = GridBagConstraints.BOTH;
+		gbc_panel_1.insets = new Insets(0, 0, 1, 0);
+		gbc_panel_1.gridx = 1;
+		gbc_panel_1.gridy = 0;
+		panel.add(panel_1, gbc_panel_1);
+		GridBagLayout gbl_panel_1 = new GridBagLayout();
+		gbl_panel_1.columnWidths = new int[]{0, 0, 0, 0, 0};
+		gbl_panel_1.rowHeights = new int[]{0};
+		gbl_panel_1.columnWeights = new double[]{1.0, 1.0, 1.0, 1.0, Double.MIN_VALUE};
+		gbl_panel_1.rowWeights = new double[]{Double.MIN_VALUE};
+		panel_1.setLayout(gbl_panel_1);
+		
+		JLabel lblPp = new JLabel("pp");
+		GridBagConstraints gbc_lblPp = new GridBagConstraints();
+		gbc_lblPp.fill = GridBagConstraints.HORIZONTAL;
+		gbc_lblPp.insets = new Insets(0, 0, 0, 1);
+		gbc_lblPp.gridx = 0;
+		gbc_lblPp.gridy = 0;
+		panel_1.add(lblPp, gbc_lblPp);
+		lblPp.setOpaque(true);
+		lblPp.setHorizontalAlignment(SwingConstants.CENTER);
+		lblPp.setForeground(Color.WHITE);
+		lblPp.setFont(new Font("Tahoma", Font.BOLD, 8));
+		lblPp.setBackground(Color.BLACK);
+		
+		JLabel lblGp = new JLabel("gp");
+		GridBagConstraints gbc_lblGp = new GridBagConstraints();
+		gbc_lblGp.fill = GridBagConstraints.HORIZONTAL;
+		gbc_lblGp.insets = new Insets(0, 0, 0, 1);
+		gbc_lblGp.gridx = 1;
+		gbc_lblGp.gridy = 0;
+		panel_1.add(lblGp, gbc_lblGp);
+		lblGp.setOpaque(true);
+		lblGp.setHorizontalAlignment(SwingConstants.CENTER);
+		lblGp.setForeground(Color.WHITE);
+		lblGp.setFont(new Font("Tahoma", Font.BOLD, 8));
+		lblGp.setBackground(Color.BLACK);
+		
+		JLabel lblSp = new JLabel("sp");
+		GridBagConstraints gbc_lblSp = new GridBagConstraints();
+		gbc_lblSp.fill = GridBagConstraints.HORIZONTAL;
+		gbc_lblSp.insets = new Insets(0, 0, 0, 1);
+		gbc_lblSp.gridx = 2;
+		gbc_lblSp.gridy = 0;
+		panel_1.add(lblSp, gbc_lblSp);
+		lblSp.setOpaque(true);
+		lblSp.setHorizontalAlignment(SwingConstants.CENTER);
+		lblSp.setForeground(Color.WHITE);
+		lblSp.setFont(new Font("Tahoma", Font.BOLD, 8));
+		lblSp.setBackground(Color.BLACK);
+		
+		JLabel lblCp = new JLabel("cp");
+		GridBagConstraints gbc_lblCp = new GridBagConstraints();
+		gbc_lblCp.insets = new Insets(0, 0, 0, 1);
+		gbc_lblCp.fill = GridBagConstraints.HORIZONTAL;
+		gbc_lblCp.gridx = 3;
+		gbc_lblCp.gridy = 0;
+		panel_1.add(lblCp, gbc_lblCp);
+		lblCp.setOpaque(true);
+		lblCp.setHorizontalAlignment(SwingConstants.CENTER);
+		lblCp.setForeground(Color.WHITE);
+		lblCp.setFont(new Font("Tahoma", Font.BOLD, 8));
+		lblCp.setBackground(Color.BLACK);
+		
+		txtPp = new JTextField();
+		GridBagConstraints gbc_txtPP = new GridBagConstraints();
+		gbc_txtPP.fill = GridBagConstraints.HORIZONTAL;
+		gbc_txtPP.insets = new Insets(0, 0, 0, 0);
+		gbc_txtPP.gridx = 0;
+		gbc_txtPP.gridy = 1;
+		panel_1.add(txtPp, gbc_txtPP);
+		txtPp.setEditable(false);
+		txtPp.setColumns(5);
+		
+		txtGp = new JTextField();
+		GridBagConstraints gbc_txtGP = new GridBagConstraints();
+		gbc_txtGP.fill = GridBagConstraints.HORIZONTAL;
+		gbc_txtGP.insets = new Insets(0, 0, 0, 0);
+		gbc_txtGP.gridx = 1;
+		gbc_txtGP.gridy = 1;
+		panel_1.add(txtGp, gbc_txtGP);
+		txtGp.setEditable(false);
+		txtGp.setColumns(5);
+		
+		txtSp = new JTextField();
+		GridBagConstraints gbc_txtSP = new GridBagConstraints();
+		gbc_txtSP.fill = GridBagConstraints.HORIZONTAL;
+		gbc_txtSP.insets = new Insets(0, 0, 0, 0);
+		gbc_txtSP.gridx = 2;
+		gbc_txtSP.gridy = 1;
+		panel_1.add(txtSp, gbc_txtSP);
+		txtSp.setEditable(false);
+		txtSp.setColumns(5);
+		
+		txtCp = new JTextField();
+		GridBagConstraints gbc_txtCP = new GridBagConstraints();
+		gbc_txtCP.fill = GridBagConstraints.HORIZONTAL;
+		gbc_txtCP.gridx = 3;
+		gbc_txtCP.gridy = 1;
+		panel_1.add(txtCp, gbc_txtCP);
+		txtCp.setEditable(false);
+		txtCp.setColumns(5);
+		
 		JScrollPane scrollPane = new JScrollPane();
 		scrollPane.setOpaque(false);
 		GridBagConstraints gbc_scrollPane_1 = new GridBagConstraints();
 		gbc_scrollPane_1.fill = GridBagConstraints.BOTH;
 		gbc_scrollPane_1.gridx = 0;
-		gbc_scrollPane_1.gridy = 0;
+		gbc_scrollPane_1.gridy = 1;
 		add(scrollPane, gbc_scrollPane_1);
 		
 		JLabel lblInventory = new JLabel("Inventory");
@@ -60,8 +204,16 @@ public class InventoryPanel extends JPanel {
 		scrollPane.setViewportView(inventory);
 		inventory.setLayout(new VerticalLayout(5));
 		
-		inventoryObserver = new ObservableListener<>(this, 
+		inventoryObserver = new ObservableListener<>(this,
 				(self, inv) -> self.updateModel(inv) );
+		
+		moneyObserver = new ObservableListener<>(txtPp, 
+				($, money) -> {
+					txtPp.setText("" + money.getPp());
+					txtGp.setText("" + money.getGp());
+					txtSp.setText("" + money.getSp());
+					txtCp.setText("" + money.getCp());
+				});
 	}
 
 	private void updateModel(DDInventory inv) {
@@ -71,6 +223,7 @@ public class InventoryPanel extends JPanel {
 
 	public void setModel(DDCharacter model) {
 		inventoryObserver.setObserved(model.getInventory());
+		moneyObserver.setObserved(model.getInventory().getWealth());
 	}
 	
 	@Override

+ 10 - 7
src/main/lombok/org/leumasjaffe/charsheet/view/inventory/ItemInfoMenu.java

@@ -28,10 +28,10 @@ class ItemInfoMenu extends JPopupMenu {
 	private static final long serialVersionUID = 1L;
 	
 	IndirectObservableListener<JMenuItem, IntValue> sellListener, equipListener = null;
-	ObservableListener<JMenuItem, DDInventory> buyListener;
+	ObservableListener<JMenuItem, Money> buyListener;
 
 	public ItemInfoMenu(final DDInventory inv, final DDItem item) {
-		final int rawValue = item.getValue().asCopper();
+		final int rawValue = item.getActualValue().asCopper();
 		final BuySellAction doTxn = new BuySellAction(inv, item);
 		final BuySellDialogHelper dlg = new BuySellDialogHelper(item.getFullName());
 
@@ -46,22 +46,24 @@ class ItemInfoMenu extends JPopupMenu {
 
 		JMenuItem mntmBuy = new JMenuItem("Purchase");
 		mntmBuy.addActionListener(e -> {
-			doTxn.applyTransaction(dlg.getNumUnits("Purchase", rawValue, inv.getWealth().asCopper() / rawValue), rawValue);
+			doTxn.applyTransaction(dlg.getNumUnits("Purchase", rawValue, rawValue == 0 ? 100 : 
+				inv.getWealth().asCopper() / rawValue), rawValue);
 		});
 		buyListener = new ObservableListener<>(mntmBuy, (c, v) -> {
-			c.setEnabled(inv.getWealth().asCopper() >= item.getValue().asCopper());
+			c.setEnabled(v.asCopper() >= item.getActualValue().asCopper());
 		});
-		buyListener.setObserved(inv);
+		buyListener.setObserved(inv.getWealth());
 		add(mntmBuy);
 
 		JMenuItem mntmSell = new JMenuItem("Sell");	
 		mntmSell.addActionListener(e -> {
-			doTxn.applyTransaction(-dlg.getNumUnits("Sell", rawValue / 2, item.getCount().value()), rawValue / 2);
+			doTxn.applyTransaction(-dlg.getNumUnits("Sell", rawValue / 2, 
+					item.getCount().value() - item.getCountEquipped().value()), rawValue / 2);
 		});
 		sellListener = new IndirectObservableListener<>(mntmSell, (c, v) -> {
 			c.setEnabled(item.getUnequippedCount() > 0);
 		});
-		sellListener.setObserved(item.getCount(), item.getCountEquipped());
+		sellListener.setObserved(item.getCount(), item.getCount(), item.getCountEquipped());
 		add(mntmSell);
 
 		if (item.getSlot() != EquipmentSlot.NONE) {
@@ -109,6 +111,7 @@ class ItemInfoMenu extends JPopupMenu {
 			final Money wealth = inv.getWealth();
 			wealth.assign(wealth.difference(Money.fromCopper(selected * txnPrice)));
 			ObserverDispatch.notifySubscribers(item.getCount(), null);
+			ObserverDispatch.notifySubscribers(wealth, null);
 		}
 	}
 }

+ 1 - 1
src/main/lombok/org/leumasjaffe/charsheet/view/inventory/ItemPanel.java

@@ -152,7 +152,7 @@ public class ItemPanel extends JPanel {
 				comp.repaint();
 			}
 		});
-		countListener.setObserved(item.getCount(), item.getCountEquipped());
+		countListener.setObserved(item.getCount(), item.getCount(), item.getCountEquipped());
 		addMouseListener(new PopClickListener(new ItemInfoMenu(inv, item)));
 	}
 

+ 1 - 1
src/main/lombok/org/leumasjaffe/charsheet/view/inventory/ShieldPanel.java

@@ -26,7 +26,7 @@ public class ShieldPanel extends JPanel {
 	private static final long serialVersionUID = 1L;
 
 	public ShieldPanel(DDItem item) {
-		final DDArmor armor = item.getArmor();
+		final DDArmor armor = item.getArmor().get();
 		
 		setPreferredSize(new Dimension(280, 70));
 		GridBagLayout gridBagLayout = new GridBagLayout();

+ 2 - 2
src/main/lombok/org/leumasjaffe/charsheet/view/inventory/WeaponPanel.java

@@ -26,7 +26,7 @@ public class WeaponPanel extends JPanel {
 	private static final long serialVersionUID = 1L;
 
 	public WeaponPanel(final DDItem item) {
-		final DDWeapon weapon = item.getWeapon();
+		final DDWeapon weapon = item.getWeapon().get();
 		
 		setPreferredSize(new Dimension(280, 70));
 		GridBagLayout gridBagLayout = new GridBagLayout();
@@ -227,7 +227,7 @@ public class WeaponPanel extends JPanel {
 		panel_1.add(rangeField, gbc_rangeField);
 		rangeField.setColumns(10);
 		
-		JTextField typeField = new JTextField(item.getWeapon().getType().toString());
+		JTextField typeField = new JTextField(weapon.getType().toString());
 		typeField.setHorizontalAlignment(SwingConstants.CENTER);
 		typeField.setColumns(10);
 		GridBagConstraints gbc_typeField = new GridBagConstraints();

+ 22 - 39
src/main/lombok/org/leumasjaffe/charsheet/view/summary/ArmorLine.java

@@ -12,6 +12,7 @@ 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.format.StringHelper;
 import org.leumasjaffe.observer.IndirectObservableListener;
 import org.leumasjaffe.observer.ObservableListener;
@@ -358,57 +359,39 @@ public class ArmorLine extends JPanel {
 		
 		armorTotalObserver = new IndirectObservableListener<>(total, (c, v) -> {
 			final DDInventory inv = v.getInventory();
-			int iarmor = 0;
-			int ishield = 0;
-			int dex = v.getAbilities().getDex().modifier();
-			int isize = v.getSize().value().modifier;
-			int inatural = 0;
-			int ideflect = 0;
-			int imisc = 0;
-			{
-				final DDItem body = inv.getEquipment().get(EquipmentSlot.BODY);
-				if ( body != null && body.isArmor() ) {
-					iarmor = body.getArmor().getActualAcBonus();
-					dex = Math.min(dex, body.getArmor().getMaxDex());
-				}
-			}
-			{
-				final DDItem offHand = inv.getEquipment().get(EquipmentSlot.OFF_HAND);
-				if ( offHand != null && offHand.isArmor() ) {
-					ishield = offHand.getArmor().getActualAcBonus();
-				}
-			}
-			c.setText(StringHelper.toString(10 + iarmor + ishield + 
-					dex + isize + inatural + ideflect + imisc));
+			IntValue iarmor = new IntValue(0), ishield = new IntValue(0);
+			IntValue dex = new IntValue(v.getAbilities().getDex().modifier());
+			int isize = v.getSize().value().modifier, inatural = 0, ideflect = 0, imisc = 0;
+			
+			inv.getEquipment().get(EquipmentSlot.BODY).flatMap(DDItem::getArmor).ifPresent( a -> {
+				iarmor.value(a.getActualAcBonus());
+				dex.value(Math.min(dex.value(), a.getMaxDex()));
+			});
+			inv.getEquipment().get(EquipmentSlot.OFF_HAND).flatMap(DDItem::getArmor).ifPresent(
+					a -> ishield.value(a.getActualAcBonus()));
+			c.setText(StringHelper.toString(10 + iarmor.value() + ishield.value() + 
+					dex.value() + isize + inatural + ideflect + imisc));
 		});
 		
 		armorArmorObserver = new ObservableListener<>(armor, (c, v) -> {
-			int iarmor = 0;
-			final DDItem body = v.getEquipment().get(EquipmentSlot.BODY);
-			if ( body != null && body.isArmor() ) {
-				iarmor = body.getArmor().getActualAcBonus();
-			}
+			IntValue iarmor = new IntValue(0);
+			v.getEquipment().get(EquipmentSlot.BODY).flatMap(DDItem::getArmor).ifPresent(
+					a -> iarmor.value(a.getActualAcBonus()));
 			c.setText(StringHelper.toString(iarmor));
 		});
 		
 		armorShieldObserver = new ObservableListener<>(shield, (c, v) -> {
-			int iarmor = 0;
-			final DDItem offHand = v.getEquipment().get(EquipmentSlot.OFF_HAND);
-			if ( offHand != null && offHand.isArmor() ) {
-				iarmor = offHand.getArmor().getActualAcBonus();
-			}
+			IntValue iarmor = new IntValue(0);
+			v.getEquipment().get(EquipmentSlot.OFF_HAND).flatMap(DDItem::getArmor).ifPresent(
+					a -> iarmor.value(a.getActualAcBonus()));
 			c.setText(StringHelper.toString(iarmor));
 		});
 		
 		armorDexObserver = new IndirectObservableListener<>(dexterity, (c, v) -> {
 			final DDInventory inv = v.getInventory();
-			int dex = v.getAbilities().getDex().modifier();
-			{
-				final DDItem body = inv.getEquipment().get(EquipmentSlot.BODY);
-				if ( body != null && body.isArmor() ) {
-					dex = Math.min(dex, body.getArmor().getMaxDex());
-				}
-			}
+			IntValue dex = new IntValue(v.getAbilities().getDex().modifier());
+			inv.getEquipment().get(EquipmentSlot.BODY).flatMap(DDItem::getArmor).ifPresent(
+					a -> dex.value(Math.min(dex.value(), a.getMaxDex())));
 			c.setText(StringHelper.toString(dex));
 		});
 	}