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 */; }; | ||||
| 		E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.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 */; }; | ||||
| 		E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219247A261989B400C84E12 /* MonsterDTO.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>"; }; | ||||
| 		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>"; }; | ||||
| 		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>"; }; | ||||
| 		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>"; }; | ||||
| @@ -306,10 +310,12 @@ | ||||
| 				E216E464261FDA2E00FD9262 /* MonsterDocument.swift */, | ||||
| 				E219247A261989B400C84E12 /* MonsterDTO.swift */, | ||||
| 				E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */, | ||||
| 				E216E46C261FDE5600FD9262 /* MonsterViewModel+CoreData.swift */, | ||||
| 				E219247F261989F700C84E12 /* SavingThrowDTO.swift */, | ||||
| 				E20209D225D8DD9600EFE733 /* Skill+CoreDataClass.swift */, | ||||
| 				E219248426198A1200C84E12 /* SkillDTO.swift */, | ||||
| 				E20209F925D8E19100EFE733 /* SkillViewModel.swift */, | ||||
| 				E216E471261FDF3200FD9262 /* SkillViewModel+CoreData.swift */, | ||||
| 				E2CB0DE0260887ED00142591 /* StringViewModel.swift */, | ||||
| 				E219248926198A5400C84E12 /* TraitDTO.swift */, | ||||
| 			); | ||||
| @@ -481,6 +487,8 @@ | ||||
| 				E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */, | ||||
| 				E219247B261989B400C84E12 /* MonsterDTO.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 */, | ||||
| 				E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */, | ||||
| 				E219249926198E0D00C84E12 /* MonsterImportHelper.swift in Sources */, | ||||
|   | ||||
| @@ -20,212 +20,6 @@ public class Monster: NSManagedObject { | ||||
|         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 | ||||
|      | ||||
|     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 | ||||
|      | ||||
|     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 | ||||
|      | ||||
|     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 { | ||||
|         get { | ||||
|             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 | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										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 reactions: [AbilityViewModel] | ||||
|     @Published var isBlind: Bool | ||||
|      | ||||
|     private var shieldBonus: Int | ||||
|     private var otherArmorDescription: String | ||||
|     @Published var shieldBonus: Int | ||||
|     @Published var otherArmorDescription: String | ||||
|      | ||||
|     let kBaseArmorClassUnarmored = 10; | ||||
|     let kBaseArmorClassMageArmor = 13; | ||||
| @@ -90,7 +89,7 @@ class MonsterViewModel: ObservableObject { | ||||
|     let kBaseArmorClassSplintMail = 17; | ||||
|     let kBaseArmorClassPlate = 18; | ||||
|      | ||||
|     init(_ rawMonster: Monster? = nil) { | ||||
|     init() { | ||||
|         self.name = "" | ||||
|         self.size = "" | ||||
|         self.type = "" | ||||
| @@ -152,199 +151,6 @@ class MonsterViewModel: ObservableObject { | ||||
|         // Private properties | ||||
|         self.shieldBonus = 0 | ||||
|         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 | ||||
| @@ -388,7 +194,7 @@ class MonsterViewModel: ObservableObject { | ||||
|             if (hasCustomHP) { | ||||
|                 return customHP; | ||||
|             } else { | ||||
|                 let dieSize = Double(Monster.hitDieForSize(sizeEnum)) | ||||
|                 let dieSize = Double(MonsterViewModel.hitDieForSize(sizeEnum)) | ||||
|                 let conMod = Double(constitutionModifier) | ||||
| //                let level1HP = Double(dieSize + conMod) | ||||
|                 let level1HP = Double(dieSize/2.0 + conMod) | ||||
| @@ -430,6 +236,16 @@ 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 | ||||
|     class func abilityModifierForScore(_ score: Int) -> Int { | ||||
| @@ -667,7 +483,7 @@ class MonsterViewModel: ObservableObject { | ||||
|             if (strengthSavingThrowAdvantage != .none || strengthSavingThrowProficiency != .none) { | ||||
|                 name = "Strength" | ||||
|                 bonus = strengthModifier + proficiencyBonusForType(strengthSavingThrowProficiency) | ||||
|                 advantage = Monster.advantageLabelStringForType(strengthSavingThrowAdvantage) | ||||
|                 advantage = MonsterViewModel.advantageLabelStringForType(strengthSavingThrowAdvantage) | ||||
|                 if (!advantage.isEmpty) { | ||||
|                     advantage = " " + advantage | ||||
|                 } | ||||
| @@ -677,7 +493,7 @@ class MonsterViewModel: ObservableObject { | ||||
|             if (dexteritySavingThrowAdvantage != .none || dexteritySavingThrowProficiency != .none) { | ||||
|                 name = "Dexterity" | ||||
|                 bonus = dexterityModifier + proficiencyBonusForType(dexteritySavingThrowProficiency) | ||||
|                 advantage = Monster.advantageLabelStringForType(dexteritySavingThrowAdvantage) | ||||
|                 advantage = MonsterViewModel.advantageLabelStringForType(dexteritySavingThrowAdvantage) | ||||
|                 if (!advantage.isEmpty) { | ||||
|                     advantage = " " + advantage | ||||
|                 } | ||||
| @@ -687,7 +503,7 @@ class MonsterViewModel: ObservableObject { | ||||
|             if (constitutionSavingThrowAdvantage != .none || constitutionSavingThrowProficiency != .none) { | ||||
|                 name = "Constitution" | ||||
|                 bonus = constitutionModifier + proficiencyBonusForType(constitutionSavingThrowProficiency) | ||||
|                 advantage = Monster.advantageLabelStringForType(constitutionSavingThrowAdvantage) | ||||
|                 advantage = MonsterViewModel.advantageLabelStringForType(constitutionSavingThrowAdvantage) | ||||
|                 if (!advantage.isEmpty) { | ||||
|                     advantage = " " + advantage | ||||
|                 } | ||||
| @@ -697,7 +513,7 @@ class MonsterViewModel: ObservableObject { | ||||
|             if (intelligenceSavingThrowAdvantage != .none || intelligenceSavingThrowProficiency != .none) { | ||||
|                 name = "Intelligence" | ||||
|                 bonus = intelligenceModifier + proficiencyBonusForType(intelligenceSavingThrowProficiency) | ||||
|                 advantage = Monster.advantageLabelStringForType(intelligenceSavingThrowAdvantage) | ||||
|                 advantage = MonsterViewModel.advantageLabelStringForType(intelligenceSavingThrowAdvantage) | ||||
|                 if (!advantage.isEmpty) { | ||||
|                     advantage = " " + advantage | ||||
|                 } | ||||
| @@ -707,7 +523,7 @@ class MonsterViewModel: ObservableObject { | ||||
|             if (wisdomSavingThrowAdvantage != .none || wisdomSavingThrowProficiency != .none) { | ||||
|                 name = "Wisdom" | ||||
|                 bonus = wisdomModifier + proficiencyBonusForType(wisdomSavingThrowProficiency) | ||||
|                 advantage = Monster.advantageLabelStringForType(wisdomSavingThrowAdvantage) | ||||
|                 advantage = MonsterViewModel.advantageLabelStringForType(wisdomSavingThrowAdvantage) | ||||
|                 if (!advantage.isEmpty) { | ||||
|                     advantage = " " + advantage | ||||
|                 } | ||||
| @@ -717,7 +533,7 @@ class MonsterViewModel: ObservableObject { | ||||
|             if (charismaSavingThrowAdvantage != .none || charismaSavingThrowProficiency != .none) { | ||||
|                 name = "Charisma" | ||||
|                 bonus = charismaModifier + proficiencyBonusForType(charismaSavingThrowProficiency) | ||||
|                 advantage = Monster.advantageLabelStringForType(charismaSavingThrowAdvantage) | ||||
|                 advantage = MonsterViewModel.advantageLabelStringForType(charismaSavingThrowAdvantage) | ||||
|                 if (!advantage.isEmpty) { | ||||
|                     advantage = " " + advantage | ||||
|                 } | ||||
| @@ -731,6 +547,16 @@ class MonsterViewModel: ObservableObject { | ||||
|  | ||||
|     // MARK: Misc Helpers | ||||
|  | ||||
|     class func advantageLabelStringForType(_ advType: AdvantageType) -> String { | ||||
|         switch advType { | ||||
|         case .none: | ||||
|             return "" | ||||
|         case .advantage: | ||||
|             return "(A)" | ||||
|         case .disadvantage: | ||||
|             return "(D)" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // MARK: Skills | ||||
|  | ||||
|   | ||||
| @@ -47,29 +47,4 @@ public class Skill: NSManagedObject { | ||||
|             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,129 +8,18 @@ | ||||
| import Foundation | ||||
| import CoreData | ||||
|  | ||||
| class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable { | ||||
|     static func < (lhs: SkillViewModel, rhs: SkillViewModel) -> Bool { | ||||
|         return lhs.name < rhs.name | ||||
|     } | ||||
| class SkillViewModel: ObservableObject, Identifiable { | ||||
|      | ||||
|     static func == (lhs: SkillViewModel, rhs: SkillViewModel) -> Bool { | ||||
|         return lhs.abilityScore == rhs.abilityScore | ||||
|             && lhs.advantage == rhs.advantage | ||||
|             && lhs.name == rhs.name | ||||
|             && lhs.proficiency == rhs.proficiency | ||||
|     } | ||||
|     @Published var name: String | ||||
|     @Published var abilityScore: AbilityScore | ||||
|     @Published var proficiency: ProficiencyType | ||||
|     @Published var advantage: AdvantageType | ||||
|      | ||||
|     func hash(into hasher: inout Hasher) { | ||||
|         hasher.combine(abilityScore) | ||||
|         hasher.combine(advantage) | ||||
|         hasher.combine(name) | ||||
|         hasher.combine(proficiency) | ||||
|     } | ||||
|      | ||||
|     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 | ||||
|     init(_ name: String = "", _ abilityScore: AbilityScore = .dexterity, _ proficiency: ProficiencyType = .proficient, _ advantage: AdvantageType = .none) { | ||||
|         self.name = name | ||||
|         self.abilityScore = abilityScore | ||||
|         self.proficiency = proficiency | ||||
|         self.advantage = advantage | ||||
|     } | ||||
|          | ||||
|     func modifier(forMonster: MonsterViewModel) -> Int { | ||||
| @@ -147,10 +36,32 @@ class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable { | ||||
|     } | ||||
|      | ||||
|     func skillDescription(forMonster: MonsterViewModel) -> String { | ||||
|         var advantageLabel = Monster.advantageLabelStringForType(advantage) | ||||
|         var advantageLabel = MonsterViewModel.advantageLabelStringForType(advantage) | ||||
|         if (advantageLabel != "") { | ||||
|             advantageLabel = " " + 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