Model cleanup.
Separates core data transport stuff to extensions so we can use the view models without a core data dependency.
This commit is contained in:
		| @@ -23,6 +23,8 @@ | |||||||
| 		E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7BB260C691400FB205F /* EditChallengeRating.swift */; }; | 		E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7BB260C691400FB205F /* EditChallengeRating.swift */; }; | ||||||
| 		E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */; }; | 		E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */; }; | ||||||
| 		E216E465261FDA2E00FD9262 /* MonsterDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216E464261FDA2E00FD9262 /* MonsterDocument.swift */; }; | 		E216E465261FDA2E00FD9262 /* MonsterDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216E464261FDA2E00FD9262 /* MonsterDocument.swift */; }; | ||||||
|  | 		E216E46D261FDE5600FD9262 /* MonsterViewModel+CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216E46C261FDE5600FD9262 /* MonsterViewModel+CoreData.swift */; }; | ||||||
|  | 		E216E472261FDF3200FD9262 /* SkillViewModel+CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216E471261FDF3200FD9262 /* SkillViewModel+CoreData.swift */; }; | ||||||
| 		E2182E6425B22F8A00DFAEF8 /* Monster+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */; }; | 		E2182E6425B22F8A00DFAEF8 /* Monster+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */; }; | ||||||
| 		E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219247A261989B400C84E12 /* MonsterDTO.swift */; }; | 		E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219247A261989B400C84E12 /* MonsterDTO.swift */; }; | ||||||
| 		E2192480261989F700C84E12 /* SavingThrowDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219247F261989F700C84E12 /* SavingThrowDTO.swift */; }; | 		E2192480261989F700C84E12 /* SavingThrowDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219247F261989F700C84E12 /* SavingThrowDTO.swift */; }; | ||||||
| @@ -104,6 +106,8 @@ | |||||||
| 		E216B7BB260C691400FB205F /* EditChallengeRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditChallengeRating.swift; sourceTree = "<group>"; }; | 		E216B7BB260C691400FB205F /* EditChallengeRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditChallengeRating.swift; sourceTree = "<group>"; }; | ||||||
| 		E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChallengeRatingPicker.swift; sourceTree = "<group>"; }; | 		E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChallengeRatingPicker.swift; sourceTree = "<group>"; }; | ||||||
| 		E216E464261FDA2E00FD9262 /* MonsterDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDocument.swift; sourceTree = "<group>"; }; | 		E216E464261FDA2E00FD9262 /* MonsterDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDocument.swift; sourceTree = "<group>"; }; | ||||||
|  | 		E216E46C261FDE5600FD9262 /* MonsterViewModel+CoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonsterViewModel+CoreData.swift"; sourceTree = "<group>"; }; | ||||||
|  | 		E216E471261FDF3200FD9262 /* SkillViewModel+CoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SkillViewModel+CoreData.swift"; sourceTree = "<group>"; }; | ||||||
| 		E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Monster+CoreDataClass.swift"; sourceTree = "<group>"; }; | 		E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Monster+CoreDataClass.swift"; sourceTree = "<group>"; }; | ||||||
| 		E219247A261989B400C84E12 /* MonsterDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDTO.swift; sourceTree = "<group>"; }; | 		E219247A261989B400C84E12 /* MonsterDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDTO.swift; sourceTree = "<group>"; }; | ||||||
| 		E219247F261989F700C84E12 /* SavingThrowDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavingThrowDTO.swift; sourceTree = "<group>"; }; | 		E219247F261989F700C84E12 /* SavingThrowDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavingThrowDTO.swift; sourceTree = "<group>"; }; | ||||||
| @@ -306,10 +310,12 @@ | |||||||
| 				E216E464261FDA2E00FD9262 /* MonsterDocument.swift */, | 				E216E464261FDA2E00FD9262 /* MonsterDocument.swift */, | ||||||
| 				E219247A261989B400C84E12 /* MonsterDTO.swift */, | 				E219247A261989B400C84E12 /* MonsterDTO.swift */, | ||||||
| 				E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */, | 				E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */, | ||||||
|  | 				E216E46C261FDE5600FD9262 /* MonsterViewModel+CoreData.swift */, | ||||||
| 				E219247F261989F700C84E12 /* SavingThrowDTO.swift */, | 				E219247F261989F700C84E12 /* SavingThrowDTO.swift */, | ||||||
| 				E20209D225D8DD9600EFE733 /* Skill+CoreDataClass.swift */, | 				E20209D225D8DD9600EFE733 /* Skill+CoreDataClass.swift */, | ||||||
| 				E219248426198A1200C84E12 /* SkillDTO.swift */, | 				E219248426198A1200C84E12 /* SkillDTO.swift */, | ||||||
| 				E20209F925D8E19100EFE733 /* SkillViewModel.swift */, | 				E20209F925D8E19100EFE733 /* SkillViewModel.swift */, | ||||||
|  | 				E216E471261FDF3200FD9262 /* SkillViewModel+CoreData.swift */, | ||||||
| 				E2CB0DE0260887ED00142591 /* StringViewModel.swift */, | 				E2CB0DE0260887ED00142591 /* StringViewModel.swift */, | ||||||
| 				E219248926198A5400C84E12 /* TraitDTO.swift */, | 				E219248926198A5400C84E12 /* TraitDTO.swift */, | ||||||
| 			); | 			); | ||||||
| @@ -481,6 +487,8 @@ | |||||||
| 				E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */, | 				E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */, | ||||||
| 				E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */, | 				E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */, | ||||||
| 				E2CB0DD72608720000142591 /* StringHelper.swift in Sources */, | 				E2CB0DD72608720000142591 /* StringHelper.swift in Sources */, | ||||||
|  | 				E216E46D261FDE5600FD9262 /* MonsterViewModel+CoreData.swift in Sources */, | ||||||
|  | 				E216E472261FDF3200FD9262 /* SkillViewModel+CoreData.swift in Sources */, | ||||||
| 				E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */, | 				E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */, | ||||||
| 				E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */, | 				E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */, | ||||||
| 				E219249926198E0D00C84E12 /* MonsterImportHelper.swift in Sources */, | 				E219249926198E0D00C84E12 /* MonsterImportHelper.swift in Sources */, | ||||||
|   | |||||||
| @@ -19,213 +19,7 @@ public class Monster: NSManagedObject { | |||||||
|         self.subtype = subtype; |         self.subtype = subtype; | ||||||
|         self.alignment = alignment; |         self.alignment = alignment; | ||||||
|     } |     } | ||||||
|      |          | ||||||
|     // hasCustomProficiencyBonus |  | ||||||
|     // telepathy int in json but seems like it should be a bool |  | ||||||
|      |  | ||||||
|     let kBaseArmorClassUnarmored = 10; |  | ||||||
|     let kBaseArmorClassMageArmor = 13; |  | ||||||
|     let kBaseArmorClassPadded = 11; |  | ||||||
|     let kBaseArmorClassLeather = 11; |  | ||||||
|     let kBaseArmorClassStudded = 12; |  | ||||||
|     let kBaseArmorClassHide = 12; |  | ||||||
|     let kBaseArmorClassChainShirt = 13; |  | ||||||
|     let kBaseArmorClassScaleMail = 14; |  | ||||||
|     let kBaseArmorClassBreastplate = 14; |  | ||||||
|     let kBaseArmorClassHalfPlate = 15; |  | ||||||
|     let kBaseArmorClassRingMail = 14; |  | ||||||
|     let kBaseArmorClassChainMail = 16; |  | ||||||
|     let kBaseArmorClassSplintMail = 17; |  | ||||||
|     let kBaseArmorClassPlate = 18; |  | ||||||
|      |  | ||||||
|     // MARK: Basic Info |  | ||||||
|      |  | ||||||
|     var meta: String { |  | ||||||
|         get { |  | ||||||
|             // size type (subtype) alignment |  | ||||||
|             var parts: [String] = [] |  | ||||||
|              |  | ||||||
|             if (!(self.size?.isEmpty ?? false)) { |  | ||||||
|                 parts.append(self.size!) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (!(self.type?.isEmpty ?? false)) { |  | ||||||
|                 parts.append(self.type!) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (!(self.subtype?.isEmpty ?? false)) { |  | ||||||
|                 parts.append(String.init(format: "(%@)", arguments: [self.subtype!])) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (!(self.alignment?.isEmpty ?? false)) { |  | ||||||
|                 parts.append(self.alignment!) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             return parts.joined(separator: " ") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var sizeEnum: SizeType { |  | ||||||
|         get { |  | ||||||
|             return SizeType.init(rawValue: size ?? "") ?? .medium |  | ||||||
|         } |  | ||||||
|         set { |  | ||||||
|             size = newValue.rawValue |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var hitPoints: String { |  | ||||||
|         get { |  | ||||||
|             if (hasCustomHP) { |  | ||||||
|                 return customHP ?? ""; |  | ||||||
|             } else { |  | ||||||
|                 let dieSize = Double(Monster.hitDieForSize(sizeEnum)) |  | ||||||
|                 let conMod = Double(constitutionModifier) |  | ||||||
| //                let level1HP = Double(dieSize + conMod) |  | ||||||
|                 let level1HP = Double(dieSize/2.0 + conMod) |  | ||||||
|                 let extraLevels = Double(hitDice - 1) |  | ||||||
|                 let levelNHP = (dieSize + 1.0) / 2.0 + conMod |  | ||||||
|                 let extraLevelsHP = extraLevels * levelNHP |  | ||||||
|                 let hpTotal = Int(ceil(level1HP + extraLevelsHP)) |  | ||||||
|                 let conBonus = Int(conMod) * Int(hitDice) |  | ||||||
|                 return String(format: "%d (%dd%d%+d)", hpTotal, hitDice, Int(dieSize), conBonus) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var speed: String { |  | ||||||
|         get { |  | ||||||
|             if (hasCustomSpeed) { |  | ||||||
|                 return customSpeed ?? "" |  | ||||||
|             } else { |  | ||||||
|                 var parts: [String] = [] |  | ||||||
|                  |  | ||||||
|                 if (walkSpeed > 0) { |  | ||||||
|                     parts.append("\(walkSpeed) ft.") |  | ||||||
|                 } |  | ||||||
|                 if (burrowSpeed > 0) { |  | ||||||
|                     parts.append("burrow \(burrowSpeed) ft.") |  | ||||||
|                 } |  | ||||||
|                 if (climbSpeed > 0) { |  | ||||||
|                     parts.append("climb \(climbSpeed) ft.") |  | ||||||
|                 } |  | ||||||
|                 if (flySpeed > 0) { |  | ||||||
|                     parts.append("fly \(flySpeed) ft.\(canHover ? " (hover)": "")") |  | ||||||
|                 } |  | ||||||
|                 if (swimSpeed > 0) { |  | ||||||
|                     parts.append("swim \(swimSpeed) ft.") |  | ||||||
|                 } |  | ||||||
|                  |  | ||||||
|                 return parts.joined(separator: ", ") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     class func hitDieForSize(_ size: SizeType) -> Int { |  | ||||||
|         switch size { |  | ||||||
|             case .tiny: return 4 |  | ||||||
|             case .small: return 6 |  | ||||||
|             case .medium: return 8 |  | ||||||
|             case .large: return 10 |  | ||||||
|             case .huge: return 12 |  | ||||||
|             case .gargantuan: return 20 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // MARK: Ability Scores |  | ||||||
|     class func abilityModifierForScore(_ score: Int) -> Int { |  | ||||||
|         return Int(floor(Double((score - 10)) / 2.0)) |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     func abilityModifierForAbilityScore(_ abilityScore: AbilityScore) -> Int { |  | ||||||
|         switch abilityScore { |  | ||||||
|         case .strength: |  | ||||||
|             return strengthModifier; |  | ||||||
|         case .dexterity: |  | ||||||
|             return dexterityModifier |  | ||||||
|         case .constitution: |  | ||||||
|             return constitutionModifier |  | ||||||
|         case .intelligence: |  | ||||||
|             return intelligenceModifier |  | ||||||
|         case .wisdom: |  | ||||||
|             return wisdomModifier |  | ||||||
|         case .charisma: |  | ||||||
|             return charismaModifier |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var strengthModifier: Int { |  | ||||||
|         get { |  | ||||||
|             return Monster.abilityModifierForScore(Int(strengthScore)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var dexterityModifier: Int { |  | ||||||
|         get { |  | ||||||
|             return Monster.abilityModifierForScore(Int(dexterityScore)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var constitutionModifier: Int { |  | ||||||
|         get { |  | ||||||
|             return Monster.abilityModifierForScore(Int(constitutionScore)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var intelligenceModifier: Int { |  | ||||||
|         get { |  | ||||||
|             return Monster.abilityModifierForScore(Int(intelligenceScore)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var wisdomModifier: Int { |  | ||||||
|         get { |  | ||||||
|             return Monster.abilityModifierForScore(Int(wisdomScore)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var charismaModifier: Int { |  | ||||||
|         get { |  | ||||||
|             return Monster.abilityModifierForScore(Int(charismaScore)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var strengthDescription: String { |  | ||||||
|         get { |  | ||||||
|             return String(format: "%d (%+d)", strengthScore, strengthModifier) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var dexterityDescription: String { |  | ||||||
|         get { |  | ||||||
|             return String(format: "%d (%+d)", dexterityScore, dexterityModifier) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var constitutionDescription: String { |  | ||||||
|         get { |  | ||||||
|             return String(format: "%d (%+d)", constitutionScore, constitutionModifier) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var intelligenceDescription: String { |  | ||||||
|         get { |  | ||||||
|             return String(format: "%d (%+d)", intelligenceScore, intelligenceModifier) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var wisdomDescription: String { |  | ||||||
|         get { |  | ||||||
|             return String(format: "%d (%+d)", wisdomScore, wisdomModifier) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var charismaDescription: String { |  | ||||||
|         get { |  | ||||||
|             return String(format: "%d (%+d)", charismaScore, charismaModifier) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // MARK: Armor |     // MARK: Armor | ||||||
|      |      | ||||||
|     var armorTypeEnum: ArmorType { |     var armorTypeEnum: ArmorType { | ||||||
| @@ -237,79 +31,6 @@ public class Monster: NSManagedObject { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     var armorClassDescription: String { |  | ||||||
|         get { |  | ||||||
|             let hasShield = shieldBonus != 0 |  | ||||||
|             var armorClassTotal = 0 |  | ||||||
|             if (armorTypeEnum == ArmorType.none) { |  | ||||||
|                 // 10 + dexMod + 2 for shieldBonus "15" or "17 (shield)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(shieldBonus) |  | ||||||
|                 return  "\(armorClassTotal)\(hasShield ? " (shield)" : "")" |  | ||||||
|             } else if (armorTypeEnum == .naturalArmor) { |  | ||||||
|                 // 10 + dexMod + naturalArmorBonus + 2 for shieldBonus "16 (natural armor)" or "18 (natural armor, shield)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(naturalArmorBonus) + Int(shieldBonus) |  | ||||||
|                 return  "\(armorClassTotal) (natural armor\(hasShield ? " (shield)" : ""))" |  | ||||||
|             } else if (armorTypeEnum == .mageArmor) { |  | ||||||
|                 // 10 + dexMod + 2 for shield + 3 for mage armor "15 (18 with mage armor)" or 17 (shield, 20 with mage armor) |  | ||||||
|                 armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(shieldBonus) |  | ||||||
|                 let acWithMageArmor = kBaseArmorClassMageArmor + dexterityModifier + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (%@%d with mage armor)", armorClassTotal, (hasShield ? "shield, " : ""), acWithMageArmor) |  | ||||||
|             } else if (armorTypeEnum == .padded) { |  | ||||||
|                 // 11 + dexMod + 2 for shield "18 (padded armor, shield)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassPadded + dexterityModifier + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (padded%@)", armorClassTotal, (hasShield ? "shield, " : "")) |  | ||||||
|             } else if (armorTypeEnum == .leather) { |  | ||||||
|                 // 11 + dexMod + 2 for shield "18 (leather, shield)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassLeather + dexterityModifier + Int(shieldBonus) |  | ||||||
|                 return String(format:"%d (leather%@)", armorClassTotal, (hasShield ? "shield, " : "")) |  | ||||||
|             } else if (armorTypeEnum == .studdedLeather) { |  | ||||||
|                 // 12 + dexMod +2 for shield "17 (studded leather)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassStudded + dexterityModifier + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (studded leather%@)", armorClassTotal, (hasShield ? "shield, " : "")) |  | ||||||
|             } else if (armorTypeEnum == .hide) { |  | ||||||
|                 // 12 + Min(2, dexMod) + 2 for shield "12 (hide armor)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassHide + min(2, dexterityModifier) + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (hide%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .chainShirt) { |  | ||||||
|                 // 13 + Min(2, dexMod) + 2 for shield "12 (chain shirt)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassChainShirt + min(2, dexterityModifier) + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (chain shirt%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .scaleMail) { |  | ||||||
|                 // 14 + Min(2, dexMod) + 2 for shield "14 (scale mail)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassScaleMail + min(2, dexterityModifier) + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (scale mail%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .breastplate) { |  | ||||||
|                 // 14 + Min(2, dexMod) + 2 for shield "16 (breastplate)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassBreastplate + min(2, dexterityModifier) + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (breastplate%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .halfPlate) { |  | ||||||
|                 // 15 + Min(2, dexMod) + 2 for shield "17 (half plate)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassHalfPlate + min(2, dexterityModifier) + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (half plate%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .ringMail) { |  | ||||||
|                 // 14 + 2 for shield "14 (ring mail) |  | ||||||
|                 armorClassTotal = kBaseArmorClassRingMail + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (ring mail%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .chainMail) { |  | ||||||
|                 // 16 + 2 for shield "16 (chain mail)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassChainMail + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (chain mail%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .splintMail) { |  | ||||||
|                 // 17 + 2 for shield "17 (splint)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassSplintMail + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (splint%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .plateMail) { |  | ||||||
|                 // 18 + 2 for shield "18 (plate)" |  | ||||||
|                 armorClassTotal = kBaseArmorClassPlate + Int(shieldBonus) |  | ||||||
|                 return String(format: "%d (plate%@)", armorClassTotal, (hasShield ? ", shield" : "")) |  | ||||||
|             } else if (armorTypeEnum == .other) { |  | ||||||
|                 // pure string value shield check does nothing just copies the string from otherArmorDesc |  | ||||||
|                 return otherArmorDescription ?? ""; |  | ||||||
|             } else { |  | ||||||
|                 return "" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     // MARK: Challenge Rating / Proficiency Bonus |     // MARK: Challenge Rating / Proficiency Bonus | ||||||
|      |      | ||||||
|     var challengeRatingEnum: ChallengeRating { |     var challengeRatingEnum: ChallengeRating { | ||||||
| @@ -321,166 +42,9 @@ public class Monster: NSManagedObject { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     var proficiencyBonus: Int { |  | ||||||
|         switch challengeRatingEnum { |  | ||||||
|         case .custom: |  | ||||||
|             return Int(customProficiencyBonus) |  | ||||||
|         case .zero: |  | ||||||
|             fallthrough |  | ||||||
|         case .oneEighth: |  | ||||||
|             fallthrough |  | ||||||
|         case .oneQuarter: |  | ||||||
|             fallthrough |  | ||||||
|         case .oneHalf: |  | ||||||
|             fallthrough |  | ||||||
|         case .one: |  | ||||||
|             fallthrough |  | ||||||
|         case .two: |  | ||||||
|             fallthrough |  | ||||||
|         case .three: |  | ||||||
|             fallthrough |  | ||||||
|         case .four: |  | ||||||
|             return 2 |  | ||||||
|         case .five: |  | ||||||
|             fallthrough |  | ||||||
|         case .six: |  | ||||||
|             fallthrough |  | ||||||
|         case .seven: |  | ||||||
|             fallthrough |  | ||||||
|         case .eight: |  | ||||||
|             return 3 |  | ||||||
|         case .nine: |  | ||||||
|             fallthrough |  | ||||||
|         case .ten: |  | ||||||
|             fallthrough |  | ||||||
|         case .eleven: |  | ||||||
|             fallthrough |  | ||||||
|         case .twelve: |  | ||||||
|             return 4 |  | ||||||
|         case .thirteen: |  | ||||||
|             fallthrough |  | ||||||
|         case .fourteen: |  | ||||||
|             fallthrough |  | ||||||
|         case .fifteen: |  | ||||||
|             fallthrough |  | ||||||
|         case .sixteen: |  | ||||||
|             return 5 |  | ||||||
|         case .seventeen: |  | ||||||
|             fallthrough |  | ||||||
|         case .eighteen: |  | ||||||
|             fallthrough |  | ||||||
|         case .nineteen: |  | ||||||
|             fallthrough |  | ||||||
|         case .twenty: |  | ||||||
|             return 6 |  | ||||||
|         case .twentyOne: |  | ||||||
|             fallthrough |  | ||||||
|         case .twentyTwo: |  | ||||||
|             fallthrough |  | ||||||
|         case .twentyThree: |  | ||||||
|             fallthrough |  | ||||||
|         case .twentyFour: |  | ||||||
|             return 7 |  | ||||||
|         case .twentyFive: |  | ||||||
|             fallthrough |  | ||||||
|         case .twentySix: |  | ||||||
|             fallthrough |  | ||||||
|         case .twentySeven: |  | ||||||
|             fallthrough |  | ||||||
|         case .twentyEight: |  | ||||||
|             return 8 |  | ||||||
|         case .twentyNine: |  | ||||||
|             fallthrough |  | ||||||
|         case .thirty: |  | ||||||
|             return 9 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     func proficiencyBonusForType(_ profType: ProficiencyType) -> Int { |  | ||||||
|         switch profType { |  | ||||||
|         case .none: |  | ||||||
|             return 0 |  | ||||||
|         case .proficient: |  | ||||||
|             return proficiencyBonus |  | ||||||
|         case .expertise: |  | ||||||
|             return proficiencyBonus * 2 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // MARK: Saving Throws |     // MARK: Saving Throws | ||||||
|      |      | ||||||
|     var savingThrowsDescription: String { |  | ||||||
|         get { |  | ||||||
|             // TODO: port from objective-c |  | ||||||
|             var parts: [String] = [] |  | ||||||
|             var name: String |  | ||||||
|             var advantage: String |  | ||||||
|             var bonus: Int |  | ||||||
|              |  | ||||||
|             if (strengthSavingThrowAdvantageEnum != .none || strengthSavingThrowProficiencyEnum != .none) { |  | ||||||
|                 name = "Strength" |  | ||||||
|                 bonus = strengthModifier + proficiencyBonusForType(strengthSavingThrowProficiencyEnum) |  | ||||||
|                 advantage = Monster.advantageLabelStringForType(strengthSavingThrowAdvantageEnum) |  | ||||||
|                 if (!advantage.isEmpty) { |  | ||||||
|                     advantage = " " + advantage |  | ||||||
|                 } |  | ||||||
|                 parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (dexteritySavingThrowAdvantageEnum != .none || dexteritySavingThrowProficiencyEnum != .none) { |  | ||||||
|                 name = "Dexterity" |  | ||||||
|                 bonus = dexterityModifier + proficiencyBonusForType(dexteritySavingThrowProficiencyEnum) |  | ||||||
|                 advantage = Monster.advantageLabelStringForType(dexteritySavingThrowAdvantageEnum) |  | ||||||
|                 if (!advantage.isEmpty) { |  | ||||||
|                     advantage = " " + advantage |  | ||||||
|                 } |  | ||||||
|                 parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (constitutionSavingThrowAdvantageEnum != .none || constitutionSavingThrowProficiencyEnum != .none) { |  | ||||||
|                 name = "Constitution" |  | ||||||
|                 bonus = constitutionModifier + proficiencyBonusForType(constitutionSavingThrowProficiencyEnum) |  | ||||||
|                 advantage = Monster.advantageLabelStringForType(constitutionSavingThrowAdvantageEnum) |  | ||||||
|                 if (!advantage.isEmpty) { |  | ||||||
|                     advantage = " " + advantage |  | ||||||
|                 } |  | ||||||
|                 parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (intelligenceSavingThrowAdvantageEnum != .none || intelligenceSavingThrowProficiencyEnum != .none) { |  | ||||||
|                 name = "Intelligence" |  | ||||||
|                 bonus = intelligenceModifier + proficiencyBonusForType(intelligenceSavingThrowProficiencyEnum) |  | ||||||
|                 advantage = Monster.advantageLabelStringForType(intelligenceSavingThrowAdvantageEnum) |  | ||||||
|                 if (!advantage.isEmpty) { |  | ||||||
|                     advantage = " " + advantage |  | ||||||
|                 } |  | ||||||
|                 parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (wisdomSavingThrowAdvantageEnum != .none || wisdomSavingThrowProficiencyEnum != .none) { |  | ||||||
|                 name = "Wisdom" |  | ||||||
|                 bonus = wisdomModifier + proficiencyBonusForType(wisdomSavingThrowProficiencyEnum) |  | ||||||
|                 advantage = Monster.advantageLabelStringForType(wisdomSavingThrowAdvantageEnum) |  | ||||||
|                 if (!advantage.isEmpty) { |  | ||||||
|                     advantage = " " + advantage |  | ||||||
|                 } |  | ||||||
|                 parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (charismaSavingThrowAdvantageEnum != .none || charismaSavingThrowProficiencyEnum != .none) { |  | ||||||
|                 name = "Charisma" |  | ||||||
|                 bonus = charismaModifier + proficiencyBonusForType(charismaSavingThrowProficiencyEnum) |  | ||||||
|                 advantage = Monster.advantageLabelStringForType(charismaSavingThrowAdvantageEnum) |  | ||||||
|                 if (!advantage.isEmpty) { |  | ||||||
|                     advantage = " " + advantage |  | ||||||
|                 } |  | ||||||
|                 parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             return parts.joined(separator: ", ") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var strengthSavingThrowProficiencyEnum: ProficiencyType { |     var strengthSavingThrowProficiencyEnum: ProficiencyType { | ||||||
|         get { |         get { | ||||||
|             return ProficiencyType.init(rawValue: strengthSavingThrowProficiency ?? "") ?? .none |             return ProficiencyType.init(rawValue: strengthSavingThrowProficiency ?? "") ?? .none | ||||||
| @@ -589,175 +153,6 @@ public class Monster: NSManagedObject { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |  | ||||||
|     // MARK: Misc Helpers |  | ||||||
|  |  | ||||||
|     class func advantageLabelStringForType(_ advType: AdvantageType) -> String { |  | ||||||
|         switch advType { |  | ||||||
|         case .none: |  | ||||||
|             return "" |  | ||||||
|         case .advantage: |  | ||||||
|             return "(A)" |  | ||||||
|         case .disadvantage: |  | ||||||
|             return "(D)" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // MARK: Skills |  | ||||||
|      |  | ||||||
|     var skillsDescription: String { |  | ||||||
|         get { |  | ||||||
|             let sortedSkills = self.skillsArray.sorted {$0.name ?? "" < $1.name ?? ""} |  | ||||||
|             return sortedSkills.reduce("") { |  | ||||||
|                 if $0 == "" { |  | ||||||
|                     return $1.skillDescription |  | ||||||
|                 } else { |  | ||||||
|                     return $0 + ", " + $1.skillDescription |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var skillsArray: [Skill] { |  | ||||||
|         let set = skills as? Set<Skill> ?? [] |  | ||||||
|         return set.sorted { |  | ||||||
|             $0.wrappedName < $1.wrappedName |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // MARK: Immunities, Resistances, and Vulnerabilities |  | ||||||
|      |  | ||||||
|     var damageVulnerabilitiesDescription: String { |  | ||||||
|         get { |  | ||||||
|             // TODO: sort "bludgeoning, piercing, and slashing from nonmagical attacks" to the end and use ; as a separator before it. |  | ||||||
|             let sortedVulnerabilities = self.damageVulnerabilities?.sorted() ?? [] |  | ||||||
|             return StringHelper.oxfordJoin(sortedVulnerabilities) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var damageResistancesDescription: String { |  | ||||||
|         get { |  | ||||||
|             // TODO: sort "bludgeoning, piercing, and slashing from nonmagical attacks" to the end and use ; as a separator before it. |  | ||||||
|             let sortedResistances = self.damageResistances?.sorted() ?? [] |  | ||||||
|             return StringHelper.oxfordJoin(sortedResistances) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|          |  | ||||||
|     var damageImmunitiesDescription: String { |  | ||||||
|         get { |  | ||||||
|             // TODO: sort "bludgeoning, piercing, and slashing from nonmagical attacks" to the end and use ; as a separator before it. |  | ||||||
|             let sortedImmunities = self.damageImmunities?.sorted() ?? [] |  | ||||||
|             return StringHelper.oxfordJoin(sortedImmunities) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var conditionImmunitiesDescription: String { |  | ||||||
|         get { |  | ||||||
|             let sortedImmunities = self.conditionImmunities?.sorted() ?? [] |  | ||||||
|             return StringHelper.oxfordJoin(sortedImmunities) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // MARK: OTHER |  | ||||||
|      |  | ||||||
|     var passivePerception: Int { |  | ||||||
|         get { |  | ||||||
|             let perceptionSkill = skillsArray.first(where: { |  | ||||||
|                 StringHelper.safeEqualsIgnoreCase($0.name, "Perception") |  | ||||||
|             }) |  | ||||||
|             if (perceptionSkill == nil) { |  | ||||||
|                 return 10 + wisdomModifier |  | ||||||
|             } else if (perceptionSkill?.wrappedProficiency == ProficiencyType.expertise) { |  | ||||||
|                 return 10 + wisdomModifier + proficiencyBonus + proficiencyBonus |  | ||||||
|             } else if (perceptionSkill?.wrappedProficiency == ProficiencyType.proficient) { |  | ||||||
|                 return 10 + wisdomModifier + proficiencyBonus |  | ||||||
|             } else { |  | ||||||
|                 return 10 + wisdomModifier |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var sensesDescription: String { |  | ||||||
|         get { |  | ||||||
|             var modifiedSenses = self.senses?.sorted() ?? [] |  | ||||||
|             let hasPassivePerceptionSense = modifiedSenses.contains(where: { |  | ||||||
|                 $0.starts(with: "passive Perception") |  | ||||||
|             }) |  | ||||||
|             if (!hasPassivePerceptionSense) { |  | ||||||
|                 let calculatedPassivePerception = String(format: "passive Perception %d", passivePerception) |  | ||||||
|                 modifiedSenses.append(calculatedPassivePerception) |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             return modifiedSenses.sorted().joined(separator: ", ") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var languagesArray: [String] { |  | ||||||
|         get { |  | ||||||
|             return ["Common", "Goblin"] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var languagesDescription: String { |  | ||||||
|         get { |  | ||||||
|             let spokenLanguages = (self.languages ?? []) |  | ||||||
|                 .filter({ $0.speaks }) |  | ||||||
|                 .map({$0.name}) |  | ||||||
|                 .sorted() |  | ||||||
|             let understoodLanguages = (self.languages ?? []) |  | ||||||
|                 .filter({ !$0.speaks }) |  | ||||||
|                 .map({$0.name}) |  | ||||||
|                 .sorted() |  | ||||||
|  |  | ||||||
|             let understandsButText = understandsBut?.isEmpty ?? false |  | ||||||
|                 ? "" |  | ||||||
|                 : String(format: " but %@", understandsBut!) |  | ||||||
|              |  | ||||||
|             let telepathyText = telepathy > 0 |  | ||||||
|                 ? String(format: ", telepathy %d ft.", telepathy) |  | ||||||
|                 : "" |  | ||||||
|              |  | ||||||
|             if (spokenLanguages.count > 0) { |  | ||||||
|                 if (understoodLanguages.count > 0) { |  | ||||||
|                     return String( |  | ||||||
|                         format:"%@ and understands %@%@%@", |  | ||||||
|                         StringHelper.oxfordJoin(spokenLanguages), |  | ||||||
|                         StringHelper.oxfordJoin(understoodLanguages), |  | ||||||
|                         understandsButText, |  | ||||||
|                         telepathyText) |  | ||||||
|                 } else { |  | ||||||
|                   return String( |  | ||||||
|                     format: "%@%@%@", |  | ||||||
|                     StringHelper.oxfordJoin(spokenLanguages), |  | ||||||
|                     understandsButText, |  | ||||||
|                     telepathyText) |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 if (understoodLanguages.count > 0) { |  | ||||||
|                     return String( |  | ||||||
|                       format: "understands %@%@%@", |  | ||||||
|                       StringHelper.oxfordJoin(understoodLanguages), |  | ||||||
|                       understandsButText, |  | ||||||
|                       telepathyText) |  | ||||||
|                 } else if (telepathy > 0){ |  | ||||||
|                     return String(format: "telepathy %d ft.", telepathy) |  | ||||||
|                 } else { |  | ||||||
|                     return "" |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var challengeRatingDescription: String { |  | ||||||
|         get { |  | ||||||
|             if (challengeRatingEnum != .custom) { |  | ||||||
|                 return challengeRatingEnum.displayName |  | ||||||
|             } else { |  | ||||||
|                 return customChallengeRating ?? "" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // MARK: End |     // MARK: End | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										223
									
								
								MonsterCards/Models/MonsterViewModel+CoreData.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								MonsterCards/Models/MonsterViewModel+CoreData.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | |||||||
|  | // | ||||||
|  | //  MonsterViewModel+CoreData.swift | ||||||
|  | //  MonsterCards | ||||||
|  | // | ||||||
|  | //  Created by Tom Hicks on 4/7/21. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import Foundation | ||||||
|  | import CoreData | ||||||
|  |  | ||||||
|  | extension MonsterViewModel { | ||||||
|  |     convenience init(_ rawMonster: Monster?) { | ||||||
|  |         self.init() | ||||||
|  |  | ||||||
|  |         // Call the copy constructor | ||||||
|  |         if (rawMonster != nil) { | ||||||
|  |             self.copyFromMonster(monster: rawMonster!) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func copyFromMonster(monster: Monster) { | ||||||
|  |         self.name = monster.name ?? "" | ||||||
|  |         self.size = monster.size ?? "" | ||||||
|  |         self.type = monster.type ?? "" | ||||||
|  |         self.subType = monster.subtype ?? "" | ||||||
|  |         self.alignment = monster.alignment ?? "" | ||||||
|  |         self.hitDice = monster.hitDice | ||||||
|  |         self.hasCustomHP = monster.hasCustomHP | ||||||
|  |         self.customHP = monster.customHP ?? "" | ||||||
|  |         self.armorType = monster.armorTypeEnum | ||||||
|  |         self.hasShield = monster.hasShield | ||||||
|  |         self.naturalArmorBonus = monster.naturalArmorBonus | ||||||
|  |         self.customArmor = monster.customArmor ?? "" | ||||||
|  |         self.walkSpeed = monster.walkSpeed | ||||||
|  |         self.burrowSpeed = monster.burrowSpeed | ||||||
|  |         self.climbSpeed = monster.climbSpeed | ||||||
|  |         self.flySpeed = monster.flySpeed | ||||||
|  |         self.canHover = monster.canHover | ||||||
|  |         self.swimSpeed = monster.swimSpeed | ||||||
|  |         self.hasCustomSpeed = monster.hasCustomSpeed | ||||||
|  |         self.customSpeed = monster.customSpeed ?? "" | ||||||
|  |         self.strengthScore = monster.strengthScore | ||||||
|  |         self.strengthSavingThrowAdvantage = monster.strengthSavingThrowAdvantageEnum | ||||||
|  |         self.strengthSavingThrowProficiency = monster.strengthSavingThrowProficiencyEnum | ||||||
|  |         self.dexterityScore = monster.dexterityScore | ||||||
|  |         self.dexteritySavingThrowAdvantage = monster.dexteritySavingThrowAdvantageEnum | ||||||
|  |         self.dexteritySavingThrowProficiency = monster.dexteritySavingThrowProficiencyEnum | ||||||
|  |         self.constitutionScore = monster.constitutionScore | ||||||
|  |         self.constitutionSavingThrowAdvantage = monster.constitutionSavingThrowAdvantageEnum | ||||||
|  |         self.constitutionSavingThrowProficiency = monster.constitutionSavingThrowProficiencyEnum | ||||||
|  |         self.intelligenceScore = monster.intelligenceScore | ||||||
|  |         self.intelligenceSavingThrowAdvantage = monster.intelligenceSavingThrowAdvantageEnum | ||||||
|  |         self.intelligenceSavingThrowProficiency = monster.intelligenceSavingThrowProficiencyEnum | ||||||
|  |         self.wisdomScore = monster.wisdomScore | ||||||
|  |         self.wisdomSavingThrowAdvantage = monster.wisdomSavingThrowAdvantageEnum | ||||||
|  |         self.wisdomSavingThrowProficiency = monster.wisdomSavingThrowProficiencyEnum | ||||||
|  |         self.charismaScore = monster.charismaScore | ||||||
|  |         self.charismaSavingThrowAdvantage = monster.charismaSavingThrowAdvantageEnum | ||||||
|  |         self.charismaSavingThrowProficiency = monster.charismaSavingThrowProficiencyEnum | ||||||
|  |         self.telepathy = monster.telepathy | ||||||
|  |         self.understandsBut = monster.understandsBut ?? "" | ||||||
|  |         self.challengeRating = monster.challengeRatingEnum | ||||||
|  |         self.customChallengeRating = monster.customChallengeRating ?? "" | ||||||
|  |         self.customProficiencyBonus = monster.customProficiencyBonus | ||||||
|  |         self.isBlind = monster.isBlind | ||||||
|  |          | ||||||
|  |         self.skills = (monster.skills?.allObjects.map { | ||||||
|  |             let skill = $0 as! Skill | ||||||
|  |             return SkillViewModel( | ||||||
|  |                 skill.name ?? "", | ||||||
|  |                 AbilityScore(rawValue: skill.abilityScoreName ?? "") ?? .dexterity, | ||||||
|  |                 ProficiencyType(rawValue: skill.proficiency ?? "") ?? .none, | ||||||
|  |                 AdvantageType(rawValue: skill.advantage ?? "") ?? .none | ||||||
|  |             ) | ||||||
|  |         })!.sorted() | ||||||
|  |          | ||||||
|  |         //            self.name = rawSkill!.name ?? "" | ||||||
|  |         //            self.abilityScore = AbilityScore(rawValue: rawSkill!.abilityScoreName ?? "") ?? .strength | ||||||
|  |         //            self.proficiency = ProficiencyType(rawValue: rawSkill!.proficiency ?? "") ?? .none | ||||||
|  |         //            self.advantage = AdvantageType(rawValue: rawSkill!.advantage ?? "") ?? .none | ||||||
|  |  | ||||||
|  |      | ||||||
|  |         self.damageImmunities = (monster.damageImmunities ?? []) | ||||||
|  |             .map {StringViewModel($0)} | ||||||
|  |             .sorted() | ||||||
|  |  | ||||||
|  |         self.damageResistances = (monster.damageResistances ?? []) | ||||||
|  |             .map {StringViewModel($0)} | ||||||
|  |             .sorted() | ||||||
|  |          | ||||||
|  |         self.damageVulnerabilities = (monster.damageVulnerabilities ?? []) | ||||||
|  |             .map {StringViewModel($0)} | ||||||
|  |             .sorted() | ||||||
|  |          | ||||||
|  |         self.conditionImmunities = (monster.conditionImmunities ?? []) | ||||||
|  |             .map {StringViewModel($0)} | ||||||
|  |             .sorted() | ||||||
|  |          | ||||||
|  |         self.senses = (monster.senses ?? []) | ||||||
|  |             .map {StringViewModel($0)} | ||||||
|  |             .sorted() | ||||||
|  |          | ||||||
|  |         self.languages = (monster.languages ?? []) | ||||||
|  |             .map {LanguageViewModel($0.name, $0.speaks)} | ||||||
|  |             .sorted() | ||||||
|  |          | ||||||
|  |         // These are manually sorted in the UI | ||||||
|  |         self.abilities = (monster.abilities ?? []) | ||||||
|  |             .map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         self.actions = (monster.actions ?? []) | ||||||
|  |             .map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         self.legendaryActions = (monster.legendaryActions ?? []) | ||||||
|  |             .map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         self.lairActions = (monster.lairActions ?? []) | ||||||
|  |             .map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |  | ||||||
|  |         self.regionalActions = (monster.regionalActions ?? []) | ||||||
|  |             .map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |  | ||||||
|  |         self.reactions = (monster.reactions ?? []) | ||||||
|  |             .map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |  | ||||||
|  |         // Private fields | ||||||
|  |          | ||||||
|  |         self.shieldBonus = Int(monster.shieldBonus) | ||||||
|  |         self.otherArmorDescription = monster.otherArmorDescription ?? "" | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func copyToMonster(monster: Monster) { | ||||||
|  |         monster.name = name | ||||||
|  |         monster.size = size | ||||||
|  |         monster.type = type | ||||||
|  |         monster.subtype = subType | ||||||
|  |         monster.alignment = alignment | ||||||
|  |         monster.hitDice = hitDice | ||||||
|  |         monster.hasCustomHP = hasCustomHP | ||||||
|  |         monster.customHP = customHP | ||||||
|  |         monster.armorTypeEnum = armorType | ||||||
|  |         monster.hasShield = hasShield | ||||||
|  |         monster.naturalArmorBonus = naturalArmorBonus | ||||||
|  |         monster.customArmor = customArmor | ||||||
|  |         monster.walkSpeed = walkSpeed | ||||||
|  |         monster.burrowSpeed = burrowSpeed | ||||||
|  |         monster.climbSpeed = climbSpeed | ||||||
|  |         monster.flySpeed = flySpeed | ||||||
|  |         monster.canHover = canHover | ||||||
|  |         monster.swimSpeed = swimSpeed | ||||||
|  |         monster.hasCustomSpeed = hasCustomSpeed | ||||||
|  |         monster.customSpeed = customSpeed | ||||||
|  |         monster.strengthScore = strengthScore | ||||||
|  |         monster.strengthSavingThrowAdvantageEnum = strengthSavingThrowAdvantage | ||||||
|  |         monster.strengthSavingThrowProficiencyEnum = strengthSavingThrowProficiency | ||||||
|  |         monster.dexterityScore = dexterityScore | ||||||
|  |         monster.dexteritySavingThrowAdvantageEnum = dexteritySavingThrowAdvantage | ||||||
|  |         monster.dexteritySavingThrowProficiencyEnum = dexteritySavingThrowProficiency | ||||||
|  |         monster.constitutionScore = constitutionScore | ||||||
|  |         monster.constitutionSavingThrowAdvantageEnum = constitutionSavingThrowAdvantage | ||||||
|  |         monster.constitutionSavingThrowProficiencyEnum = constitutionSavingThrowProficiency | ||||||
|  |         monster.intelligenceScore = intelligenceScore | ||||||
|  |         monster.intelligenceSavingThrowAdvantageEnum = intelligenceSavingThrowAdvantage | ||||||
|  |         monster.intelligenceSavingThrowProficiencyEnum = intelligenceSavingThrowProficiency | ||||||
|  |         monster.wisdomScore = wisdomScore | ||||||
|  |         monster.wisdomSavingThrowAdvantageEnum = wisdomSavingThrowAdvantage | ||||||
|  |         monster.wisdomSavingThrowProficiencyEnum = wisdomSavingThrowProficiency | ||||||
|  |         monster.charismaScore = charismaScore | ||||||
|  |         monster.charismaSavingThrowAdvantageEnum = charismaSavingThrowAdvantage | ||||||
|  |         monster.charismaSavingThrowProficiencyEnum = charismaSavingThrowProficiency | ||||||
|  |         monster.telepathy = telepathy | ||||||
|  |         monster.understandsBut = understandsBut | ||||||
|  |         monster.challengeRatingEnum = challengeRating | ||||||
|  |         monster.customChallengeRating = customChallengeRating | ||||||
|  |         monster.customProficiencyBonus = customProficiencyBonus | ||||||
|  |         monster.isBlind = isBlind | ||||||
|  |  | ||||||
|  |         // Remove missing skills from raw monster | ||||||
|  |         monster.skills?.forEach {s in | ||||||
|  |             let skill = s as! Skill | ||||||
|  |             let skillVM = skills.first { $0.isEqualTo(rawSkill: skill) } | ||||||
|  |             if (skillVM != nil) { | ||||||
|  |                 skillVM!.copyToSkill(skill: skill) | ||||||
|  |             } else { | ||||||
|  |                 monster.removeFromSkills(skill) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         // Add new skills to raw monster | ||||||
|  |         skills.forEach {skillVM in | ||||||
|  |             if (!(monster.skills?.contains( | ||||||
|  |                     where: { | ||||||
|  |                         skillVM.isEqualTo(rawSkill: $0 as? Skill) | ||||||
|  |                     }) ?? true)){ | ||||||
|  |                 monster.addToSkills(skillVM.buildRawSkill(context: monster.managedObjectContext)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         monster.conditionImmunities = conditionImmunities.map {$0.name} | ||||||
|  |         monster.damageImmunities = damageImmunities.map {$0.name} | ||||||
|  |         monster.damageResistances = damageResistances.map {$0.name} | ||||||
|  |         monster.damageVulnerabilities = damageVulnerabilities.map {$0.name} | ||||||
|  |         monster.senses = senses.map {$0.name} | ||||||
|  |          | ||||||
|  |         // This is necessary so core data sees the language objects as changed. Without it they won't be persisted. | ||||||
|  |         monster.languages = languages.map {LanguageViewModel($0.name, $0.speaks)} | ||||||
|  |          | ||||||
|  |         monster.abilities = abilities.map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         monster.actions = actions.map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         monster.legendaryActions = legendaryActions.map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         monster.lairActions = lairActions.map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         monster.regionalActions = regionalActions.map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         monster.reactions = reactions.map {AbilityViewModel($0.name, $0.abilityDescription)} | ||||||
|  |          | ||||||
|  |         monster.shieldBonus = Int64(shieldBonus) | ||||||
|  |         monster.otherArmorDescription = otherArmorDescription | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -71,9 +71,8 @@ class MonsterViewModel: ObservableObject { | |||||||
|     @Published var regionalActions: [AbilityViewModel] |     @Published var regionalActions: [AbilityViewModel] | ||||||
|     @Published var reactions: [AbilityViewModel] |     @Published var reactions: [AbilityViewModel] | ||||||
|     @Published var isBlind: Bool |     @Published var isBlind: Bool | ||||||
|      |     @Published var shieldBonus: Int | ||||||
|     private var shieldBonus: Int |     @Published var otherArmorDescription: String | ||||||
|     private var otherArmorDescription: String |  | ||||||
|      |      | ||||||
|     let kBaseArmorClassUnarmored = 10; |     let kBaseArmorClassUnarmored = 10; | ||||||
|     let kBaseArmorClassMageArmor = 13; |     let kBaseArmorClassMageArmor = 13; | ||||||
| @@ -90,7 +89,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|     let kBaseArmorClassSplintMail = 17; |     let kBaseArmorClassSplintMail = 17; | ||||||
|     let kBaseArmorClassPlate = 18; |     let kBaseArmorClassPlate = 18; | ||||||
|      |      | ||||||
|     init(_ rawMonster: Monster? = nil) { |     init() { | ||||||
|         self.name = "" |         self.name = "" | ||||||
|         self.size = "" |         self.size = "" | ||||||
|         self.type = "" |         self.type = "" | ||||||
| @@ -152,199 +151,6 @@ class MonsterViewModel: ObservableObject { | |||||||
|         // Private properties |         // Private properties | ||||||
|         self.shieldBonus = 0 |         self.shieldBonus = 0 | ||||||
|         self.otherArmorDescription = "" |         self.otherArmorDescription = "" | ||||||
|  |  | ||||||
|         // Call the copy constructor |  | ||||||
|         if (rawMonster != nil) { |  | ||||||
|             self.copyFromMonster(monster: rawMonster!) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     func copyFromMonster(monster: Monster) { |  | ||||||
|         self.name = monster.name ?? "" |  | ||||||
|         self.size = monster.size ?? "" |  | ||||||
|         self.type = monster.type ?? "" |  | ||||||
|         self.subType = monster.subtype ?? "" |  | ||||||
|         self.alignment = monster.alignment ?? "" |  | ||||||
|         self.hitDice = monster.hitDice |  | ||||||
|         self.hasCustomHP = monster.hasCustomHP |  | ||||||
|         self.customHP = monster.customHP ?? "" |  | ||||||
|         self.armorType = monster.armorTypeEnum |  | ||||||
|         self.hasShield = monster.hasShield |  | ||||||
|         self.naturalArmorBonus = monster.naturalArmorBonus |  | ||||||
|         self.customArmor = monster.customArmor ?? "" |  | ||||||
|         self.walkSpeed = monster.walkSpeed |  | ||||||
|         self.burrowSpeed = monster.burrowSpeed |  | ||||||
|         self.climbSpeed = monster.climbSpeed |  | ||||||
|         self.flySpeed = monster.flySpeed |  | ||||||
|         self.canHover = monster.canHover |  | ||||||
|         self.swimSpeed = monster.swimSpeed |  | ||||||
|         self.hasCustomSpeed = monster.hasCustomSpeed |  | ||||||
|         self.customSpeed = monster.customSpeed ?? "" |  | ||||||
|         self.strengthScore = monster.strengthScore |  | ||||||
|         self.strengthSavingThrowAdvantage = monster.strengthSavingThrowAdvantageEnum |  | ||||||
|         self.strengthSavingThrowProficiency = monster.strengthSavingThrowProficiencyEnum |  | ||||||
|         self.dexterityScore = monster.dexterityScore |  | ||||||
|         self.dexteritySavingThrowAdvantage = monster.dexteritySavingThrowAdvantageEnum |  | ||||||
|         self.dexteritySavingThrowProficiency = monster.dexteritySavingThrowProficiencyEnum |  | ||||||
|         self.constitutionScore = monster.constitutionScore |  | ||||||
|         self.constitutionSavingThrowAdvantage = monster.constitutionSavingThrowAdvantageEnum |  | ||||||
|         self.constitutionSavingThrowProficiency = monster.constitutionSavingThrowProficiencyEnum |  | ||||||
|         self.intelligenceScore = monster.intelligenceScore |  | ||||||
|         self.intelligenceSavingThrowAdvantage = monster.intelligenceSavingThrowAdvantageEnum |  | ||||||
|         self.intelligenceSavingThrowProficiency = monster.intelligenceSavingThrowProficiencyEnum |  | ||||||
|         self.wisdomScore = monster.wisdomScore |  | ||||||
|         self.wisdomSavingThrowAdvantage = monster.wisdomSavingThrowAdvantageEnum |  | ||||||
|         self.wisdomSavingThrowProficiency = monster.wisdomSavingThrowProficiencyEnum |  | ||||||
|         self.charismaScore = monster.charismaScore |  | ||||||
|         self.charismaSavingThrowAdvantage = monster.charismaSavingThrowAdvantageEnum |  | ||||||
|         self.charismaSavingThrowProficiency = monster.charismaSavingThrowProficiencyEnum |  | ||||||
|         self.telepathy = monster.telepathy |  | ||||||
|         self.understandsBut = monster.understandsBut ?? "" |  | ||||||
|         self.challengeRating = monster.challengeRatingEnum |  | ||||||
|         self.customChallengeRating = monster.customChallengeRating ?? "" |  | ||||||
|         self.customProficiencyBonus = monster.customProficiencyBonus |  | ||||||
|         self.isBlind = monster.isBlind |  | ||||||
|          |  | ||||||
|         self.skills = (monster.skills?.allObjects.map {SkillViewModel(($0 as! Skill))})!.sorted() |  | ||||||
|      |  | ||||||
|         self.damageImmunities = (monster.damageImmunities ?? []) |  | ||||||
|             .map {StringViewModel($0)} |  | ||||||
|             .sorted() |  | ||||||
|  |  | ||||||
|         self.damageResistances = (monster.damageResistances ?? []) |  | ||||||
|             .map {StringViewModel($0)} |  | ||||||
|             .sorted() |  | ||||||
|          |  | ||||||
|         self.damageVulnerabilities = (monster.damageVulnerabilities ?? []) |  | ||||||
|             .map {StringViewModel($0)} |  | ||||||
|             .sorted() |  | ||||||
|          |  | ||||||
|         self.conditionImmunities = (monster.conditionImmunities ?? []) |  | ||||||
|             .map {StringViewModel($0)} |  | ||||||
|             .sorted() |  | ||||||
|          |  | ||||||
|         self.senses = (monster.senses ?? []) |  | ||||||
|             .map {StringViewModel($0)} |  | ||||||
|             .sorted() |  | ||||||
|          |  | ||||||
|         self.languages = (monster.languages ?? []) |  | ||||||
|             .map {LanguageViewModel($0.name, $0.speaks)} |  | ||||||
|             .sorted() |  | ||||||
|          |  | ||||||
|         // These are manually sorted in the UI |  | ||||||
|         self.abilities = (monster.abilities ?? []) |  | ||||||
|             .map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         self.actions = (monster.actions ?? []) |  | ||||||
|             .map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         self.legendaryActions = (monster.legendaryActions ?? []) |  | ||||||
|             .map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         self.lairActions = (monster.lairActions ?? []) |  | ||||||
|             .map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|  |  | ||||||
|         self.regionalActions = (monster.regionalActions ?? []) |  | ||||||
|             .map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|  |  | ||||||
|         self.reactions = (monster.reactions ?? []) |  | ||||||
|             .map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|  |  | ||||||
|         // Private fields |  | ||||||
|          |  | ||||||
|         self.shieldBonus = Int(monster.shieldBonus) |  | ||||||
|         self.otherArmorDescription = monster.otherArmorDescription ?? "" |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     func copyToMonster(monster: Monster) { |  | ||||||
|         monster.name = name |  | ||||||
|         monster.size = size |  | ||||||
|         monster.type = type |  | ||||||
|         monster.subtype = subType |  | ||||||
|         monster.alignment = alignment |  | ||||||
|         monster.hitDice = hitDice |  | ||||||
|         monster.hasCustomHP = hasCustomHP |  | ||||||
|         monster.customHP = customHP |  | ||||||
|         monster.armorTypeEnum = armorType |  | ||||||
|         monster.hasShield = hasShield |  | ||||||
|         monster.naturalArmorBonus = naturalArmorBonus |  | ||||||
|         monster.customArmor = customArmor |  | ||||||
|         monster.walkSpeed = walkSpeed |  | ||||||
|         monster.burrowSpeed = burrowSpeed |  | ||||||
|         monster.climbSpeed = climbSpeed |  | ||||||
|         monster.flySpeed = flySpeed |  | ||||||
|         monster.canHover = canHover |  | ||||||
|         monster.swimSpeed = swimSpeed |  | ||||||
|         monster.hasCustomSpeed = hasCustomSpeed |  | ||||||
|         monster.customSpeed = customSpeed |  | ||||||
|         monster.strengthScore = strengthScore |  | ||||||
|         monster.strengthSavingThrowAdvantageEnum = strengthSavingThrowAdvantage |  | ||||||
|         monster.strengthSavingThrowProficiencyEnum = strengthSavingThrowProficiency |  | ||||||
|         monster.dexterityScore = dexterityScore |  | ||||||
|         monster.dexteritySavingThrowAdvantageEnum = dexteritySavingThrowAdvantage |  | ||||||
|         monster.dexteritySavingThrowProficiencyEnum = dexteritySavingThrowProficiency |  | ||||||
|         monster.constitutionScore = constitutionScore |  | ||||||
|         monster.constitutionSavingThrowAdvantageEnum = constitutionSavingThrowAdvantage |  | ||||||
|         monster.constitutionSavingThrowProficiencyEnum = constitutionSavingThrowProficiency |  | ||||||
|         monster.intelligenceScore = intelligenceScore |  | ||||||
|         monster.intelligenceSavingThrowAdvantageEnum = intelligenceSavingThrowAdvantage |  | ||||||
|         monster.intelligenceSavingThrowProficiencyEnum = intelligenceSavingThrowProficiency |  | ||||||
|         monster.wisdomScore = wisdomScore |  | ||||||
|         monster.wisdomSavingThrowAdvantageEnum = wisdomSavingThrowAdvantage |  | ||||||
|         monster.wisdomSavingThrowProficiencyEnum = wisdomSavingThrowProficiency |  | ||||||
|         monster.charismaScore = charismaScore |  | ||||||
|         monster.charismaSavingThrowAdvantageEnum = charismaSavingThrowAdvantage |  | ||||||
|         monster.charismaSavingThrowProficiencyEnum = charismaSavingThrowProficiency |  | ||||||
|         monster.telepathy = telepathy |  | ||||||
|         monster.understandsBut = understandsBut |  | ||||||
|         monster.challengeRatingEnum = challengeRating |  | ||||||
|         monster.customChallengeRating = customChallengeRating |  | ||||||
|         monster.customProficiencyBonus = customProficiencyBonus |  | ||||||
|         monster.isBlind = isBlind |  | ||||||
|  |  | ||||||
|         // Remove missing skills from raw monster |  | ||||||
|         monster.skills?.forEach {s in |  | ||||||
|             let skill = s as! Skill |  | ||||||
|             let skillVM = skills.first { $0.isEqualTo(rawSkill: skill) } |  | ||||||
|             if (skillVM != nil) { |  | ||||||
|                 skillVM!.copyToSkill(skill: skill) |  | ||||||
|             } else { |  | ||||||
|                 monster.removeFromSkills(skill) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         // Add new skills to raw monster |  | ||||||
|         skills.forEach {skillVM in |  | ||||||
|             if (!(monster.skills?.contains( |  | ||||||
|                     where: { |  | ||||||
|                         skillVM.isEqualTo(rawSkill: $0 as? Skill) |  | ||||||
|                     }) ?? true)){ |  | ||||||
|                 monster.addToSkills(skillVM.buildRawSkill(context: monster.managedObjectContext)) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         monster.conditionImmunities = conditionImmunities.map {$0.name} |  | ||||||
|         monster.damageImmunities = damageImmunities.map {$0.name} |  | ||||||
|         monster.damageResistances = damageResistances.map {$0.name} |  | ||||||
|         monster.damageVulnerabilities = damageVulnerabilities.map {$0.name} |  | ||||||
|         monster.senses = senses.map {$0.name} |  | ||||||
|          |  | ||||||
|         // This is necessary so core data sees the language objects as changed. Without it they won't be persisted. |  | ||||||
|         monster.languages = languages.map {LanguageViewModel($0.name, $0.speaks)} |  | ||||||
|          |  | ||||||
|         monster.abilities = abilities.map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         monster.actions = actions.map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         monster.legendaryActions = legendaryActions.map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         monster.lairActions = lairActions.map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         monster.regionalActions = regionalActions.map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         monster.reactions = reactions.map {AbilityViewModel($0.name, $0.abilityDescription)} |  | ||||||
|          |  | ||||||
|         monster.shieldBonus = Int64(shieldBonus) |  | ||||||
|         monster.otherArmorDescription = otherArmorDescription |  | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // MARK: Basic Info |     // MARK: Basic Info | ||||||
| @@ -388,7 +194,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|             if (hasCustomHP) { |             if (hasCustomHP) { | ||||||
|                 return customHP; |                 return customHP; | ||||||
|             } else { |             } else { | ||||||
|                 let dieSize = Double(Monster.hitDieForSize(sizeEnum)) |                 let dieSize = Double(MonsterViewModel.hitDieForSize(sizeEnum)) | ||||||
|                 let conMod = Double(constitutionModifier) |                 let conMod = Double(constitutionModifier) | ||||||
| //                let level1HP = Double(dieSize + conMod) | //                let level1HP = Double(dieSize + conMod) | ||||||
|                 let level1HP = Double(dieSize/2.0 + conMod) |                 let level1HP = Double(dieSize/2.0 + conMod) | ||||||
| @@ -429,7 +235,17 @@ class MonsterViewModel: ObservableObject { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |  | ||||||
|  |     class func hitDieForSize(_ size: SizeType) -> Int { | ||||||
|  |         switch size { | ||||||
|  |             case .tiny: return 4 | ||||||
|  |             case .small: return 6 | ||||||
|  |             case .medium: return 8 | ||||||
|  |             case .large: return 10 | ||||||
|  |             case .huge: return 12 | ||||||
|  |             case .gargantuan: return 20 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // MARK: Ability Scores |     // MARK: Ability Scores | ||||||
|     class func abilityModifierForScore(_ score: Int) -> Int { |     class func abilityModifierForScore(_ score: Int) -> Int { | ||||||
| @@ -667,7 +483,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|             if (strengthSavingThrowAdvantage != .none || strengthSavingThrowProficiency != .none) { |             if (strengthSavingThrowAdvantage != .none || strengthSavingThrowProficiency != .none) { | ||||||
|                 name = "Strength" |                 name = "Strength" | ||||||
|                 bonus = strengthModifier + proficiencyBonusForType(strengthSavingThrowProficiency) |                 bonus = strengthModifier + proficiencyBonusForType(strengthSavingThrowProficiency) | ||||||
|                 advantage = Monster.advantageLabelStringForType(strengthSavingThrowAdvantage) |                 advantage = MonsterViewModel.advantageLabelStringForType(strengthSavingThrowAdvantage) | ||||||
|                 if (!advantage.isEmpty) { |                 if (!advantage.isEmpty) { | ||||||
|                     advantage = " " + advantage |                     advantage = " " + advantage | ||||||
|                 } |                 } | ||||||
| @@ -677,7 +493,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|             if (dexteritySavingThrowAdvantage != .none || dexteritySavingThrowProficiency != .none) { |             if (dexteritySavingThrowAdvantage != .none || dexteritySavingThrowProficiency != .none) { | ||||||
|                 name = "Dexterity" |                 name = "Dexterity" | ||||||
|                 bonus = dexterityModifier + proficiencyBonusForType(dexteritySavingThrowProficiency) |                 bonus = dexterityModifier + proficiencyBonusForType(dexteritySavingThrowProficiency) | ||||||
|                 advantage = Monster.advantageLabelStringForType(dexteritySavingThrowAdvantage) |                 advantage = MonsterViewModel.advantageLabelStringForType(dexteritySavingThrowAdvantage) | ||||||
|                 if (!advantage.isEmpty) { |                 if (!advantage.isEmpty) { | ||||||
|                     advantage = " " + advantage |                     advantage = " " + advantage | ||||||
|                 } |                 } | ||||||
| @@ -687,7 +503,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|             if (constitutionSavingThrowAdvantage != .none || constitutionSavingThrowProficiency != .none) { |             if (constitutionSavingThrowAdvantage != .none || constitutionSavingThrowProficiency != .none) { | ||||||
|                 name = "Constitution" |                 name = "Constitution" | ||||||
|                 bonus = constitutionModifier + proficiencyBonusForType(constitutionSavingThrowProficiency) |                 bonus = constitutionModifier + proficiencyBonusForType(constitutionSavingThrowProficiency) | ||||||
|                 advantage = Monster.advantageLabelStringForType(constitutionSavingThrowAdvantage) |                 advantage = MonsterViewModel.advantageLabelStringForType(constitutionSavingThrowAdvantage) | ||||||
|                 if (!advantage.isEmpty) { |                 if (!advantage.isEmpty) { | ||||||
|                     advantage = " " + advantage |                     advantage = " " + advantage | ||||||
|                 } |                 } | ||||||
| @@ -697,7 +513,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|             if (intelligenceSavingThrowAdvantage != .none || intelligenceSavingThrowProficiency != .none) { |             if (intelligenceSavingThrowAdvantage != .none || intelligenceSavingThrowProficiency != .none) { | ||||||
|                 name = "Intelligence" |                 name = "Intelligence" | ||||||
|                 bonus = intelligenceModifier + proficiencyBonusForType(intelligenceSavingThrowProficiency) |                 bonus = intelligenceModifier + proficiencyBonusForType(intelligenceSavingThrowProficiency) | ||||||
|                 advantage = Monster.advantageLabelStringForType(intelligenceSavingThrowAdvantage) |                 advantage = MonsterViewModel.advantageLabelStringForType(intelligenceSavingThrowAdvantage) | ||||||
|                 if (!advantage.isEmpty) { |                 if (!advantage.isEmpty) { | ||||||
|                     advantage = " " + advantage |                     advantage = " " + advantage | ||||||
|                 } |                 } | ||||||
| @@ -707,7 +523,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|             if (wisdomSavingThrowAdvantage != .none || wisdomSavingThrowProficiency != .none) { |             if (wisdomSavingThrowAdvantage != .none || wisdomSavingThrowProficiency != .none) { | ||||||
|                 name = "Wisdom" |                 name = "Wisdom" | ||||||
|                 bonus = wisdomModifier + proficiencyBonusForType(wisdomSavingThrowProficiency) |                 bonus = wisdomModifier + proficiencyBonusForType(wisdomSavingThrowProficiency) | ||||||
|                 advantage = Monster.advantageLabelStringForType(wisdomSavingThrowAdvantage) |                 advantage = MonsterViewModel.advantageLabelStringForType(wisdomSavingThrowAdvantage) | ||||||
|                 if (!advantage.isEmpty) { |                 if (!advantage.isEmpty) { | ||||||
|                     advantage = " " + advantage |                     advantage = " " + advantage | ||||||
|                 } |                 } | ||||||
| @@ -717,7 +533,7 @@ class MonsterViewModel: ObservableObject { | |||||||
|             if (charismaSavingThrowAdvantage != .none || charismaSavingThrowProficiency != .none) { |             if (charismaSavingThrowAdvantage != .none || charismaSavingThrowProficiency != .none) { | ||||||
|                 name = "Charisma" |                 name = "Charisma" | ||||||
|                 bonus = charismaModifier + proficiencyBonusForType(charismaSavingThrowProficiency) |                 bonus = charismaModifier + proficiencyBonusForType(charismaSavingThrowProficiency) | ||||||
|                 advantage = Monster.advantageLabelStringForType(charismaSavingThrowAdvantage) |                 advantage = MonsterViewModel.advantageLabelStringForType(charismaSavingThrowAdvantage) | ||||||
|                 if (!advantage.isEmpty) { |                 if (!advantage.isEmpty) { | ||||||
|                     advantage = " " + advantage |                     advantage = " " + advantage | ||||||
|                 } |                 } | ||||||
| @@ -731,7 +547,17 @@ class MonsterViewModel: ObservableObject { | |||||||
|  |  | ||||||
|     // MARK: Misc Helpers |     // MARK: Misc Helpers | ||||||
|  |  | ||||||
|      |     class func advantageLabelStringForType(_ advType: AdvantageType) -> String { | ||||||
|  |         switch advType { | ||||||
|  |         case .none: | ||||||
|  |             return "" | ||||||
|  |         case .advantage: | ||||||
|  |             return "(A)" | ||||||
|  |         case .disadvantage: | ||||||
|  |             return "(D)" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // MARK: Skills |     // MARK: Skills | ||||||
|  |  | ||||||
|     var skillsDescription: String { |     var skillsDescription: String { | ||||||
|   | |||||||
| @@ -47,29 +47,4 @@ public class Skill: NSManagedObject { | |||||||
|             advantage = newValue.rawValue |             advantage = newValue.rawValue | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |  | ||||||
|     var modifier: Int { |  | ||||||
|         get { |  | ||||||
|             let proficiencyBonus = Double(monster?.proficiencyBonus ?? 0) |  | ||||||
|             let abilityScoreModifier = Double(monster?.abilityModifierForAbilityScore(wrappedAbilityScore) ?? 0) |  | ||||||
|             switch wrappedProficiency { |  | ||||||
|             case .none: |  | ||||||
|                 return Int(abilityScoreModifier) |  | ||||||
|             case .proficient: |  | ||||||
|                 return Int(abilityScoreModifier + proficiencyBonus) |  | ||||||
|             case .expertise: |  | ||||||
|                 return Int(abilityScoreModifier + 2 * proficiencyBonus) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     var skillDescription: String { |  | ||||||
|         get { |  | ||||||
|             var advantageLabel = Monster.advantageLabelStringForType(wrappedAdvantage) |  | ||||||
|             if (advantageLabel != "") { |  | ||||||
|                 advantageLabel = " " + advantageLabel |  | ||||||
|             } |  | ||||||
|             return String(format: "%@ %+d%@", name ?? "", modifier, advantageLabel) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								MonsterCards/Models/SkillViewModel+CoreData.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								MonsterCards/Models/SkillViewModel+CoreData.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | // | ||||||
|  | //  SkillViewModel+CoreData.swift | ||||||
|  | //  MonsterCards | ||||||
|  | // | ||||||
|  | //  Created by Tom Hicks on 4/7/21. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import Foundation | ||||||
|  | import CoreData | ||||||
|  |  | ||||||
|  | extension SkillViewModel { | ||||||
|  |     func isEqualTo(rawSkill: Skill?) -> Bool { | ||||||
|  |         if (rawSkill == nil) { | ||||||
|  |             return false; | ||||||
|  |         } else if (abilityScore != rawSkill!.wrappedAbilityScore) { | ||||||
|  |             return false; | ||||||
|  |         } else if (advantage != rawSkill!.wrappedAdvantage) { | ||||||
|  |             return false; | ||||||
|  |         } else if (name != rawSkill!.name) { | ||||||
|  |             return false; | ||||||
|  |         } else if (proficiency != rawSkill!.wrappedProficiency) { | ||||||
|  |             return false; | ||||||
|  |         } else { | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func copyToSkill(skill: Skill) { | ||||||
|  |         skill.wrappedAbilityScore = abilityScore | ||||||
|  |         skill.wrappedAdvantage = advantage | ||||||
|  |         skill.name = name | ||||||
|  |         skill.wrappedProficiency = proficiency | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     convenience init(_ rawSkill: Skill?) { | ||||||
|  |         if (rawSkill == nil) { | ||||||
|  |             self.init() | ||||||
|  |         } else { | ||||||
|  |             let skill = rawSkill! | ||||||
|  |             self.init( | ||||||
|  |                 skill.wrappedName, | ||||||
|  |                 skill.wrappedAbilityScore, | ||||||
|  |                 skill.wrappedProficiency, | ||||||
|  |                 skill.wrappedAdvantage | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     func buildRawSkill(context: NSManagedObjectContext?) -> Skill { | ||||||
|  |         let newSkill = context == nil ? Skill.init() : Skill.init(context: context!) | ||||||
|  |         newSkill.name = name | ||||||
|  |         newSkill.wrappedAbilityScore = abilityScore | ||||||
|  |         newSkill.wrappedProficiency = proficiency | ||||||
|  |         newSkill.wrappedAdvantage = advantage | ||||||
|  |         return newSkill | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,131 +8,20 @@ | |||||||
| import Foundation | import Foundation | ||||||
| import CoreData | import CoreData | ||||||
|  |  | ||||||
| class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable { | class SkillViewModel: ObservableObject, Identifiable { | ||||||
|     static func < (lhs: SkillViewModel, rhs: SkillViewModel) -> Bool { |  | ||||||
|         return lhs.name < rhs.name |  | ||||||
|     } |  | ||||||
|      |      | ||||||
|     static func == (lhs: SkillViewModel, rhs: SkillViewModel) -> Bool { |     @Published var name: String | ||||||
|         return lhs.abilityScore == rhs.abilityScore |     @Published var abilityScore: AbilityScore | ||||||
|             && lhs.advantage == rhs.advantage |     @Published var proficiency: ProficiencyType | ||||||
|             && lhs.name == rhs.name |     @Published var advantage: AdvantageType | ||||||
|             && lhs.proficiency == rhs.proficiency |  | ||||||
|     } |  | ||||||
|      |      | ||||||
|     func hash(into hasher: inout Hasher) { |     init(_ name: String = "", _ abilityScore: AbilityScore = .dexterity, _ proficiency: ProficiencyType = .proficient, _ advantage: AdvantageType = .none) { | ||||||
|         hasher.combine(abilityScore) |         self.name = name | ||||||
|         hasher.combine(advantage) |         self.abilityScore = abilityScore | ||||||
|         hasher.combine(name) |         self.proficiency = proficiency | ||||||
|         hasher.combine(proficiency) |         self.advantage = advantage | ||||||
|     } |     } | ||||||
|      |          | ||||||
|     func isEqualTo(rawSkill: Skill?) -> Bool { |  | ||||||
|         if (rawSkill == nil) { |  | ||||||
|             return false; |  | ||||||
|         } else if (abilityScore != rawSkill!.wrappedAbilityScore) { |  | ||||||
|             return false; |  | ||||||
|         } else if (advantage != rawSkill!.wrappedAdvantage) { |  | ||||||
|             return false; |  | ||||||
|         } else if (name != rawSkill!.name) { |  | ||||||
|             return false; |  | ||||||
|         } else if (proficiency != rawSkill!.wrappedProficiency) { |  | ||||||
|             return false; |  | ||||||
|         } else { |  | ||||||
|             return true |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     func copyToSkill(skill: Skill) { |  | ||||||
|         skill.wrappedAbilityScore = abilityScore |  | ||||||
|         skill.wrappedAdvantage = advantage |  | ||||||
|         skill.name = name |  | ||||||
|         skill.wrappedProficiency = proficiency |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     init(_ rawSkill: Skill? = nil) { |  | ||||||
|         if (rawSkill != nil) { |  | ||||||
|             _name = rawSkill!.name ?? "" |  | ||||||
|             _abilityScore = AbilityScore(rawValue: rawSkill!.abilityScoreName ?? "") ?? .strength |  | ||||||
|             _proficiency = ProficiencyType(rawValue: rawSkill!.proficiency ?? "") ?? .none |  | ||||||
|             _advantage = AdvantageType(rawValue: rawSkill!.advantage ?? "") ?? .none |  | ||||||
|             _advantage = .none |  | ||||||
|         } else { |  | ||||||
|             _name = "" |  | ||||||
|             _abilityScore = .strength |  | ||||||
|             _proficiency = .none |  | ||||||
|             _advantage = .none |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     init(_ name: String, _ abilityScore: AbilityScore, _ proficiency: ProficiencyType = .proficient, _ advantage: AdvantageType = .none) { |  | ||||||
|         _name = name |  | ||||||
|         _abilityScore = abilityScore |  | ||||||
|         _proficiency = proficiency |  | ||||||
|         _advantage = advantage |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private var _name: String = "" |  | ||||||
|     var name: String { |  | ||||||
|         get { |  | ||||||
|             return _name |  | ||||||
|         } |  | ||||||
|         set { |  | ||||||
|             if (newValue != _name) { |  | ||||||
|                 _name = newValue |  | ||||||
|                 // Notify changed name |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private var _abilityScore: AbilityScore |  | ||||||
|     var abilityScore: AbilityScore { |  | ||||||
|         get { |  | ||||||
|             return _abilityScore |  | ||||||
|         } |  | ||||||
|         set { |  | ||||||
|             if (newValue != _abilityScore) { |  | ||||||
|                 _abilityScore = newValue |  | ||||||
|                 // Notify changed |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private var _proficiency: ProficiencyType |  | ||||||
|     var proficiency: ProficiencyType { |  | ||||||
|         get { |  | ||||||
|             return _proficiency |  | ||||||
|         } |  | ||||||
|         set { |  | ||||||
|             if (newValue != _proficiency) { |  | ||||||
|                 _proficiency = newValue |  | ||||||
|                 // Notify changed |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private var _advantage: AdvantageType |  | ||||||
|     var advantage: AdvantageType { |  | ||||||
|         get { |  | ||||||
|             return _advantage |  | ||||||
|         } |  | ||||||
|         set { |  | ||||||
|             if (newValue != _advantage) { |  | ||||||
|                 _advantage = newValue |  | ||||||
|                 // Notify changed |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     func buildRawSkill(context: NSManagedObjectContext?) -> Skill { |  | ||||||
|         let newSkill = context == nil ? Skill.init() : Skill.init(context: context!) |  | ||||||
|         newSkill.name = name |  | ||||||
|         newSkill.wrappedAbilityScore = abilityScore |  | ||||||
|         newSkill.wrappedProficiency = proficiency |  | ||||||
|         newSkill.wrappedAdvantage = advantage |  | ||||||
|         return newSkill |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     func modifier(forMonster: MonsterViewModel) -> Int { |     func modifier(forMonster: MonsterViewModel) -> Int { | ||||||
|         let proficiencyBonus = Double(forMonster.proficiencyBonus) |         let proficiencyBonus = Double(forMonster.proficiencyBonus) | ||||||
|         let abilityScoreModifier = Double(forMonster.abilityModifierForAbilityScore(abilityScore)) |         let abilityScoreModifier = Double(forMonster.abilityModifierForAbilityScore(abilityScore)) | ||||||
| @@ -147,10 +36,32 @@ class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable { | |||||||
|     } |     } | ||||||
|      |      | ||||||
|     func skillDescription(forMonster: MonsterViewModel) -> String { |     func skillDescription(forMonster: MonsterViewModel) -> String { | ||||||
|         var advantageLabel = Monster.advantageLabelStringForType(advantage) |         var advantageLabel = MonsterViewModel.advantageLabelStringForType(advantage) | ||||||
|         if (advantageLabel != "") { |         if (advantageLabel != "") { | ||||||
|             advantageLabel = " " + advantageLabel |             advantageLabel = " " + advantageLabel | ||||||
|         } |         } | ||||||
|         return String(format: "%@ %+d%@", name, modifier(forMonster: forMonster), advantageLabel) |         return String(format: "%@ %+d%@", name, modifier(forMonster: forMonster), advantageLabel) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | extension SkillViewModel: Hashable { | ||||||
|  |     func hash(into hasher: inout Hasher) { | ||||||
|  |         hasher.combine(abilityScore) | ||||||
|  |         hasher.combine(advantage) | ||||||
|  |         hasher.combine(name) | ||||||
|  |         hasher.combine(proficiency) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension SkillViewModel: Comparable { | ||||||
|  |     static func < (lhs: SkillViewModel, rhs: SkillViewModel) -> Bool { | ||||||
|  |         return lhs.name < rhs.name | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     static func == (lhs: SkillViewModel, rhs: SkillViewModel) -> Bool { | ||||||
|  |         return lhs.abilityScore == rhs.abilityScore | ||||||
|  |             && lhs.advantage == rhs.advantage | ||||||
|  |             && lhs.name == rhs.name | ||||||
|  |             && lhs.proficiency == rhs.proficiency | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user