diff --git a/MonsterCards.xcodeproj/project.pbxproj b/MonsterCards.xcodeproj/project.pbxproj index 4fe3ba8..90965a7 100644 --- a/MonsterCards.xcodeproj/project.pbxproj +++ b/MonsterCards.xcodeproj/project.pbxproj @@ -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 = ""; }; E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChallengeRatingPicker.swift; sourceTree = ""; }; E216E464261FDA2E00FD9262 /* MonsterDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDocument.swift; sourceTree = ""; }; + E216E46C261FDE5600FD9262 /* MonsterViewModel+CoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonsterViewModel+CoreData.swift"; sourceTree = ""; }; + E216E471261FDF3200FD9262 /* SkillViewModel+CoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SkillViewModel+CoreData.swift"; sourceTree = ""; }; E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Monster+CoreDataClass.swift"; sourceTree = ""; }; E219247A261989B400C84E12 /* MonsterDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDTO.swift; sourceTree = ""; }; E219247F261989F700C84E12 /* SavingThrowDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavingThrowDTO.swift; sourceTree = ""; }; @@ -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 */, diff --git a/MonsterCards/Models/Monster+CoreDataClass.swift b/MonsterCards/Models/Monster+CoreDataClass.swift index 21d6c8d..2b707cb 100644 --- a/MonsterCards/Models/Monster+CoreDataClass.swift +++ b/MonsterCards/Models/Monster+CoreDataClass.swift @@ -19,213 +19,7 @@ public class Monster: NSManagedObject { self.subtype = subtype; 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 ?? [] - 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 } diff --git a/MonsterCards/Models/MonsterViewModel+CoreData.swift b/MonsterCards/Models/MonsterViewModel+CoreData.swift new file mode 100644 index 0000000..b3835c9 --- /dev/null +++ b/MonsterCards/Models/MonsterViewModel+CoreData.swift @@ -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 + } + +} diff --git a/MonsterCards/Models/MonsterViewModel.swift b/MonsterCards/Models/MonsterViewModel.swift index 61031a4..2d8f8b9 100644 --- a/MonsterCards/Models/MonsterViewModel.swift +++ b/MonsterCards/Models/MonsterViewModel.swift @@ -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) @@ -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 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,7 +547,17 @@ 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 var skillsDescription: String { diff --git a/MonsterCards/Models/Skill+CoreDataClass.swift b/MonsterCards/Models/Skill+CoreDataClass.swift index da583db..a29477a 100644 --- a/MonsterCards/Models/Skill+CoreDataClass.swift +++ b/MonsterCards/Models/Skill+CoreDataClass.swift @@ -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) - } - } } diff --git a/MonsterCards/Models/SkillViewModel+CoreData.swift b/MonsterCards/Models/SkillViewModel+CoreData.swift new file mode 100644 index 0000000..5b40151 --- /dev/null +++ b/MonsterCards/Models/SkillViewModel+CoreData.swift @@ -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 + } +} diff --git a/MonsterCards/Models/SkillViewModel.swift b/MonsterCards/Models/SkillViewModel.swift index 557cb2e..9153ce4 100644 --- a/MonsterCards/Models/SkillViewModel.swift +++ b/MonsterCards/Models/SkillViewModel.swift @@ -8,131 +8,20 @@ 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) + 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 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 { let proficiencyBonus = Double(forMonster.proficiencyBonus) let abilityScoreModifier = Double(forMonster.abilityModifierForAbilityScore(abilityScore)) @@ -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 + } +}