From fcf4ab76190453c71d81e75b6f071e5363df9a6b Mon Sep 17 00:00:00 2001
From: Ben Wallis <atomyc@gmail.com>
Date: Sun, 16 May 2021 19:38:29 +0100
Subject: [PATCH] main/offhand weapon swap check refactor

---
 common/src/comp/inventory/loadout.rs | 159 ++++++++-------------------
 common/src/comp/inventory/mod.rs     |   2 +-
 voxygen/src/hud/bag.rs               |   8 +-
 3 files changed, 53 insertions(+), 116 deletions(-)

diff --git a/common/src/comp/inventory/loadout.rs b/common/src/comp/inventory/loadout.rs
index 0d02b9c8d6..b0ebde4170 100644
--- a/common/src/comp/inventory/loadout.rs
+++ b/common/src/comp/inventory/loadout.rs
@@ -1,6 +1,6 @@
 use crate::comp::{
     inventory::{
-        item::{tool, ItemKind},
+        item::{Hands, ItemKind, Tool},
         slot::{ArmorSlot, EquipSlot},
         InvSlot,
     },
@@ -167,84 +167,18 @@ impl Loadout {
             return;
         }
 
-        enum MainhandHand {
-            MainhandA,
-            MainhandB,
-        }
-
-        let hands_swapping = match (equip_slot_a, equip_slot_b) {
-            (EquipSlot::ActiveMainhand, EquipSlot::ActiveOffhand) => Some(MainhandHand::MainhandA),
-            (EquipSlot::ActiveOffhand, EquipSlot::ActiveMainhand) => Some(MainhandHand::MainhandB),
-            (EquipSlot::InactiveMainhand, EquipSlot::InactiveOffhand) => {
-                Some(MainhandHand::MainhandA)
-            },
-            (EquipSlot::InactiveOffhand, EquipSlot::InactiveMainhand) => {
-                Some(MainhandHand::MainhandB)
-            },
-            _ => None,
-        };
-
         let item_a = self.swap(equip_slot_a, None);
-        let item_b = self.swap(equip_slot_b, None);
+        let item_b = self.swap(equip_slot_b, item_a);
+        assert_eq!(self.swap(equip_slot_a, item_b), None);
 
-        if let Some(hands_swapping) = hands_swapping {
-            match hands_swapping {
-                MainhandHand::MainhandA => {
-                    if item_b
-                        .as_ref()
-                        .map_or(true, |i| self.slot_can_hold(equip_slot_a, &i.kind()))
-                        && item_a
-                            .as_ref()
-                            .map_or(true, |i| equip_slot_b.can_hold(&i.kind()))
-                    {
-                        // Checks that item b (from offhand) can go into equip slot a (mainhand
-                        // slot) and that item a (from mainhand) is a valid item to insert into
-                        // equip slot b (offhand slot) Swap
-                        self.swap(equip_slot_a, item_b).unwrap_none();
-                        self.swap(equip_slot_b, item_a).unwrap_none();
-                    } else {
-                        // Otherwise put the items back
-                        self.swap(equip_slot_a, item_a).unwrap_none();
-                        self.swap(equip_slot_b, item_b).unwrap_none();
-                    }
-                },
-                MainhandHand::MainhandB => {
-                    if item_a
-                        .as_ref()
-                        .map_or(true, |i| self.slot_can_hold(equip_slot_b, &i.kind()))
-                        && item_b
-                            .as_ref()
-                            .map_or(true, |i| equip_slot_b.can_hold(&i.kind()))
-                    {
-                        // Checks that item a (from offhand) can go into equip slot b (mainhand
-                        // slot) and that item b (from mainhand) is a valid item to insert into
-                        // equip slot a (offhand slot) Swap
-                        self.swap(equip_slot_b, item_a).unwrap_none();
-                        self.swap(equip_slot_a, item_b).unwrap_none();
-                    } else {
-                        // Otherwise put the items back
-                        self.swap(equip_slot_a, item_a).unwrap_none();
-                        self.swap(equip_slot_b, item_b).unwrap_none();
-                    }
-                },
-            }
-        } else {
-            // Check if items can go in the other slots
-            if item_a
-                .as_ref()
-                .map_or(true, |i| self.slot_can_hold(equip_slot_b, &i.kind()))
-                && item_b
-                    .as_ref()
-                    .map_or(true, |i| self.slot_can_hold(equip_slot_a, &i.kind()))
-            {
-                // Swap
-                self.swap(equip_slot_b, item_a).unwrap_none();
-                self.swap(equip_slot_a, item_b).unwrap_none();
-            } else {
-                // Otherwise put the items back
-                self.swap(equip_slot_a, item_a).unwrap_none();
-                self.swap(equip_slot_b, item_b).unwrap_none();
-            }
+        // Check if items are valid in their new positions
+        if !self.slot_can_hold(equip_slot_a, self.equipped(equip_slot_b).map(|x| x.kind()))
+            || !self.slot_can_hold(equip_slot_b, self.equipped(equip_slot_a).map(|x| x.kind()))
+        {
+            // If not, revert the swap
+            let item_a = self.swap(equip_slot_a, None);
+            let item_b = self.swap(equip_slot_b, item_a);
+            assert_eq!(self.swap(equip_slot_a, item_b), None);
         }
     }
 
@@ -257,7 +191,7 @@ impl Loadout {
         let mut suitable_slots = self
             .slots
             .iter()
-            .filter(|s| self.slot_can_hold(s.equip_slot, item_kind));
+            .filter(|s| self.slot_can_hold(s.equip_slot, Some(item_kind)));
 
         let first = suitable_slots.next();
 
@@ -277,7 +211,7 @@ impl Loadout {
     ) -> impl Iterator<Item = &Item> {
         self.slots
             .iter()
-            .filter(move |s| self.slot_can_hold(s.equip_slot, &item_kind))
+            .filter(move |s| self.slot_can_hold(s.equip_slot, Some(&item_kind)))
             .filter_map(|s| s.slot.as_ref())
     }
 
@@ -370,7 +304,7 @@ impl Loadout {
         let loadout_slot = self
             .slots
             .iter()
-            .find(|s| s.slot.is_none() && self.slot_can_hold(s.equip_slot, item.kind()))
+            .find(|s| s.slot.is_none() && self.slot_can_hold(s.equip_slot, Some(item.kind())))
             .map(|s| s.equip_slot);
         if let Some(slot) = self
             .slots
@@ -389,39 +323,42 @@ impl Loadout {
     }
 
     /// Checks that a slot can hold a given item
-    pub(super) fn slot_can_hold(&self, equip_slot: EquipSlot, item_kind: &ItemKind) -> bool {
-        // Checks if item can be equipped in a mainhand slot
-        let mainhand_check = |offhand_slot| {
-            // Allows item to be equipped if itemkind is a tool and...
-            matches!(item_kind, ItemKind::Tool(mainhand) if {
-                if let Some(ItemKind::Tool(offhand)) = self.equipped(offhand_slot).map(|i| i.kind()) {
-                    // if offhand is 1 handed, only if mainhand is also 1 handed
-                    matches!(offhand.hands, tool::Hands::One) && matches!(mainhand.hands, tool::Hands::One)
-                } else {
-                    // else there is no tool equipped in offhand, so only if slot can normally hold this item
-                    equip_slot.can_hold(item_kind)
-                }
-            })
+    pub(super) fn slot_can_hold(
+        &self,
+        equip_slot: EquipSlot,
+        item_kind: Option<&ItemKind>,
+    ) -> bool {
+        let weapon_compare_slots = match equip_slot {
+            EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand => {
+                Some((EquipSlot::ActiveMainhand, EquipSlot::ActiveOffhand))
+            },
+            EquipSlot::InactiveMainhand | EquipSlot::InactiveOffhand => {
+                Some((EquipSlot::InactiveMainhand, EquipSlot::InactiveOffhand))
+            },
+            _ => None,
         };
 
-        // Checks if item can be equipped in an offhand slot
-        let offhand_check = |mainhand_slot| {
-            // Allows item to be equipped if itemkind is a tool and...
-            matches!(item_kind, ItemKind::Tool(offhand) if {
-                // if offhand weapon is 1 handed...
-                matches!(offhand.hands, tool::Hands::One)
-                // and if mainhand has a 1 handed weapon...
-                && matches!(self.equipped(mainhand_slot).map(|i| i.kind()), Some(ItemKind::Tool(mainhand)) if matches!(mainhand.hands, tool::Hands::One))
-            })
-        };
-
-        match equip_slot {
-            EquipSlot::ActiveMainhand => mainhand_check(EquipSlot::ActiveOffhand),
-            EquipSlot::ActiveOffhand => offhand_check(EquipSlot::ActiveMainhand),
-            EquipSlot::InactiveMainhand => mainhand_check(EquipSlot::InactiveOffhand),
-            EquipSlot::InactiveOffhand => offhand_check(EquipSlot::InactiveMainhand),
-            _ => equip_slot.can_hold(item_kind),
+        // Disallow equipping incompatible weapon pairs (i.e a two-handed weapon and a
+        // one-handed weapon)
+        if let Some(weapon_compare_slots) = weapon_compare_slots {
+            if !Loadout::is_valid_weapon_pair(
+                self.equipped(weapon_compare_slots.0).map(|x| &x.kind),
+                self.equipped(weapon_compare_slots.1).map(|x| &x.kind),
+            ) {
+                return false;
+            }
         }
+
+        item_kind.map_or(true, |item_kind| equip_slot.can_hold(item_kind))
+    }
+
+    #[rustfmt::skip]
+    fn is_valid_weapon_pair(main_hand: Option<&ItemKind>, off_hand: Option<&ItemKind>) -> bool {
+        matches!((main_hand, off_hand),
+            (Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), None) |
+            (Some(ItemKind::Tool(Tool { hands: Hands::Two, .. })), None) |
+            (Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), Some(ItemKind::Tool(Tool { hands: Hands::One, .. }))) |
+            (None, None))
     }
 
     pub(super) fn swap_equipped_weapons(&mut self) {
@@ -429,7 +366,7 @@ impl Loadout {
         // nothing is equipped in slot
         let valid_slot = |equip_slot| {
             self.equipped(equip_slot)
-                .map_or(true, |i| self.slot_can_hold(equip_slot, i.kind()))
+                .map_or(true, |i| self.slot_can_hold(equip_slot, Some(i.kind())))
         };
 
         if valid_slot(EquipSlot::ActiveMainhand)
diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs
index ad8c887f10..0fed36145a 100644
--- a/common/src/comp/inventory/mod.rs
+++ b/common/src/comp/inventory/mod.rs
@@ -765,7 +765,7 @@ impl Inventory {
     pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
         // Check if loadout slot can hold item
         if !self.get(inv_slot_id).map_or(true, |item| {
-            self.loadout.slot_can_hold(equip_slot, &item.kind())
+            self.loadout.slot_can_hold(equip_slot, Some(&item.kind()))
         }) {
             trace!("can_swap = false, equip slot can't hold item");
             return false;
diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs
index e6f69b6b00..844797a42a 100644
--- a/voxygen/src/hud/bag.rs
+++ b/voxygen/src/hud/bag.rs
@@ -1004,10 +1004,10 @@ impl<'a> Widget for Bag<'a> {
             }
             // Ring
             let ring1_item = inventory
-                .equipped(EquipSlot::Armor(ArmorSlot::Ring1))
+                .equipped(EquipSlot::InactiveOffhand)
                 .map(|item| item.to_owned());
             let slot = slot_maker
-                .fabricate(EquipSlot::Armor(ArmorSlot::Ring1), [45.0; 2])
+                .fabricate(EquipSlot::InactiveOffhand, [45.0; 2])
                 .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0)
                 .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
                 .filled_slot(filled_slot);
@@ -1026,10 +1026,10 @@ impl<'a> Widget for Bag<'a> {
             }
             // Ring 2
             let ring2_item = inventory
-                .equipped(EquipSlot::Armor(ArmorSlot::Ring2))
+                .equipped(EquipSlot::InactiveMainhand)
                 .map(|item| item.to_owned());
             let slot = slot_maker
-                .fabricate(EquipSlot::Armor(ArmorSlot::Ring2), [45.0; 2])
+                .fabricate(EquipSlot::InactiveMainhand, [45.0; 2])
                 .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0)
                 .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
                 .filled_slot(filled_slot);