Bläddra i källkod

Adding the ability to save and load favorite equipment loadouts.

Sam Jaffe 8 år sedan
förälder
incheckning
1b66e9d013

+ 2 - 0
resources/Potato.json

@@ -150,6 +150,8 @@
       "BODY": "+1 Full Plate Armor",
       "MAIN_HAND": "MWK Quarterstaff",
       "OFF_HAND": "MWK Quarterstaff"
+    },
+    "favorites": {
     }
   }
 }

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

@@ -6,6 +6,7 @@ import lombok.experimental.UtilityClass;
 @UtilityClass
 @FieldDefaults(makeFinal=true) // level is not correctly handled when delomboking in maven builds
 public final class Constants {
+	public String PREVIOUS_LOADOUT = "$$PREVIOUS";
 	public String NO_FLAT_FOOTED = "Keeps Dexterity When Flat-footed";
 	
 	public String K_DISTANCE = "Distance Measurement Unit";

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

@@ -1,29 +1,19 @@
 package org.leumasjaffe.charsheet.model.inventory;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
+import org.leumasjaffe.charsheet.config.Constants;
 import org.leumasjaffe.observer.Observable;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonValue;
 
-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.Setter;
-import lombok.ToString;
+import lombok.*;
 import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
 
-@Getter
-@Setter
-@ToString
+@Getter @Setter @ToString
 @EqualsAndHashCode(callSuper=false)
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class DDInventory extends Observable {
@@ -31,66 +21,83 @@ public class DDInventory extends Observable {
 	public static class Serializable {
 		@NonNull List<DDItem> items;
 		@NonNull Map<EquipmentSlot, String> equipment;
+		@NonNull Map<String, Map<EquipmentSlot, String>> favorites;
 	}
 	
-	@NonNull List<DDItem> items;
-	@NonNull Map<EquipmentSlot, DDItem> equipment;
+	// Serializable properties
+	@NonNull List<DDItem> items = new ArrayList<>();
+	@NonNull Map<EquipmentSlot, DDItem> equipment = new EnumMap<>(EquipmentSlot.class);
+	@NonNull Map<String, Map<EquipmentSlot, String>> favorites = new TreeMap<>();
+	// Transient/Record-Keeping properties
+	@NonNull Map<String, DDItem> named = new HashMap<>();
+	@NonFinal Map<EquipmentSlot, String> previous = null;
 	
-	public DDInventory() {
-		items = new ArrayList<>();
-		equipment = new EnumMap<>(EquipmentSlot.class);
-	}
+	public DDInventory() {}
 	
 	@JsonCreator
-	public DDInventory(DDInventory.Serializable data) {
-		items = new ArrayList<>(data.items);
-		equipment = new EnumMap<>(EquipmentSlot.class);
-		final Map<String, DDItem> named = items.stream().collect(
-				Collectors.toMap(DDItem::getName, i -> i));
-		data.equipment.entrySet().stream().forEach(
-				e -> equipment.put(e.getKey(), named.get(e.getValue())));
+	private DDInventory(DDInventory.Serializable data) {
+		items.addAll(data.items);
+		named.putAll(items.stream().collect(Collectors.toMap(DDItem::getName, i -> i)));
+		fromSerializableEquipment(data.equipment);
+		favorites.putAll(data.favorites);
 	}
-	
+
 	@JsonValue
 	private DDInventory.Serializable getSerial() {
-		final Map<EquipmentSlot, String> m = new EnumMap<>(EquipmentSlot.class);
-		equipment.entrySet().stream().forEach( 
-				e -> m.put(e.getKey(), e.getValue().getName())
-				);
-		return new DDInventory.Serializable(items, m);
+		return new DDInventory.Serializable(items, getSerializableEquipment(), favorites);
+	}
+
+	public void load(final String name) {
+		final @NonNull Map<EquipmentSlot, String> chosen;
+		if (name.equals(Constants.PREVIOUS_LOADOUT)) { chosen = previous; }
+		else { chosen = favorites.get(name); }
+		previous = getSerializableEquipment();
+		fromSerializableEquipment(chosen);
+	}
+	
+	public void save(final String name) {
+		favorites.put(name, getSerializableEquipment());
+	}
+	
+	public Map<String, Map<EquipmentSlot, String>> getFavorites() {
+		return Collections.unmodifiableMap(favorites);
 	}
 	
 	public Map<EquipmentSlot, DDItem> getEquipment() {
 		return Collections.unmodifiableMap(equipment);
 	}
 	
-	public boolean canEquip( final DDItem item ) {
-		return canEquip( item.getSlot() );
+	public boolean hasItem(final String itemName) {
+		return named.containsKey(itemName);
 	}
 	
-	private boolean canEquip( final EquipmentSlot slot ) {
-		switch ( slot ) {
+	public boolean canEquip(final DDItem item) {
+		return canEquip(item.getSlot());
+	}
+	
+	private boolean canEquip(final EquipmentSlot slot) {
+		switch (slot) {
 		case NONE: return false;
-		case TWO_HANDS: return canEquip( EquipmentSlot.MAIN_HAND ) && 
-				canEquip( EquipmentSlot.OFF_HAND );
-		case ONE_HAND: return canEquip( EquipmentSlot.MAIN_HAND ) || 
-				canEquip( EquipmentSlot.OFF_HAND );
-		case RING: return canEquip( EquipmentSlot.RING1 ) || 
-				canEquip( EquipmentSlot.RING2 );
+		case TWO_HANDS: return canEquip(EquipmentSlot.MAIN_HAND) && 
+				canEquip(EquipmentSlot.OFF_HAND);
+		case ONE_HAND: return canEquip(EquipmentSlot.MAIN_HAND) || 
+				canEquip(EquipmentSlot.OFF_HAND);
+		case RING: return canEquip(EquipmentSlot.RING1) || 
+				canEquip(EquipmentSlot.RING2);
 		default:
 			return !equipment.containsKey(slot);
 		}
 	}
 	
-	public void equip( final EquipmentSlot slot, final DDItem item ) {
+	public void equip(final EquipmentSlot slot, final DDItem item) {
 		switch ( slot ) {
 		case NONE:
 		case ONE_HAND:
 		case RING:
 			throw new IllegalArgumentException("Cannot equip directly to slot:" + slot);
 		case TWO_HANDS: 
-			equip( EquipmentSlot.MAIN_HAND, item ); 
-			equip( EquipmentSlot.OFF_HAND, item );
+			equip(EquipmentSlot.MAIN_HAND, item); 
+			equip(EquipmentSlot.OFF_HAND, item);
 			break;
 		default:
 			equipment.put(slot, item);
@@ -98,15 +105,15 @@ public class DDInventory extends Observable {
 		}
 	}
 	
-	public void unequip( final EquipmentSlot slot ) {
-		switch ( slot ) {
+	public void unequip(final EquipmentSlot slot) {
+		switch (slot) {
 		case NONE:
 		case ONE_HAND:
 		case RING:
 			throw new IllegalArgumentException("Cannot unequip directly to slot:" + slot);
 		case TWO_HANDS: 
-			unequip( EquipmentSlot.MAIN_HAND ); 
-			unequip( EquipmentSlot.OFF_HAND );
+			unequip(EquipmentSlot.MAIN_HAND); 
+			unequip(EquipmentSlot.OFF_HAND);
 			break;
 		default:
 			equipment.remove(slot);
@@ -114,27 +121,41 @@ public class DDInventory extends Observable {
 		}
 	}
 
-	public void equipNext( final DDItem item ) {
-		switch ( item.getSlot() ) {
+	public void equipNext(final DDItem item) {
+		switch (item.getSlot()) {
 		case NONE:
 			throw new IllegalArgumentException("Cannot equip unequippable item");
 		case ONE_HAND:
-			if ( canEquip( EquipmentSlot.MAIN_HAND ) ) { 
-				equip( EquipmentSlot.MAIN_HAND, item );
+			if (canEquip(EquipmentSlot.MAIN_HAND)) { 
+				equip(EquipmentSlot.MAIN_HAND, item);
 			} else {
-				equip( EquipmentSlot.OFF_HAND, item );
+				equip(EquipmentSlot.OFF_HAND, item);
 			}
 			break;
 		case RING:
-			if ( canEquip( EquipmentSlot.RING1 ) ) { 
-				equip( EquipmentSlot.RING1, item );
+			if (canEquip(EquipmentSlot.RING1) ) { 
+				equip(EquipmentSlot.RING1, item);
 			} else {
-				equip( EquipmentSlot.RING2, item );
+				equip(EquipmentSlot.RING2, item);
 			}
 			break;
 		default:
-			equip( item.getSlot(), item );
+			equip(item.getSlot(), item);
 			break;
 		}
 	}
+
+	private Map<EquipmentSlot, String> getSerializableEquipment() {
+		final Map<EquipmentSlot, String> m = new EnumMap<>(EquipmentSlot.class);
+		equipment.entrySet().stream().forEach(e -> m.put(e.getKey(), e.getValue().getName()));
+		return Collections.unmodifiableMap(m);
+	}
+	
+	private void fromSerializableEquipment(@NonNull Map<EquipmentSlot, String> data) {
+		equipment.clear();
+		data.entrySet().stream().forEach(e -> {
+			@NonNull DDItem item = named.get(e.getValue()); // Throws NPE if item d/n exist any more
+			equipment.put(e.getKey(), item);
+		});
+	}	
 }

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

@@ -1,12 +1,17 @@
 package org.leumasjaffe.charsheet.view.inventory;
 
+import javax.swing.JComboBox;
 import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
 import javax.swing.JScrollPane;
 import javax.swing.SwingConstants;
 import javax.swing.border.MatteBorder;
 
 import org.jdesktop.swingx.VerticalLayout;
+import org.leumasjaffe.charsheet.config.Constants;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.inventory.DDInventory;
 import org.leumasjaffe.charsheet.model.inventory.DDItem;
@@ -16,6 +21,8 @@ import org.leumasjaffe.observer.ObservableListener;
 import org.leumasjaffe.observer.ObserverDispatch;
 
 import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
 import lombok.experimental.FieldDefaults;
 
 import java.awt.Color;
@@ -27,16 +34,76 @@ import java.util.EnumSet;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.stream.Stream;
 
 import static org.leumasjaffe.charsheet.model.inventory.EquipmentSlot.*;
 
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class EquipmentPanel extends JPanel {
+	
+	private class LoadoutMenu extends JPopupMenu {
+
+		/**
+		 * 
+		 */
+		private static final long serialVersionUID = 1L;
+		
+		@Getter(AccessLevel.PRIVATE) @Setter DDInventory model = null;
+		
+		public LoadoutMenu() {
+			JMenuItem mntmSaveAs = new JMenuItem("Save As");
+			add(mntmSaveAs);
+			mntmSaveAs.addActionListener(e -> {
+				showSaveDialog().ifPresent(name -> getModel().save(name));
+			});
+			
+			JMenuItem mntmLoad = new JMenuItem("Load");
+			add(mntmLoad);
+			mntmLoad.addActionListener(e -> {
+				showLoadDialog().ifPresent(name -> {
+					try {
+						getModel().load(name);
+						EquipmentPanel.this.updateModel(getModel());
+					} catch (NullPointerException except) {
+						JOptionPane.showMessageDialog(this, "Unable to load equipment, some items are missing", 
+								"Error", JOptionPane.ERROR_MESSAGE);
+					}
+				});
+			});
+			
+			JMenuItem mntmViewLoadouts = new JMenuItem("View Loadouts");
+			add(mntmViewLoadouts);
+			mntmViewLoadouts.addActionListener(e -> {
+				
+			});
+		}
+		
+		private Optional<String> showSaveDialog() {
+			return showSaveLoadDialog("Save Equipment Loadout As...", Stream.empty(), true);
+		}
+		
+		private Optional<String> showLoadDialog() {
+			return showSaveLoadDialog("Select Equipment Loadout", Stream.of(Constants.PREVIOUS_LOADOUT), false);
+		}
+		
+		private Optional<String> showSaveLoadDialog(String message, Stream<String> base, boolean edit) {
+			final String[] options = Stream.concat(base, getModel().getFavorites().keySet().stream()).toArray(String[]::new);
+			final JComboBox<String> comboBox = new JComboBox<>(options);
+			comboBox.setEditable(edit);
+			if (JOptionPane.showConfirmDialog(this, comboBox, message, 
+					JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
+				return Optional.of(comboBox.getSelectedItem().toString());
+			}
+			return Optional.empty();
+		}
+	}
+	
 	/**
 	 * 
 	 */
 	private static final long serialVersionUID = 1L;
 	JPanel equipment;
+	LoadoutMenu loadoutMenu = new LoadoutMenu();
 	
 	ObservableListener<EquipmentPanel, DDInventory> equipmentObserver;
 
@@ -64,6 +131,7 @@ public class EquipmentPanel extends JPanel {
 		lblEquipment.setBackground(Color.BLACK);
 		lblEquipment.setOpaque(true);
 		lblEquipment.setForeground(Color.WHITE);
+		lblEquipment.addMouseListener(new PopClickListener(this.loadoutMenu));
 		scrollPane.setColumnHeaderView(lblEquipment);
 
 		equipment = new JPanel();
@@ -77,13 +145,14 @@ public class EquipmentPanel extends JPanel {
 	
 	public void setModel(DDCharacter model) {
 		equipmentObserver.setObserved(model.getInventory());
+		loadoutMenu.setModel(model.getInventory());
 	}
 
 	private void updateModel(final DDInventory inv) {
 		final Set<EquipmentSlot> manual = EnumSet.noneOf(EquipmentSlot.class);
 		equipment.removeAll();	
 		final DDItem armor = inv.getEquipment().get(BODY);
-		if ( armor != null && armor.isArmor() ) {
+		if (armor != null && armor.isArmor()) {
 			manual.add(BODY);
 			createWithRightClickMenu(ArmorPanel::new, inv, BODY, BODY);
 		}
@@ -115,6 +184,7 @@ public class EquipmentPanel extends JPanel {
 		.forEach( slot -> {
 			createWithRightClickMenu(null, inv, slot, slot);
 		});
+		repaint();
 	}
 
 	private Optional<Function<DDItem, JPanel>> getEquipmentRightClickPanelFactory(final DDItem item) {