瀏覽代碼

Altering Weapon/Armor enhancement into a mixin-esque format. This will allow weapon enhancements to be dumped into an object store, and combined together with ease.
TODO: maybe cleanup weapon and armor JSON schema more.

Sam Jaffe 8 年之前
父節點
當前提交
2f78e09f62

+ 10 - 9
resources/Potato.json

@@ -110,7 +110,7 @@
   "inventory": {
     "items": [
       {
-        "name": "MWK Quarterstaff",
+        "name": "Quarterstaff",
         "count": 1,
         "countEquipped": 1,
         "value": {"pp": 0, "gp": 600, "sp": 0, "cp": 0},
@@ -118,31 +118,32 @@
         "slot": "TWO_HANDS",
         "weight": 4,
         "weapon": {
-          "attackBonus": 1,
-          "damageBonus": 0,
           "damage": "1d6",
           "secondaryDamage": "1d6",
           "criticalThreat": 20,
           "criticalDamage": 2,
           "range": "Melee",
-          "type": "Bludgeoning"
+          "type": "Bludgeoning",
+          "masterwork": true
         }
       },
       {
-        "name": "+1 Full Plate Armor",
+        "name": "Full Plate Armor",
         "count": 1,
         "countEquipped": 1,
-        "value": {"pp": 0, "gp": 2650, "sp": 0, "cp": 0},
+        "value": {"pp": 0, "gp": 1500, "sp": 0, "cp": 0},
         "page": "PH123",
         "slot": "BODY",
         "weight": 50,
         "armor": {
-          "bonus": 9,
+          "acBonus": 8,
           "type": "Heavy",
           "maxDex": 1,
-          "checkPenalty": -5,
+          "checkPenalty": -6,
           "spellFailure": 35,
-          "speed": 15
+          "speed": 15,
+          "masterwork": true,
+          "bonus": "+1"
         }
       }
     ],

+ 96 - 55
schema/inventory.json

@@ -85,66 +85,107 @@
         "TWO_HANDS"
       ]
     },
-    "weapon": {
-      "dependencies":{
-        "secondaryCriticalDamage": ["secondaryDamage"]
+    "enchantable": {
+      "bonus": {
+        "type": "string",
+        "enum": [
+          "+1", "+2", "+3", "+4", "+5"
+        ]
       },
       "properties": {
-        "attackBonus": {"type": "integer"},
-        "damageBonus": {"type": "integer"},
-        "damage": {"type": "string"},
-        "secondaryDamage": {"type": "string"},
-        "criticalThreat": {"type": "integer", "maximum": 20},
-        "criticalDamage": {"type": "integer", "minimum": 2},
-        "secondaryCriticalDamage": {"type": "integer", "minimum": 2},
-        "range": {
-          "oneOf": [
-            {"type": "string", "const":"Melee"},
-            {"$ref": "file:common.json#/definitions/distance"}
-          ]
-        },
-        "type": {
-          "type": "string", 
-          "enum": [
-            "Slashing",
-            "Piercing",
-            "Bludgeoning"
-          ]
+        "masterwork": {"type": "boolean"},
+        "bonus": {"$ref": "#/definitions/enchantable/bonus"},
+        "enchantments": {
+          "items": {
+            "oneOf": [
+              {
+                "properties": {
+                  "name": {"type": "string"},
+                  "effectiveBonus": {"$ref": "#/definitions/enchantable/bonus"}
+                },
+                "required": ["name", "effectiveBonus"],
+                "type": "object"
+              }
+              {
+                "properties": {
+                  "name": {"type": "string"},
+                  "adHocValue": {"$ref": "#/definitions/money"}
+                },
+                "required": ["name", "adHocValue"],
+                "type": "object"
+              }
+            ]
+          },
+          "type": "array"
         }
-      },
-      "required": [
-        "damage",
-        "type",
-        "range",
-        "criticalThreat",
-        "criticalDamage"
-      ],
-      "type": "object",
-      "additionalProperties": false
+      }
+    },
+    "weapon": {
+      "allOf": [
+        {"$ref": "#/definitions/enchantable"}
+        {
+          "dependencies":{
+            "secondaryCriticalDamage": ["secondaryDamage"]
+          },
+          "properties": {
+            "damage": {"type": "string"},
+            "secondaryDamage": {"type": "string"},
+            "criticalThreat": {"type": "integer", "maximum": 20},
+            "criticalDamage": {"type": "integer", "minimum": 2},
+            "secondaryCriticalDamage": {"type": "integer", "minimum": 2},
+            "range": {
+              "oneOf": [
+                {"type": "string", "const":"Melee"},
+                {"$ref": "file:common.json#/definitions/distance"}
+              ]
+            },
+            "type": {
+              "type": "string", 
+              "enum": [
+                "Slashing",
+                "Piercing",
+                "Bludgeoning"
+              ]
+            }
+          },
+          "required": [
+            "damage",
+            "type",
+            "range",
+            "criticalThreat",
+            "criticalDamage"
+          ],
+          "type": "object"
+        }
+      ]
     },
     "armor": {
-      "dependencies": {
-        "speed": {"properties": {"type": {"not": {"const": "Shield"}}}}
-      },
-      "properties": {
-        "bonus": {"type": "integer", "minimum": 0},
-        "type": {
-          "type": "string", 
-          "enum": ["Light", "Medium", "Heavy", "Shield"]
-        },
-        "maxDex": {"type": "integer", "minimum": 0},
-        "checkPenalty": {"type": "integer", "maximum": 0},
-        "spellFailure": {"type": "integer", "minimum": 0, "maximum": 100},
-        "speed": {"type": "integer"}
-      },
-      "required": [
-        "bonus",
-        "type",
-        "checkPenalty",
-        "spellFailure"
-      ],
-      "type": "object",
-      "additionalProperties": false
+      "allOf": [
+        {"$ref": "#/definitions/enchantable"}
+        {
+          "dependencies": {
+            "speed": {"properties": {"type": {"not": {"const": "Shield"}}}}
+          },
+          "properties": {
+            "acBonus": {"type": "integer", "minimum": 0},
+            "type": {
+              "type": "string", 
+              "enum": ["Light", "Medium", "Heavy", "Shield"]
+            },
+            "maxDex": {"type": "integer", "minimum": 0},
+            "checkPenalty": {"type": "integer", "maximum": 0},
+            "spellFailure": {"type": "integer", "minimum": 0, "maximum": 100},
+            "speed": {"type": "integer"}
+          },
+          "required": [
+            "bonus",
+            "type",
+            "checkPenalty",
+            "spellFailure"
+          ],
+          "type": "object"
+        }
+      ]
     },
     "money": {
       "properties": {

+ 18 - 3
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDArmor.java

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

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

@@ -0,0 +1,32 @@
+package org.leumasjaffe.charsheet.model.inventory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.Data;
+
+@Data
+class DDEnchantableItem {
+	boolean isMasterwork = false;
+	Enhancement bonus = Enhancement.NONE;
+	List<Enchantment> enchantments = new ArrayList<>();
+
+	public String getNameModifier() {
+		return bonus == Enhancement.NONE ? (isMasterwork ? "MWK " : "") : 
+			bonus.toValue() + " " + getEnchantNames();
+	}
+
+	int getAdHocPrice() {
+		return enchantments.stream().map(Enchantment::getAdHocValue)
+				.map(o -> o.orElse(Money.fromCopper(0))).mapToInt(Money::asCopper).sum();
+	}
+
+	String getEnchantNames() {
+		return enchantments.stream().map(Enchantment::getName).reduce("", (l, r) -> l + r + " ");
+	}
+
+	int getEnchantBonus() {
+		return enchantments.stream().map(Enchantment::getEffectiveBonus)
+				.mapToInt(Enhancement::getValue).sum();
+	}
+}

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

@@ -39,7 +39,7 @@ public class DDInventory extends Observable {
 	@JsonCreator
 	private DDInventory(DDInventory.Serializable data) {
 		items.addAll(data.items);
-		named.putAll(items.stream().collect(Collectors.toMap(DDItem::getName, i -> i)));
+		named.putAll(items.stream().collect(Collectors.toMap(DDItem::getFullName, i -> i)));
 		fromSerializableEquipment(data.equipment);
 		favorites.putAll(data.favorites);
 		wealth.assign(data.wealth);

+ 9 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDItem.java

@@ -45,6 +45,14 @@ public class DDItem {
 		properties.put(key, prop);
 	}
 	
+	public String getFullName() {
+		return (isWeapon() ? weapon.getNameModifier() : isArmor() ? armor.getNameModifier() : "") + getName(); 
+	}
+	
+	public Money getActualValue() {
+		return isWeapon() ? value.sum(weapon.getActualValue()) : isArmor() ? value.sum(armor.getActualValue()) : value;
+	}
+	
 	public void adjustCount(int amt) {
 		this.count.value(this.count.value() + amt);
 	}
@@ -55,6 +63,7 @@ public class DDItem {
 	
 	@JsonAnyGetter 
 	private Map<String, Object> getProperties() { 
+		if ( properties == null ) { properties = new HashMap<>(); }
 		return Collections.unmodifiableMap(properties);
 	}
 	

+ 21 - 4
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/DDWeapon.java

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

+ 12 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/Enchantment.java

@@ -0,0 +1,12 @@
+package org.leumasjaffe.charsheet.model.inventory;
+
+import java.util.Optional;
+
+import lombok.Getter;
+
+@Getter
+public class Enchantment {
+	Enhancement effectiveBonus;
+	String name;
+	Optional<Money> adHocValue;
+}

+ 22 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/Enhancement.java

@@ -0,0 +1,22 @@
+package org.leumasjaffe.charsheet.model.inventory;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+public enum Enhancement {
+	NONE(0), P1(1), P2(2), P3(3), P4(4), P5(5);
+	
+	@Getter final int value;
+	
+	@JsonCreator Enhancement of(String name) {
+		return name.equals("") ? NONE : Enhancement.valueOf(name.replace('+', 'P'));
+	}
+	
+	@JsonValue String toValue() {
+		return this == NONE ? "" : name().replace('P', '+');
+	}
+}

+ 6 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/inventory/Money.java

@@ -58,6 +58,12 @@ public class Money implements Comparable<Money> {
 		final int sp = cp / 10; cp -= sp * 10;
 		return new Money(pp, gp, sp, cp);
 	}
+	
+	public static Money fromCopperToGold(int cp) {
+		final int gp = cp / 100; cp -= gp * 100;
+		final int sp = cp / 10; cp -= sp * 10;
+		return new Money(0, gp, sp, cp);
+	}
 
 	public int asCopper() {
 		return cp + sp * 10 + gp * 100 + pp * 1000;

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

@@ -113,7 +113,7 @@ public class ArmorPanel extends JPanel {
 		gbc_lblMaxDex.gridy = 1;
 		panel.add(lblMaxDex, gbc_lblMaxDex);
 		
-		JTextField armorNameField = new JTextField(item.getName());
+		JTextField armorNameField = new JTextField(item.getFullName());
 		GridBagConstraints gbc_armorNameField = new GridBagConstraints();
 		gbc_armorNameField.insets = new Insets(0, 0, 0, 0);
 		gbc_armorNameField.fill = GridBagConstraints.HORIZONTAL;
@@ -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().getBonus()));
+		JTextField armorBonusField = new JTextField(StringHelper.toSignedString(item.getArmor().getActualAcBonus()));
 		armorBonusField.setHorizontalAlignment(SwingConstants.CENTER);
 		GridBagConstraints gbc_armorBonusField = new GridBagConstraints();
 		gbc_armorBonusField.insets = new Insets(0, 0, 0, 0);
@@ -228,7 +228,7 @@ public class ArmorPanel extends JPanel {
 		gbc_lblSpecialProperties.gridy = 0;
 		panel_1.add(lblSpecialProperties, gbc_lblSpecialProperties);
 		
-		JTextField checkField = new JTextField(StringHelper.toString(armor.getCheckPenalty()));
+		JTextField checkField = new JTextField(StringHelper.toString(armor.getActualCheckPenalty()));
 		checkField.setHorizontalAlignment(SwingConstants.CENTER);
 		GridBagConstraints gbc_checkField = new GridBagConstraints();
 		gbc_checkField.insets = new Insets(0, 0, 0, 0);

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

@@ -115,7 +115,7 @@ public class EquipmentPanel extends JPanel {
 			});
 		}
 		
-		equip.keySet().stream().filter( slot -> ! manual.contains(slot) )
+		equip.keySet().stream().filter( slot -> equip.get(slot) != null && !manual.contains(slot) )
 		.forEach( slot -> {
 			createWithRightClickMenu(null, equip, slot, slot);
 		});

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

@@ -33,11 +33,11 @@ class ItemInfoMenu extends JPopupMenu {
 	public ItemInfoMenu(final DDInventory inv, final DDItem item) {
 		final int rawValue = item.getValue().asCopper();
 		final BuySellAction doTxn = new BuySellAction(inv, item);
-		final BuySellDialogHelper dlg = new BuySellDialogHelper(item.getName());
+		final BuySellDialogHelper dlg = new BuySellDialogHelper(item.getFullName());
 
 		JMenuItem mntmInfo = new JMenuItem("Info");
 		mntmInfo.addActionListener(e -> {
-			JFrame frame = new JFrame(item.getName());
+			JFrame frame = new JFrame(item.getFullName());
 			frame.add(new ItemInfoPanel(item));
 			frame.pack();
 			frame.setVisible(true);

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

@@ -105,7 +105,7 @@ public class ItemPanel extends JPanel {
 		gbc_lblValue.gridy = 1;
 		add(lblValue, gbc_lblValue);
 		
-		JTextField nameField = new JTextField(item.getName());
+		JTextField nameField = new JTextField(item.getFullName());
 		GridBagConstraints gbc_nameField = new GridBagConstraints();
 		gbc_nameField.insets = new Insets(0, 0, 0, 0);
 		gbc_nameField.fill = GridBagConstraints.HORIZONTAL;
@@ -134,7 +134,7 @@ public class ItemPanel extends JPanel {
 		add(weightField, gbc_weightField);
 		weightField.setColumns(10);
 		
-		JTextField valueField = new JTextField(item.getValue().toString());
+		JTextField valueField = new JTextField(item.getActualValue().toString());
 		valueField.setHorizontalAlignment(SwingConstants.CENTER);
 		GridBagConstraints gbc_valueField = new GridBagConstraints();
 		gbc_valueField.insets = new Insets(0, 0, 0, 0);

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

@@ -113,7 +113,7 @@ public class ShieldPanel extends JPanel {
 		lblCheckPenalty.setBackground(Color.BLACK);
 		lblCheckPenalty.setFont(new Font("Tahoma", Font.BOLD, 8));
 		
-		JTextField nameField = new JTextField(item.getName());
+		JTextField nameField = new JTextField(item.getFullName());
 		GridBagConstraints gbc_nameField = new GridBagConstraints();
 		gbc_nameField.insets = new Insets(0, 0, 0, 0);
 		gbc_nameField.fill = GridBagConstraints.HORIZONTAL;
@@ -122,7 +122,7 @@ public class ShieldPanel extends JPanel {
 		panel.add(nameField, gbc_nameField);
 		nameField.setColumns(10);
 		
-		JTextField armorBonusField = new JTextField(StringHelper.toSignedString(armor.getBonus()));
+		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);
@@ -142,7 +142,7 @@ public class ShieldPanel extends JPanel {
 		panel.add(weightField, gbc_weightField);
 		weightField.setColumns(10);
 		
-		JTextField checkField = new JTextField(StringHelper.toString(armor.getCheckPenalty()));
+		JTextField checkField = new JTextField(StringHelper.toString(armor.getActualCheckPenalty()));
 		checkField.setHorizontalAlignment(SwingConstants.CENTER);
 		GridBagConstraints gbc_checkField = new GridBagConstraints();
 		gbc_checkField.fill = GridBagConstraints.HORIZONTAL;

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

@@ -113,7 +113,7 @@ public class WeaponPanel extends JPanel {
 		lblCritical.setBackground(Color.BLACK);
 		lblCritical.setFont(new Font("Tahoma", Font.BOLD, 8));
 		
-		JTextField nameField = new JTextField(item.getName());
+		JTextField nameField = new JTextField(item.getFullName());
 		GridBagConstraints gbc_nameField = new GridBagConstraints();
 		gbc_nameField.insets = new Insets(0, 0, 0, 0);
 		gbc_nameField.fill = GridBagConstraints.HORIZONTAL;

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

@@ -368,14 +368,14 @@ public class ArmorLine extends JPanel {
 			{
 				final DDItem body = inv.getEquipment().get(EquipmentSlot.BODY);
 				if ( body != null && body.isArmor() ) {
-					iarmor = body.getArmor().getBonus();
+					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().getBonus();
+					ishield = offHand.getArmor().getActualAcBonus();
 				}
 			}
 			c.setText(StringHelper.toString(10 + iarmor + ishield + 
@@ -386,7 +386,7 @@ public class ArmorLine extends JPanel {
 			int iarmor = 0;
 			final DDItem body = v.getEquipment().get(EquipmentSlot.BODY);
 			if ( body != null && body.isArmor() ) {
-				iarmor = body.getArmor().getBonus();
+				iarmor = body.getArmor().getActualAcBonus();
 			}
 			c.setText(StringHelper.toString(iarmor));
 		});
@@ -395,7 +395,7 @@ public class ArmorLine extends JPanel {
 			int iarmor = 0;
 			final DDItem offHand = v.getEquipment().get(EquipmentSlot.OFF_HAND);
 			if ( offHand != null && offHand.isArmor() ) {
-				iarmor = offHand.getArmor().getBonus();
+				iarmor = offHand.getArmor().getActualAcBonus();
 			}
 			c.setText(StringHelper.toString(iarmor));
 		});