From 568485a62e1b12a8f5addf30e2e66bb2acb18f75 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 00:19:49 -0700 Subject: [PATCH 01/13] Adds calculated fields from Monster to MonsterViewModel. --- .../Models/MonsterViewModel.swift | 565 ++++++++++++++++++ 1 file changed, 565 insertions(+) diff --git a/iOS/MonsterCards/Models/MonsterViewModel.swift b/iOS/MonsterCards/Models/MonsterViewModel.swift index 1c39817..62cb57a 100644 --- a/iOS/MonsterCards/Models/MonsterViewModel.swift +++ b/iOS/MonsterCards/Models/MonsterViewModel.swift @@ -64,6 +64,24 @@ class MonsterViewModel: ObservableObject { @Published var actions: [AbilityViewModel] @Published var legendaryActions: [AbilityViewModel] + private var shieldBonus: Int + private var otherArmorDescription: String + + 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; + init(_ rawMonster: Monster? = nil) { self.name = "" self.size = "" @@ -118,7 +136,12 @@ class MonsterViewModel: ObservableObject { self.abilities = [] self.actions = [] self.legendaryActions = [] + + // Private properties + self.shieldBonus = 0 + self.otherArmorDescription = "" + // Call the copy constructor if (rawMonster != nil) { self.copyFromMonster(monster: rawMonster!) } @@ -204,6 +227,11 @@ class MonsterViewModel: ObservableObject { self.legendaryActions = (monster.legendaryActions ?? []) .map {AbilityViewModel($0.name, $0.abilityDescription)} + + // Private fields + + self.shieldBonus = Int(monster.shieldBonus) + self.otherArmorDescription = monster.otherArmorDescription ?? "" } func copyToMonster(monster: Monster) { @@ -285,5 +313,542 @@ class MonsterViewModel: ObservableObject { monster.actions = actions.map {AbilityViewModel($0.name, $0.abilityDescription)} monster.legendaryActions = legendaryActions.map {AbilityViewModel($0.name, $0.abilityDescription)} + + monster.shieldBonus = Int64(shieldBonus) + monster.otherArmorDescription = otherArmorDescription } + + // MARK: Basic Info + + var meta: String { + get { + // size type (subtype) alignment + var parts: [String] = [] + + if (!(self.size.isEmpty)) { + parts.append(self.size) + } + + if (!(self.type.isEmpty)) { + parts.append(self.type) + } + + if (!(self.subType.isEmpty)) { + parts.append(String.init(format: "(%@)", arguments: [self.subType])) + } + + if (!(self.alignment.isEmpty)) { + 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 (baseSpeed > 0) { + parts.append("\(baseSpeed) 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: ", ") + } + } + } + + + // 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 MonsterViewModel.abilityModifierForScore(Int(strengthScore)) + } + } + + var dexterityModifier: Int { + get { + return MonsterViewModel.abilityModifierForScore(Int(dexterityScore)) + } + } + + var constitutionModifier: Int { + get { + return MonsterViewModel.abilityModifierForScore(Int(constitutionScore)) + } + } + + var intelligenceModifier: Int { + get { + return MonsterViewModel.abilityModifierForScore(Int(intelligenceScore)) + } + } + + var wisdomModifier: Int { + get { + return MonsterViewModel.abilityModifierForScore(Int(wisdomScore)) + } + } + + var charismaModifier: Int { + get { + return MonsterViewModel.abilityModifierForScore(Int(charismaScore)) + } + } + + + // MARK: Armor + + var armorClassDescription: String { + get { + let hasShield = shieldBonus != 0 + var armorClassTotal = 0 + if (armorType == ArmorType.none) { + // 10 + dexMod + 2 for shieldBonus "15" or "17 (shield)" + armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(shieldBonus) + return "\(armorClassTotal)\(hasShield ? " (shield)" : "")" + } else if (armorType == .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 (armorType == .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 (armorType == .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 (armorType == .leather) { + // 11 + dexMod + 2 for shield "18 (leather, shield)" + armorClassTotal = kBaseArmorClassLeather + dexterityModifier + Int(shieldBonus) + return String(format:"%d (leather%@)", armorClassTotal, (hasShield ? "shield, " : "")) + } else if (armorType == .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 (armorType == .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 (armorType == .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 (armorType == .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 (armorType == .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 (armorType == .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 (armorType == .ringMail) { + // 14 + 2 for shield "14 (ring mail) + armorClassTotal = kBaseArmorClassRingMail + Int(shieldBonus) + return String(format: "%d (ring mail%@)", armorClassTotal, (hasShield ? ", shield" : "")) + } else if (armorType == .chainMail) { + // 16 + 2 for shield "16 (chain mail)" + armorClassTotal = kBaseArmorClassChainMail + Int(shieldBonus) + return String(format: "%d (chain mail%@)", armorClassTotal, (hasShield ? ", shield" : "")) + } else if (armorType == .splintMail) { + // 17 + 2 for shield "17 (splint)" + armorClassTotal = kBaseArmorClassSplintMail + Int(shieldBonus) + return String(format: "%d (splint%@)", armorClassTotal, (hasShield ? ", shield" : "")) + } else if (armorType == .plateMail) { + // 18 + 2 for shield "18 (plate)" + armorClassTotal = kBaseArmorClassPlate + Int(shieldBonus) + return String(format: "%d (plate%@)", armorClassTotal, (hasShield ? ", shield" : "")) + } else if (armorType == .other) { + // pure string value shield check does nothing just copies the string from otherArmorDesc + return otherArmorDescription; + } else { + return "" + } + } + } + + // MARK: Challenge Rating / Proficiency Bonus + + + var proficiencyBonus: Int { + switch challengeRating { + 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 (strengthSavingThrowAdvantage != .none || strengthSavingThrowProficiency != .none) { + name = "Strength" + bonus = strengthModifier + proficiencyBonusForType(strengthSavingThrowProficiency) + advantage = Monster.advantageLabelStringForType(strengthSavingThrowAdvantage) + if (!advantage.isEmpty) { + advantage = " " + advantage + } + parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) + } + + if (dexteritySavingThrowAdvantage != .none || dexteritySavingThrowProficiency != .none) { + name = "Dexterity" + bonus = dexterityModifier + proficiencyBonusForType(dexteritySavingThrowProficiency) + advantage = Monster.advantageLabelStringForType(dexteritySavingThrowAdvantage) + if (!advantage.isEmpty) { + advantage = " " + advantage + } + parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) + } + + if (constitutionSavingThrowAdvantage != .none || constitutionSavingThrowProficiency != .none) { + name = "Constitution" + bonus = constitutionModifier + proficiencyBonusForType(constitutionSavingThrowProficiency) + advantage = Monster.advantageLabelStringForType(constitutionSavingThrowAdvantage) + if (!advantage.isEmpty) { + advantage = " " + advantage + } + parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) + } + + if (intelligenceSavingThrowAdvantage != .none || intelligenceSavingThrowProficiency != .none) { + name = "Intelligence" + bonus = intelligenceModifier + proficiencyBonusForType(intelligenceSavingThrowProficiency) + advantage = Monster.advantageLabelStringForType(intelligenceSavingThrowAdvantage) + if (!advantage.isEmpty) { + advantage = " " + advantage + } + parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) + } + + if (wisdomSavingThrowAdvantage != .none || wisdomSavingThrowProficiency != .none) { + name = "Wisdom" + bonus = wisdomModifier + proficiencyBonusForType(wisdomSavingThrowProficiency) + advantage = Monster.advantageLabelStringForType(wisdomSavingThrowAdvantage) + if (!advantage.isEmpty) { + advantage = " " + advantage + } + parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) + } + + if (charismaSavingThrowAdvantage != .none || charismaSavingThrowProficiency != .none) { + name = "Charisma" + bonus = charismaModifier + proficiencyBonusForType(charismaSavingThrowProficiency) + advantage = Monster.advantageLabelStringForType(charismaSavingThrowAdvantage) + if (!advantage.isEmpty) { + advantage = " " + advantage + } + parts.append(String(format: "%@ %+d%@", name, bonus, advantage)) + } + + return parts.joined(separator: ", ") + } + } + + + // MARK: Misc Helpers + + + // MARK: Skills + + var skillsDescription: String { + get { + let sortedSkills = self.skills.sorted {$0.name < $1.name} + return sortedSkills.reduce("") { + if $0 == "" { + return $1.skillDescription(forMonster: self) + } else { + return $0 + ", " + $1.skillDescription(forMonster: self) + } + } + } + } + + + // 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().map({$0.name}) + 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().map({$0.name}) + 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().map({$0.name}) + return StringHelper.oxfordJoin(sortedImmunities) + } + } + + var conditionImmunitiesDescription: String { + get { + let sortedImmunities = self.conditionImmunities.sorted().map({$0.name}) + return StringHelper.oxfordJoin(sortedImmunities) + } + } + + // MARK: OTHER + + var passivePerception: Int { + get { + let perceptionSkill = skills.first(where: { + StringHelper.safeEqualsIgnoreCase($0.name, "Perception") + }) + if (perceptionSkill == nil) { + return 10 + wisdomModifier + } else if (perceptionSkill!.proficiency == ProficiencyType.expertise) { + return 10 + wisdomModifier + proficiencyBonus + proficiencyBonus + } else if (perceptionSkill!.proficiency == ProficiencyType.proficient) { + return 10 + wisdomModifier + proficiencyBonus + } else { + return 10 + wisdomModifier + } + } + } + + var sensesDescription: String { + get { + var modifiedSenses = self.senses.sorted().map({$0.name}) + 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 languagesDescription: String { + get { + let spokenLanguages = + languages + .filter({ $0.speaks }) + .map({$0.name}) + .sorted() + let understoodLanguages = + languages + .filter({ !$0.speaks }) + .map({$0.name}) + .sorted() + + let understandsButText = understandsBut.isEmpty + ? "" + : 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 (challengeRating != .custom) { + return challengeRating.displayName + } else { + return customChallengeRating + } + } + } + + // MARK: End } From 68a6051dcaadfa0ab921c85ccea8d11427cc5b41 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 00:30:55 -0700 Subject: [PATCH 02/13] Makes MonsterDetail use a MonsterViewModel instead of the core data type Monster. --- .../Models/AbilityViewModel.swift | 2 +- iOS/MonsterCards/Models/SkillViewModel.swift | 21 +++++ iOS/MonsterCards/Views/Library.swift | 2 +- iOS/MonsterCards/Views/MonsterDetail.swift | 94 ++++++++++--------- iOS/MonsterCards/Views/Search.swift | 2 +- 5 files changed, 74 insertions(+), 47 deletions(-) diff --git a/iOS/MonsterCards/Models/AbilityViewModel.swift b/iOS/MonsterCards/Models/AbilityViewModel.swift index 5643800..474ed13 100644 --- a/iOS/MonsterCards/Models/AbilityViewModel.swift +++ b/iOS/MonsterCards/Models/AbilityViewModel.swift @@ -34,7 +34,7 @@ public class AbilityViewModel: NSObject, ObservableObject, Identifiable, NSSecur } } - public func renderedText(_ monster: Monster) -> String { + func renderedText(_ monster: MonsterViewModel) -> String { let strSave = monster.strengthModifier + monster.proficiencyBonus + 8 let dexSave = monster.dexterityModifier + monster.proficiencyBonus + 8 let conSave = monster.constitutionModifier + monster.proficiencyBonus + 8 diff --git a/iOS/MonsterCards/Models/SkillViewModel.swift b/iOS/MonsterCards/Models/SkillViewModel.swift index b9a2acc..eb32593 100644 --- a/iOS/MonsterCards/Models/SkillViewModel.swift +++ b/iOS/MonsterCards/Models/SkillViewModel.swift @@ -125,4 +125,25 @@ class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable { newSkill.wrappedAdvantage = advantage return newSkill } + + func modifier(forMonster: MonsterViewModel) -> Int { + let proficiencyBonus = Double(forMonster.proficiencyBonus) + let abilityScoreModifier = Double(forMonster.abilityModifierForAbilityScore(abilityScore)) + switch proficiency { + case .none: + return Int(abilityScoreModifier) + case .proficient: + return Int(abilityScoreModifier + proficiencyBonus) + case .expertise: + return Int(abilityScoreModifier + 2 * proficiencyBonus) + } + } + + func skillDescription(forMonster: MonsterViewModel) -> String { + var advantageLabel = Monster.advantageLabelStringForType(advantage) + if (advantageLabel != "") { + advantageLabel = " " + advantageLabel + } + return String(format: "%@ %+d%@", name, modifier(forMonster: forMonster), advantageLabel) + } } diff --git a/iOS/MonsterCards/Views/Library.swift b/iOS/MonsterCards/Views/Library.swift index fb2c7a4..99c9597 100644 --- a/iOS/MonsterCards/Views/Library.swift +++ b/iOS/MonsterCards/Views/Library.swift @@ -20,7 +20,7 @@ struct Library: View { var body: some View { NavigationView{ List(allMonsters) { monster in - NavigationLink(destination: MonsterDetail(monster: monster)) { + NavigationLink(destination: MonsterDetailWrapper(monster: monster)) { Text(monster.name ?? "") } } diff --git a/iOS/MonsterCards/Views/MonsterDetail.swift b/iOS/MonsterCards/Views/MonsterDetail.swift index 56af88a..5f660ad 100644 --- a/iOS/MonsterCards/Views/MonsterDetail.swift +++ b/iOS/MonsterCards/Views/MonsterDetail.swift @@ -72,7 +72,7 @@ struct SmallAbilityScore: View { } struct BasicInfoView: View { - @ObservedObject var monster: Monster + @ObservedObject var monster: MonsterViewModel var body: some View { let monsterMeta = monster.meta @@ -113,7 +113,7 @@ struct BasicInfoView: View { } struct AbilityScoresView: View { - @ObservedObject var monster: Monster + @ObservedObject var monster: MonsterViewModel var body: some View { SectionDivider() @@ -131,7 +131,7 @@ struct AbilityScoresView: View { } struct ResistancesAndImmunitiesView: View { - @ObservedObject var monster: Monster + @ObservedObject var monster: MonsterViewModel var body: some View { let monsterDamageVulnerabilitiesDescription = monster.damageVulnerabilitiesDescription @@ -178,7 +178,7 @@ struct ResistancesAndImmunitiesView: View { } struct SavingThrowsAndSkillsView: View { - @ObservedObject var monster: Monster + @ObservedObject var monster: MonsterViewModel var body: some View { let savingThrowsDescription = monster.savingThrowsDescription @@ -200,37 +200,28 @@ struct SavingThrowsAndSkillsView: View { } } -struct MonsterDetail: View { - let kTextColor: Color = Color(hex: 0x982818) +struct MonsterDetailView: View { + let kTextColor = Color(hex: 0x982818) - @ObservedObject var monster: Monster + var viewModel: MonsterViewModel var body: some View { + let monsterLanguagesDescription = viewModel.languagesDescription + let monsterChallengeRatingDescription = viewModel.challengeRatingDescription + ScrollView { // TODO: Consider adding an inmage here at the top VStack (alignment: .leading) { - let monsterLanguagesDescription = monster.languagesDescription - let monsterChallengeRatingDescription = monster.challengeRatingDescription - let monsterAbilities: [AbilityViewModel] = monster.abilities ?? [] - let monsterActions: [AbilityViewModel] = monster.actions ?? [] - let monsterLegendaryActions: [AbilityViewModel] = monster.legendaryActions ?? [] - - BasicInfoView(monster: monster) - // TODO: Find a way to hide unnecessarry dividiers. // if sections 0, 1, 2, and 3 are present there should be a divider between each of them // if section 1 is not present there should be one and only one divider between sections 0 and 2 as well as the one between 2 and 3 // if sections 1 and 2 are not present there should be a single divider between sections 0 and 3 - - - AbilityScoresView(monster: monster) + BasicInfoView(monster: viewModel) + AbilityScoresView(monster: viewModel) SectionDivider() - - SavingThrowsAndSkillsView(monster: monster) - - ResistancesAndImmunitiesView(monster: monster) - + SavingThrowsAndSkillsView(monster: viewModel) + ResistancesAndImmunitiesView(monster: viewModel) Group { // Languages if (!monsterLanguagesDescription.isEmpty) { @@ -248,27 +239,27 @@ struct MonsterDetail: View { // Proficiency Bonus LabeledField("Proficiency Bonus") { - Text(String(monster.proficiencyBonus)) + Text(String(viewModel.proficiencyBonus)) } // Abilities - if (monsterAbilities.count > 0) { - ForEach(monsterAbilities) { ability in + if (viewModel.abilities.count > 0) { + ForEach(viewModel.abilities) { ability in VStack { - Markdown(Document(ability.renderedText(monster))) + Markdown(Document(ability.renderedText(viewModel))) Divider() } } } - + // Actions - if (monsterActions.count > 0) { + if (viewModel.actions.count > 0) { VStack(alignment: .leading) { Text("Actions") .font(.system(size: 24, weight: .bold)) - ForEach(monsterActions) { action in + ForEach(viewModel.actions) { action in VStack { - Markdown(Document(action.renderedText(monster))) + Markdown(Document(action.renderedText(viewModel))) Divider() } } @@ -276,13 +267,13 @@ struct MonsterDetail: View { } // Legendary Actions - if (monsterLegendaryActions.count > 0) { + if (viewModel.legendaryActions.count > 0) { VStack(alignment: .leading) { Text("Legendary Actions") .font(.system(size: 20, weight: .bold)) - ForEach(monsterLegendaryActions) { action in + ForEach(viewModel.legendaryActions) { action in VStack { - Markdown(Document(action.renderedText(monster))) + Markdown(Document(action.renderedText(viewModel))) Divider() } } @@ -293,13 +284,28 @@ struct MonsterDetail: View { .padding(.horizontal) .foregroundColor(kTextColor) } - .toolbar(content: { - ToolbarItem(placement: .primaryAction) { - NavigationLink("Edit", destination: EditMonster(monster: monster)) - } - }) - .navigationTitle(monster.name ?? "") - .navigationBarTitleDisplayMode(.inline) + } +} + +struct MonsterDetailWrapper: View { + let kTextColor: Color = Color(hex: 0x982818) + + @ObservedObject var monster: Monster + @StateObject private var viewModel = MonsterViewModel() + + var body: some View { + + MonsterDetailView(viewModel: viewModel) + .onAppear(perform: { + viewModel.copyFromMonster(monster: monster) + }) + .toolbar(content: { + ToolbarItem(placement: .primaryAction) { + NavigationLink("Edit", destination: EditMonster(monster: monster)) + } + }) + .navigationTitle(monster.name ?? "") + .navigationBarTitleDisplayMode(.inline) } private func editMonster() { @@ -307,7 +313,7 @@ struct MonsterDetail: View { } } -struct MonsterDetail_Previews: PreviewProvider { +struct MonsterDetailWrapper_Previews: PreviewProvider { static var previews: some View { let context = PersistenceController.preview.container.viewContext let monster = Monster.init(context: context) @@ -352,10 +358,10 @@ struct MonsterDetail_Previews: PreviewProvider { ] return Group { - MonsterDetail(monster: monster) + MonsterDetailWrapper(monster: monster) .environment(\.managedObjectContext, context) .previewDevice("iPod touch (7th generation)") - MonsterDetail(monster: monster) + MonsterDetailWrapper(monster: monster) .environment(\.managedObjectContext, context) .previewDevice("iPad Pro (11-inch) (2nd generation)") } diff --git a/iOS/MonsterCards/Views/Search.swift b/iOS/MonsterCards/Views/Search.swift index b2c7fd7..0b2c295 100644 --- a/iOS/MonsterCards/Views/Search.swift +++ b/iOS/MonsterCards/Views/Search.swift @@ -59,7 +59,7 @@ struct Search: View { return false })) { monster in - NavigationLink(destination: MonsterDetail(monster: monster)) { + NavigationLink(destination: MonsterDetailWrapper(monster: monster)) { Text(monster.name ?? "") } } From 46682e1de7fa9e2840056fd0bd96a4f30dab6d71 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 00:31:18 -0700 Subject: [PATCH 03/13] Adds the monster name to MonsterDetail. --- iOS/MonsterCards/Views/MonsterDetail.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iOS/MonsterCards/Views/MonsterDetail.swift b/iOS/MonsterCards/Views/MonsterDetail.swift index 5f660ad..d3bcc5f 100644 --- a/iOS/MonsterCards/Views/MonsterDetail.swift +++ b/iOS/MonsterCards/Views/MonsterDetail.swift @@ -80,6 +80,11 @@ struct BasicInfoView: View { let monsterHitPoints = monster.hitPoints let monsterSpeed = monster.speed + if (!monster.name.isEmpty) { + Text(monster.name) + .font(.largeTitle) + } + // meta: "(large humanoid (elf) lawful evil" if (!monsterMeta.isEmpty) { Text(monsterMeta) From 94a3ceb9e0762698372b463892e435b81cbfa58c Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 01:04:40 -0700 Subject: [PATCH 04/13] Removes a bunch of commented out code. --- iOS/MonsterCards/Views/ContentView.swift | 61 ------------------------ 1 file changed, 61 deletions(-) diff --git a/iOS/MonsterCards/Views/ContentView.swift b/iOS/MonsterCards/Views/ContentView.swift index 09e1fa6..735781c 100644 --- a/iOS/MonsterCards/Views/ContentView.swift +++ b/iOS/MonsterCards/Views/ContentView.swift @@ -36,69 +36,8 @@ struct ContentView: View { } } } - -// @FetchRequest( -// sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)], -// animation: .default) -// private var items: FetchedResults -// -// var body: some View { -// List { -// ForEach(items) { item in -// Text("Item at \(item.timestamp!, formatter: itemFormatter)") -// } -// .onDelete(perform: deleteItems) -// } -// .toolbar { -// #if os(iOS) -// EditButton() -// #endif -// -// Button(action: addItem) { -// Label("Add Item", systemImage: "plus") -// } -// } -// } -// -// private func addItem() { -// withAnimation { -// let newItem = Item(context: viewContext) -// newItem.timestamp = Date() -// -// do { -// try viewContext.save() -// } catch { -// // Replace this implementation with code to handle the error appropriately. -// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. -// let nsError = error as NSError -// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") -// } -// } -// } -// -// private func deleteItems(offsets: IndexSet) { -// withAnimation { -// offsets.map { items[$0] }.forEach(viewContext.delete) -// -// do { -// try viewContext.save() -// } catch { -// // Replace this implementation with code to handle the error appropriately. -// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. -// let nsError = error as NSError -// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") -// } -// } -// } } -//private let itemFormatter: DateFormatter = { -// let formatter = DateFormatter() -// formatter.dateStyle = .short -// formatter.timeStyle = .medium -// return formatter -//}() - struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) From 9076f5896d3705a11f76093b00e0619f25f61f14 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 01:10:46 -0700 Subject: [PATCH 05/13] Creates new document type for .monster files. Sets the app as the default editor for .monster files. --- iOS/MonsterCards/Info.plist | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/iOS/MonsterCards/Info.plist b/iOS/MonsterCards/Info.plist index 1f41a42..9089a16 100644 --- a/iOS/MonsterCards/Info.plist +++ b/iOS/MonsterCards/Info.plist @@ -50,5 +50,76 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UISupportsDocumentBrowser + + LSSupportsOpeningDocumentsInPlace + + CFBundleDocumentTypes + + + CFBundleTypeName + Monster Data + CFBundleTypeRole + Editor + LSHandlerRank + Owner + LSItemContentTypes + + com.majinnaibu.Monster + + + + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.json + + UTTypeDescription + Monster data file + UTTypeIconFiles + + UTTypeIdentifier + com.majinnaibu.Monster + UTTypeTagSpecification + + public.filename-extension + + monster + + public.mime-type + + text/vnd.monstercards.monster + + + + + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.json + + UTTypeDescription + Monster data file + UTTypeIconFiles + + UTTypeIdentifier + com.majinnaibu.Monster + UTTypeTagSpecification + + public.filename-extension + + monster + + public.mime-type + + text/vnd.monstercards.monster + + + + From 6eca7efb0ccc30ce78a8252997ae33bbe68fe8a0 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 01:18:00 -0700 Subject: [PATCH 06/13] Adds an import view that shows when the app is opening a .monster file. --- iOS/MonsterCards/Views/ContentView.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/iOS/MonsterCards/Views/ContentView.swift b/iOS/MonsterCards/Views/ContentView.swift index 735781c..80b147f 100644 --- a/iOS/MonsterCards/Views/ContentView.swift +++ b/iOS/MonsterCards/Views/ContentView.swift @@ -10,7 +10,8 @@ import CoreData struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext - + @State private var isShowingImportDialog = false + var body: some View { TabView { Search() @@ -35,6 +36,14 @@ struct ContentView: View { Text("Library") } } + .onOpenURL(perform: beginImportingMonster) + .sheet(isPresented: self.$isShowingImportDialog) { + Text("Importing Monster") + } + } + + func beginImportingMonster(url: URL) { + self.isShowingImportDialog = true } } From cd2be8490f73ca7e4125722195ab5fac2a42a074 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 01:36:49 -0700 Subject: [PATCH 07/13] Adds a view to show the monster being imported and confirm the user wants to import that monster. --- iOS/MonsterCards.xcodeproj/project.pbxproj | 4 ++++ iOS/MonsterCards/Views/ContentView.swift | 10 +++++++-- iOS/MonsterCards/Views/ImportMonster.swift | 24 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 iOS/MonsterCards/Views/ImportMonster.swift diff --git a/iOS/MonsterCards.xcodeproj/project.pbxproj b/iOS/MonsterCards.xcodeproj/project.pbxproj index ac40118..63f0178 100644 --- a/iOS/MonsterCards.xcodeproj/project.pbxproj +++ b/iOS/MonsterCards.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ E20209FC25D8E19100EFE733 /* MonsterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */; }; E210B83A25B42D980083EAC5 /* MCProficiencyPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E210B83925B42D980083EAC5 /* MCProficiencyPicker.swift */; }; E210B83F25B42DAB0083EAC5 /* MCAdvantagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */; }; + E21661D12616E9A800117782 /* ImportMonster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21661D02616E9A800117782 /* ImportMonster.swift */; }; E216B791260C1FE800FB205F /* LanguageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B790260C1FE800FB205F /* LanguageViewModel.swift */; }; E216B799260C2DF200FB205F /* EditLanguages.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B798260C2DF200FB205F /* EditLanguages.swift */; }; E216B79E260C396F00FB205F /* EditLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B79D260C396F00FB205F /* EditLanguage.swift */; }; @@ -87,6 +88,7 @@ E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MonsterViewModel.swift; sourceTree = ""; }; E210B83925B42D980083EAC5 /* MCProficiencyPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCProficiencyPicker.swift; sourceTree = ""; }; E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCAdvantagePicker.swift; sourceTree = ""; }; + E21661D02616E9A800117782 /* ImportMonster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportMonster.swift; sourceTree = ""; }; E216B790260C1FE800FB205F /* LanguageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageViewModel.swift; sourceTree = ""; }; E216B798260C2DF200FB205F /* EditLanguages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLanguages.swift; sourceTree = ""; }; E216B79D260C396F00FB205F /* EditLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLanguage.swift; sourceTree = ""; }; @@ -254,6 +256,7 @@ E2B5285825B3028700AAA69E /* EditMonster.swift */, E24ACE642607F55D009BF703 /* EditSavingThrows.swift */, E2CB0DB226080C0500142591 /* EditSkill.swift */, + E21661D02616E9A800117782 /* ImportMonster.swift */, E24ACE692607F715009BF703 /* EditSkills.swift */, E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */, E2CB0DE526088CE400142591 /* EditStrings.swift */, @@ -435,6 +438,7 @@ buildActionMask = 2147483647; files = ( E20209FB25D8E19100EFE733 /* SkillViewModel.swift in Sources */, + E21661D12616E9A800117782 /* ImportMonster.swift in Sources */, E24ACE602607F45E009BF703 /* EditAbilityScores.swift in Sources */, E2570FC225B1AC550055B23B /* Persistence.swift in Sources */, E216B799260C2DF200FB205F /* EditLanguages.swift in Sources */, diff --git a/iOS/MonsterCards/Views/ContentView.swift b/iOS/MonsterCards/Views/ContentView.swift index 80b147f..e1a6d99 100644 --- a/iOS/MonsterCards/Views/ContentView.swift +++ b/iOS/MonsterCards/Views/ContentView.swift @@ -8,10 +8,15 @@ import SwiftUI import CoreData +struct ImportInfo { + var monster: MonsterViewModel = MonsterViewModel() +} + struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext + @State private var importInfo = ImportInfo() @State private var isShowingImportDialog = false - + var body: some View { TabView { Search() @@ -38,11 +43,12 @@ struct ContentView: View { } .onOpenURL(perform: beginImportingMonster) .sheet(isPresented: self.$isShowingImportDialog) { - Text("Importing Monster") + ImportMonster(monster: $importInfo.monster) } } func beginImportingMonster(url: URL) { + self.importInfo.monster.name = url.absoluteString self.isShowingImportDialog = true } } diff --git a/iOS/MonsterCards/Views/ImportMonster.swift b/iOS/MonsterCards/Views/ImportMonster.swift new file mode 100644 index 0000000..0f79dfa --- /dev/null +++ b/iOS/MonsterCards/Views/ImportMonster.swift @@ -0,0 +1,24 @@ +// +// ImportMonster.swift +// MonsterCards +// +// Created by Tom Hicks on 4/1/21. +// + +import SwiftUI + +struct ImportMonster: View { + @Binding var monster: MonsterViewModel + + var body: some View { + MonsterDetailView(viewModel: monster) + } +} + +struct ImportMonster_Previews: PreviewProvider { + static var previews: some View { + ImportMonster( + monster: .constant(MonsterViewModel()) + ) + } +} From c09c3327586c44e2ccec07ff3189098673c2d5ca Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 01:41:41 -0700 Subject: [PATCH 08/13] Adds DTO classes to help load a monster file from https://tetra-cube.com/dnd/dnd-statblock.html --- iOS/MonsterCards.xcodeproj/project.pbxproj | 24 ++ iOS/MonsterCards/Models/DamageTypeDTO.swift | 42 +++ iOS/MonsterCards/Models/LanguageDTO.swift | 38 +++ iOS/MonsterCards/Models/MonsterDTO.swift | 278 +++++++++++++++++++ iOS/MonsterCards/Models/SavingThrowDTO.swift | 38 +++ iOS/MonsterCards/Models/SkillDTO.swift | 42 +++ iOS/MonsterCards/Models/TraitDTO.swift | 42 +++ 7 files changed, 504 insertions(+) create mode 100644 iOS/MonsterCards/Models/DamageTypeDTO.swift create mode 100644 iOS/MonsterCards/Models/LanguageDTO.swift create mode 100644 iOS/MonsterCards/Models/MonsterDTO.swift create mode 100644 iOS/MonsterCards/Models/SavingThrowDTO.swift create mode 100644 iOS/MonsterCards/Models/SkillDTO.swift create mode 100644 iOS/MonsterCards/Models/TraitDTO.swift diff --git a/iOS/MonsterCards.xcodeproj/project.pbxproj b/iOS/MonsterCards.xcodeproj/project.pbxproj index 63f0178..7527c24 100644 --- a/iOS/MonsterCards.xcodeproj/project.pbxproj +++ b/iOS/MonsterCards.xcodeproj/project.pbxproj @@ -23,6 +23,12 @@ E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7BB260C691400FB205F /* EditChallengeRating.swift */; }; E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.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 */; }; + E219248526198A1200C84E12 /* SkillDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248426198A1200C84E12 /* SkillDTO.swift */; }; + E219248A26198A5400C84E12 /* TraitDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248926198A5400C84E12 /* TraitDTO.swift */; }; + E219248F26198A6A00C84E12 /* DamageTypeDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */; }; + E219249426198A8200C84E12 /* LanguageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219249326198A8200C84E12 /* LanguageDTO.swift */; }; E24ACE502607326E009BF703 /* EditBasicInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */; }; E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE552607EE94009BF703 /* EditArmor.swift */; }; E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */; }; @@ -96,6 +102,12 @@ E216B7BB260C691400FB205F /* EditChallengeRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditChallengeRating.swift; sourceTree = ""; }; E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChallengeRatingPicker.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 = ""; }; + E219248426198A1200C84E12 /* SkillDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkillDTO.swift; sourceTree = ""; }; + E219248926198A5400C84E12 /* TraitDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraitDTO.swift; sourceTree = ""; }; + E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageTypeDTO.swift; sourceTree = ""; }; + E219249326198A8200C84E12 /* LanguageDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageDTO.swift; sourceTree = ""; }; E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditBasicInfo.swift; sourceTree = ""; }; E24ACE552607EE94009BF703 /* EditArmor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditArmor.swift; sourceTree = ""; }; E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSpeed.swift; sourceTree = ""; }; @@ -289,6 +301,12 @@ E20209F925D8E19100EFE733 /* SkillViewModel.swift */, E2CB0DE0260887ED00142591 /* StringViewModel.swift */, E20209E625D8DEB600EFE733 /* Enums */, + E219247A261989B400C84E12 /* MonsterDTO.swift */, + E219249326198A8200C84E12 /* LanguageDTO.swift */, + E219247F261989F700C84E12 /* SavingThrowDTO.swift */, + E219248426198A1200C84E12 /* SkillDTO.swift */, + E219248926198A5400C84E12 /* TraitDTO.swift */, + E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */, ); path = Models; sourceTree = ""; @@ -444,6 +462,7 @@ E216B799260C2DF200FB205F /* EditLanguages.swift in Sources */, E2570FBB25B1AC520055B23B /* ContentView.swift in Sources */, E24ACE502607326E009BF703 /* EditBasicInfo.swift in Sources */, + E219249426198A8200C84E12 /* LanguageDTO.swift in Sources */, E254F90E260D19A0009295A5 /* EditTraits.swift in Sources */, E2570FC525B1AC550055B23B /* MonsterCards.xcdatamodeld in Sources */, E2182E6425B22F8A00DFAEF8 /* Monster+CoreDataClass.swift in Sources */, @@ -451,8 +470,10 @@ E210B83A25B42D980083EAC5 /* MCProficiencyPicker.swift in Sources */, E2570FF025B1ADC10055B23B /* Search.swift in Sources */, E257100925B1B2480055B23B /* MonsterDetail.swift in Sources */, + E219248526198A1200C84E12 /* SkillDTO.swift in Sources */, E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */, E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */, + E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */, E2CB0DD72608720000142591 /* StringHelper.swift in Sources */, E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */, E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */, @@ -469,6 +490,7 @@ E2CB0DCA26086E8300142591 /* ArmorType.swift in Sources */, E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */, E2CB0DE1260887ED00142591 /* StringViewModel.swift in Sources */, + E219248F26198A6A00C84E12 /* DamageTypeDTO.swift in Sources */, E20209F425D8E04300EFE733 /* ProficiencyType.swift in Sources */, E2CB0DC526086E5F00142591 /* SizeType.swift in Sources */, E254F906260D0818009295A5 /* AbilityViewModel.swift in Sources */, @@ -476,12 +498,14 @@ E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */, E2570FB925B1AC520055B23B /* MonsterCardsApp.swift in Sources */, E254F913260D1F6D009295A5 /* EditTrait.swift in Sources */, + E2192480261989F700C84E12 /* SavingThrowDTO.swift in Sources */, E216B7B7260C5A9800FB205F /* ChallengeRatingViewModel.swift in Sources */, E20209D325D8DD9600EFE733 /* Skill+CoreDataClass.swift in Sources */, E24ACE652607F55D009BF703 /* EditSavingThrows.swift in Sources */, E2BD702C25B3A8D70058ED69 /* MCTextField.swift in Sources */, E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */, E20209E825D8DEC100EFE733 /* AbilityScore.swift in Sources */, + E219248A26198A5400C84E12 /* TraitDTO.swift in Sources */, E210B83F25B42DAB0083EAC5 /* MCAdvantagePicker.swift in Sources */, E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */, E26CDA2B25CFB38E00E3F50D /* MCArmorTypePicker.swift in Sources */, diff --git a/iOS/MonsterCards/Models/DamageTypeDTO.swift b/iOS/MonsterCards/Models/DamageTypeDTO.swift new file mode 100644 index 0000000..62f7e92 --- /dev/null +++ b/iOS/MonsterCards/Models/DamageTypeDTO.swift @@ -0,0 +1,42 @@ +// +// DamageTypeDTO.swift +// MonsterCards +// +// Created by Tom Hicks on 3/28/21. +// + +import Foundation + +struct DamageTypeDTO { + var name: String + var note: String + var type: String +} + +private enum DamageTypeDTOCodingKeys: String, CodingKey { + case name = "name" + case note = "note" + case type = "type" +} + +extension DamageTypeDTO: Decodable { + + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: DamageTypeDTOCodingKeys.self) + self.name = (try? container.decode(String.self, forKey: .name)) ?? "" + self.note = (try? container.decode(String.self, forKey: .note)) ?? "" + self.type = (try? container.decode(String.self, forKey: .type)) ?? "" + } +} + +extension DamageTypeDTO: Encodable { + + func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: DamageTypeDTOCodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.note, forKey: .note) + try container.encode(self.type, forKey: .type) + } +} diff --git a/iOS/MonsterCards/Models/LanguageDTO.swift b/iOS/MonsterCards/Models/LanguageDTO.swift new file mode 100644 index 0000000..906b881 --- /dev/null +++ b/iOS/MonsterCards/Models/LanguageDTO.swift @@ -0,0 +1,38 @@ +// +// LanguageDTO.swift +// MonsterCards +// +// Created by Tom Hicks on 3/28/21. +// + +import Foundation + +struct LanguageDTO { + var name: String + var speaks: Bool +} + +private enum LanguageDTOCodingKeys: String, CodingKey { + case name = "name" + case speaks = "speaks" +} + +extension LanguageDTO: Decodable { + + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: LanguageDTOCodingKeys.self) + self.name = (try? container.decode(String.self, forKey: .name)) ?? "" + self.speaks = (try? container.decode(Bool.self, forKey: .speaks)) ?? false + } +} + +extension LanguageDTO: Encodable { + + func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: LanguageDTOCodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.speaks, forKey: .speaks) + } +} diff --git a/iOS/MonsterCards/Models/MonsterDTO.swift b/iOS/MonsterCards/Models/MonsterDTO.swift new file mode 100644 index 0000000..ec598c3 --- /dev/null +++ b/iOS/MonsterCards/Models/MonsterDTO.swift @@ -0,0 +1,278 @@ +// +// MonsterDTO.swift +// MonsterCards +// +// Created by Tom Hicks on 3/28/21. +// + +import Foundation + +struct MonsterDTO { + var name: String + var type: String + var alignment: String + var size: String + var hitDice: Int + var armorName: String + var otherArmorDesc: String + var shieldBonus: Int + var natArmorBonus: Int + var speed: Int + var burrowSpeed: Int + var climbSpeed: Int + var flySpeed: Int + var hover: Bool + var swimSpeed: Int + var speedDesc: String + var customSpeed: Bool + var strPoints: Int + var dexPoints: Int + var conPoints: Int + var intPoints: Int + var wisPoints: Int + var chaPoints: Int + var cr: String + var customCr: String + var customProf: Int + var hpText: String + var sthrows: [SavingThrowDTO] + var skills: [SkillDTO] + var actions: [TraitDTO] + var legendaryDescription: String + var legendaries: [TraitDTO] + var reactions: [TraitDTO]// TODO: verify this + var abilities: [TraitDTO] + var damageTypes: [DamageTypeDTO] + var conditions: [DamageTypeDTO] // TODO: figure this out + var languages: [LanguageDTO] + var telepathy: Int + var understandsBut: String + var blindsight: Int + var blind: Bool + var darkvision: Int + var tremorsense: Int + var truesight: Int + var tag: String + var customHP: Bool + var isLegendary: Bool + var isLair: Bool + var lairDescription: String + var lairDescriptionEnd: String + var isRegional: Bool + var regionalDescription: String + var regionalDescriptionEnd: String +// var properties: [???] // TODO: figure this out + var lairs: [TraitDTO] + var regionals: [TraitDTO] + var specialDamage: [DamageTypeDTO] + var shortName: String + var doubleColumns: Bool + var separationPoint: Int + var damage: [DamageTypeDTO] // TODO: figure this out +} + +enum MonsterDTOCodingKeys: String, CodingKey { + case name = "name" + case type = "type" + case alignment = "alignment" + case size = "size" + case hitDice = "hitDice" + case armorName = "armorName" + case otherArmorDesc = "otherArmorDesc" + case shieldBonus = "shieldBonus" + case natArmorBonus = "natArmorBonus" + case speed = "speed" + case burrowSpeed = "burrowSpeed" + case climbSpeed = "climbSpeed" + case flySpeed = "flySpeed" + case hover = "hover" + case swimSpeed = "swimSpeed" + case speedDesc = "speedDesc" + case customSpeed = "customSpeed" + case strPoints = "strPoints" + case dexPoints = "dexPoints" + case conPoints = "conPoints" + case intPoints = "intPoints" + case wisPoints = "wisPoints" + case chaPoints = "chaPoints" + case cr = "cr" + case customCr = "customCr" + case customProf = "customProf" + case hpText = "hpText" + case sthrows = "sthrows" + case skills = "skills" + case actions = "actions" + case legendaryDescription = "legendaryDescription" + case legendaries = "legendaries" + case reactions = "reactions" + case abilities = "abilities" + case damageTypes = "damageTypes" + case conditions = "conditions" + case languages = "languages" + case telepathy = "telepathy" + case understandsBut = "understandsBut" + case blindsight = "blindsight" + case blind = "blind" + case darkvision = "darkvision" + case tremorsense = "tremorsense" + case truesight = "truesight" + case tag = "tag" + case customHP = "customHP" + case isLegendary = "isLegendary" + case isLair = "isLair" + case lairDescription = "lairDescription" + case lairDescriptionEnd = "lairDescriptionEnd" + case isRegional = "isRegional" + case regionalDescription = "regionalDescription" + case regionalDescriptionEnd = "regionalDescriptionEnd" + case properties = "properties" + case lairs = "lairs" + case regionals = "regionals" + case specialDamage = "specialDamage" + case shortName = "shortName" + case doubleColumns = "doubleColumns" + case separationPoint = "separationPoint" + case damage = "damage" +} + +extension MonsterDTO: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: MonsterDTOCodingKeys.self) + self.name = (try? container.decode(String.self, forKey: .name)) ?? "Imported Monster" + self.type = (try? container.decode(String.self, forKey: .type)) ?? "" + self.alignment = (try? container.decode(String.self, forKey: .alignment)) ?? "" + self.size = (try? container.decode(String.self, forKey: .size)) ?? "" + self.hitDice = (try? container.decode(Int.self, forKey: .hitDice)) ?? 0 + self.armorName = (try? container.decode(String.self, forKey: .armorName)) ?? "" + self.otherArmorDesc = (try? container.decode(String.self, forKey: .otherArmorDesc)) ?? "" + self.shieldBonus = (try? container.decode(Int.self, forKey: .shieldBonus)) ?? 0 + self.natArmorBonus = (try? container.decode(Int.self, forKey: .natArmorBonus)) ?? 0 + self.speed = (try? container.decode(Int.self, forKey: .speed)) ?? 0 + self.burrowSpeed = (try? container.decode(Int.self, forKey: .burrowSpeed)) ?? 0 + self.climbSpeed = (try? container.decode(Int.self, forKey: .climbSpeed)) ?? 0 + self.flySpeed = (try? container.decode(Int.self, forKey: .flySpeed)) ?? 0 + self.hover = (try? container.decode(Bool.self, forKey: .hover)) ?? false + self.swimSpeed = (try? container.decode(Int.self, forKey: .swimSpeed)) ?? 0 + self.speedDesc = (try? container.decode(String.self, forKey: .speedDesc)) ?? "" + self.customSpeed = (try? container.decode(Bool.self, forKey: .customSpeed)) ?? false + self.strPoints = (try? container.decode(Int.self, forKey: .strPoints)) ?? 0 + self.dexPoints = (try? container.decode(Int.self, forKey: .dexPoints)) ?? 0 + self.conPoints = (try? container.decode(Int.self, forKey: .conPoints)) ?? 0 + self.intPoints = (try? container.decode(Int.self, forKey: .intPoints)) ?? 0 + self.wisPoints = (try? container.decode(Int.self, forKey: .wisPoints)) ?? 0 + self.chaPoints = (try? container.decode(Int.self, forKey: .chaPoints)) ?? 0 + self.cr = (try? container.decode(String.self, forKey: .cr)) ?? "" + self.customCr = (try? container.decode(String.self, forKey: .customCr)) ?? "" + self.customProf = (try? container.decode(Int.self, forKey: .customProf)) ?? 0 + self.hpText = (try? container.decode(String.self, forKey: .hpText)) ?? "" + self.legendaryDescription = (try? container.decode(String.self, forKey: .legendaryDescription)) ?? "" + self.understandsBut = (try? container.decode(String.self, forKey: .understandsBut)) ?? "" + self.tag = (try? container.decode(String.self, forKey: .tag)) ?? "" + self.lairDescription = (try? container.decode(String.self, forKey: .lairDescription)) ?? "" + self.lairDescriptionEnd = (try? container.decode(String.self, forKey: .lairDescriptionEnd)) ?? "" + self.regionalDescription = (try? container.decode(String.self, forKey: .regionalDescription)) ?? "" + self.regionalDescriptionEnd = (try? container.decode(String.self, forKey: .regionalDescriptionEnd)) ?? "" + self.shortName = (try? container.decode(String.self, forKey: .shortName)) ?? "" + + self.telepathy = (try? container.decode(Int.self, forKey: .telepathy)) ?? 0 + self.blindsight = (try? container.decode(Int.self, forKey: .blindsight)) ?? 0 + self.darkvision = (try? container.decode(Int.self, forKey: .darkvision)) ?? 0 + self.tremorsense = (try? container.decode(Int.self, forKey: .tremorsense)) ?? 0 + self.truesight = (try? container.decode(Int.self, forKey: .truesight)) ?? 0 + self.separationPoint = (try? container.decode(Int.self, forKey: .separationPoint)) ?? 0 + + self.blind = (try? container.decode(Bool.self, forKey: .blind)) ?? false + self.customHP = (try? container.decode(Bool.self, forKey: .customHP)) ?? false + self.isLegendary = (try? container.decode(Bool.self, forKey: .isLegendary)) ?? false + self.isLair = (try? container.decode(Bool.self, forKey: .isLair)) ?? false + self.isRegional = (try? container.decode(Bool.self, forKey: .isRegional)) ?? false + self.doubleColumns = (try? container.decode(Bool.self, forKey: .doubleColumns)) ?? false + + // properties is always an empty array + +// self.properties = (try? container.decode([String].self, forKey: .properties)) ?? [] + self.lairs = (try? container.decode([TraitDTO].self, forKey: .lairs)) ?? [] + self.regionals = (try? container.decode([TraitDTO].self, forKey: .regionals)) ?? [] + self.specialDamage = (try? container.decode([DamageTypeDTO].self, forKey: .specialDamage)) ?? [] + self.damage = (try? container.decode([DamageTypeDTO].self, forKey: .damage)) ?? [] + self.conditions = (try? container.decode([DamageTypeDTO].self, forKey: .conditions)) ?? [] + + + self.sthrows = (try? container.decode([SavingThrowDTO].self, forKey: .sthrows)) ?? [] + self.skills = (try? container.decode([SkillDTO].self, forKey: .skills)) ?? [] + self.actions = (try? container.decode([TraitDTO].self, forKey: .actions)) ?? [] + self.legendaries = (try? container.decode([TraitDTO].self, forKey: .legendaries)) ?? [] + self.reactions = (try? container.decode([TraitDTO].self, forKey: .reactions)) ?? [] + self.abilities = (try? container.decode([TraitDTO].self, forKey: .abilities)) ?? [] + self.damageTypes = (try? container.decode([DamageTypeDTO].self, forKey: .damageTypes)) ?? [] + self.languages = (try? container.decode([LanguageDTO].self, forKey: .languages)) ?? [] + } +} + +extension MonsterDTO: Encodable { + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: MonsterDTOCodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.type, forKey: .type) + try container.encode(self.alignment, forKey: .alignment) + try container.encode(self.size, forKey: .size) + try container.encode(self.hitDice, forKey: .hitDice) + try container.encode(self.armorName, forKey: .armorName) + try container.encode(self.otherArmorDesc, forKey: .otherArmorDesc) + try container.encode(self.shieldBonus, forKey: .shieldBonus) + try container.encode(self.natArmorBonus, forKey: .natArmorBonus) + try container.encode(self.speed, forKey: .speed) + try container.encode(self.burrowSpeed, forKey: .burrowSpeed) + try container.encode(self.climbSpeed, forKey: .climbSpeed) + try container.encode(self.flySpeed, forKey: .flySpeed) + try container.encode(self.hover, forKey: .hover) + try container.encode(self.swimSpeed, forKey: .swimSpeed) + try container.encode(self.speedDesc, forKey: .speedDesc) + try container.encode(self.customSpeed, forKey: .customSpeed) + try container.encode(self.strPoints, forKey: .strPoints) + try container.encode(self.dexPoints, forKey: .dexPoints) + try container.encode(self.conPoints, forKey: .conPoints) + try container.encode(self.intPoints, forKey: .intPoints) + try container.encode(self.wisPoints, forKey: .wisPoints) + try container.encode(self.chaPoints, forKey: .chaPoints) + try container.encode(self.cr, forKey: .cr) + try container.encode(self.customCr, forKey: .customCr) + try container.encode(self.customProf, forKey: .customProf) + try container.encode(self.hpText, forKey: .hpText) + try container.encode(self.sthrows, forKey: .sthrows) + try container.encode(self.skills, forKey: .skills) + try container.encode(self.actions, forKey: .actions) + try container.encode(self.legendaryDescription, forKey: .legendaryDescription) + try container.encode(self.legendaries, forKey: .legendaries) + try container.encode(self.reactions, forKey: .reactions) + try container.encode(self.abilities, forKey: .abilities) + try container.encode(self.damageTypes, forKey: .damageTypes) + try container.encode(self.conditions, forKey: .conditions) + try container.encode(self.languages, forKey: .languages) + try container.encode(self.telepathy, forKey: .telepathy) + try container.encode(self.understandsBut, forKey: .understandsBut) + try container.encode(self.blindsight, forKey: .blindsight) + try container.encode(self.blind, forKey: .blind) + try container.encode(self.darkvision, forKey: .darkvision) + try container.encode(self.tremorsense, forKey: .tremorsense) + try container.encode(self.truesight, forKey: .truesight) + try container.encode(self.tag, forKey: .tag) + try container.encode(self.customHP, forKey: .customHP) + try container.encode(self.isLegendary, forKey: .isLegendary) + try container.encode(self.isLair, forKey: .isLair) + try container.encode(self.lairDescription, forKey: .lairDescription) + try container.encode(self.lairDescriptionEnd, forKey: .lairDescriptionEnd) + try container.encode(self.isRegional, forKey: .isRegional) + try container.encode(self.regionalDescription, forKey: .regionalDescription) + try container.encode(self.regionalDescriptionEnd, forKey: .regionalDescriptionEnd) +// try container.encode(self.properties, forKey: .properties) + try container.encode(self.lairs, forKey: .lairs) + try container.encode(self.regionals, forKey: .regionals) + try container.encode(self.specialDamage, forKey: .specialDamage) + try container.encode(self.shortName, forKey: .shortName) + try container.encode(self.doubleColumns, forKey: .doubleColumns) + try container.encode(self.separationPoint, forKey: .separationPoint) + try container.encode(self.damage, forKey: .damage) + } +} diff --git a/iOS/MonsterCards/Models/SavingThrowDTO.swift b/iOS/MonsterCards/Models/SavingThrowDTO.swift new file mode 100644 index 0000000..6851f3f --- /dev/null +++ b/iOS/MonsterCards/Models/SavingThrowDTO.swift @@ -0,0 +1,38 @@ +// +// SavingThrowDTO.swift +// MonsterCards +// +// Created by Tom Hicks on 3/28/21. +// + +import Foundation + +struct SavingThrowDTO { + var name: String + var order: Int +} + +private enum SavingThrowDTOCodingKeys: String, CodingKey { + case name = "name" + case order = "order" +} + +extension SavingThrowDTO: Decodable { + + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: SavingThrowDTOCodingKeys.self) + self.name = (try? container.decode(String.self, forKey: .name)) ?? "" + self.order = (try? container.decode(Int.self, forKey: .order)) ?? 0 + } +} + +extension SavingThrowDTO: Encodable { + + func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: SavingThrowDTOCodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.order, forKey: .order) + } +} diff --git a/iOS/MonsterCards/Models/SkillDTO.swift b/iOS/MonsterCards/Models/SkillDTO.swift new file mode 100644 index 0000000..e547d7f --- /dev/null +++ b/iOS/MonsterCards/Models/SkillDTO.swift @@ -0,0 +1,42 @@ +// +// SkillDTO.swift +// MonsterCards +// +// Created by Tom Hicks on 3/28/21. +// + +import Foundation + +struct SkillDTO { + var name: String + var stat: String + var note: String +} + +private enum SkillDTOCodingKeys: String, CodingKey { + case name = "name" + case stat = "stat" + case note = "note" +} + +extension SkillDTO: Decodable { + + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: SkillDTOCodingKeys.self) + self.name = (try? container.decode(String.self, forKey: .name)) ?? "" + self.note = (try? container.decode(String.self, forKey: .note)) ?? "" + self.stat = (try? container.decode(String.self, forKey: .stat)) ?? "" + } +} + +extension SkillDTO: Encodable { + + func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: SkillDTOCodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.note, forKey: .note) + try container.encode(self.stat, forKey: .stat) + } +} diff --git a/iOS/MonsterCards/Models/TraitDTO.swift b/iOS/MonsterCards/Models/TraitDTO.swift new file mode 100644 index 0000000..933eed4 --- /dev/null +++ b/iOS/MonsterCards/Models/TraitDTO.swift @@ -0,0 +1,42 @@ +// +// TraitDTO.swift +// MonsterCards +// +// Created by Tom Hicks on 3/28/21. +// + +import Foundation + +struct TraitDTO { + var name: String + var note: String + var desc: String +} + +private enum TraitDTOCodingKeys: String, CodingKey { + case name = "name" + case note = "note" + case desc = "desc" +} + +extension TraitDTO: Decodable { + + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: TraitDTOCodingKeys.self) + self.name = (try? container.decode(String.self, forKey: .name)) ?? "" + self.note = (try? container.decode(String.self, forKey: .note)) ?? "" + self.desc = (try? container.decode(String.self, forKey: .desc)) ?? "" + } +} + +extension TraitDTO: Encodable { + + func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: TraitDTOCodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.note, forKey: .note) + try container.encode(self.desc, forKey: .desc) + } +} From 2e6b8bb3772fe6479451e2e4cb51949cbc0989c4 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 01:46:58 -0700 Subject: [PATCH 09/13] Loads the imported monster from file and sets the new monster's name from the loaded data. --- iOS/MonsterCards/Views/ContentView.swift | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/iOS/MonsterCards/Views/ContentView.swift b/iOS/MonsterCards/Views/ContentView.swift index e1a6d99..335e61e 100644 --- a/iOS/MonsterCards/Views/ContentView.swift +++ b/iOS/MonsterCards/Views/ContentView.swift @@ -48,9 +48,24 @@ struct ContentView: View { } func beginImportingMonster(url: URL) { - self.importInfo.monster.name = url.absoluteString - self.isShowingImportDialog = true + + // TOOD: only do this if the file name ends in .json or .monster + + let decoder = JSONDecoder() + do { + let data = try Data(contentsOf: url) + let monsterDTO = try decoder.decode(MonsterDTO.self, from: data) + print(String(format: "Loaded monster: %@", monsterDTO.name)) + // TODO: check for some minimal set of properties to ensure this is the expected json schema + self.importInfo.monster = MonsterImportHelper.import5ESBMonster(monsterDTO) + // TODO: throw or set an err here and don't set isShowingImportDialog to true if the file didn't match any of our supported monster schemas. + self.isShowingImportDialog = true + } catch let error as NSError { + // TODO: handle this better + print(error) + } } + } struct ContentView_Previews: PreviewProvider { From f9647eaf976db5ebfcdf9681028be04ff8dff6d4 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 03:13:19 -0700 Subject: [PATCH 10/13] Fixes toggling a shield in the editor not affecting the displayed AC. --- iOS/MonsterCards/Models/MonsterViewModel.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOS/MonsterCards/Models/MonsterViewModel.swift b/iOS/MonsterCards/Models/MonsterViewModel.swift index 62cb57a..44188ef 100644 --- a/iOS/MonsterCards/Models/MonsterViewModel.swift +++ b/iOS/MonsterCards/Models/MonsterViewModel.swift @@ -19,7 +19,9 @@ class MonsterViewModel: ObservableObject { @Published var hasCustomHP: Bool @Published var customHP: String @Published var armorType: ArmorType - @Published var hasShield: Bool + @Published var hasShield: Bool { + didSet { shieldBonus = hasShield ? 2 : 0 } + } @Published var naturalArmorBonus: Int64 @Published var customArmor: String @Published var baseSpeed: Int64 From b519b80209975559c5841766c5fe3e43525f085e Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 03:27:00 -0700 Subject: [PATCH 11/13] Adds lair actions, regional actions, and reactions to the data model. Renames baseSpeed to walkSpeed. --- .../Models/Monster+CoreDataClass.swift | 4 +- .../Models/MonsterViewModel.swift | 38 +++++++++++++++---- iOS/MonsterCards/Models/SkillViewModel.swift | 7 ++++ .../MonsterCards.xcdatamodel/contents | 7 +++- iOS/MonsterCards/Views/EditMonster.swift | 8 +++- iOS/MonsterCards/Views/EditSpeed.swift | 4 +- iOS/MonsterCards/Views/MonsterDetail.swift | 6 ++- 7 files changed, 58 insertions(+), 16 deletions(-) diff --git a/iOS/MonsterCards/Models/Monster+CoreDataClass.swift b/iOS/MonsterCards/Models/Monster+CoreDataClass.swift index 90dffba..21d6c8d 100644 --- a/iOS/MonsterCards/Models/Monster+CoreDataClass.swift +++ b/iOS/MonsterCards/Models/Monster+CoreDataClass.swift @@ -100,8 +100,8 @@ public class Monster: NSManagedObject { } else { var parts: [String] = [] - if (baseSpeed > 0) { - parts.append("\(baseSpeed) ft.") + if (walkSpeed > 0) { + parts.append("\(walkSpeed) ft.") } if (burrowSpeed > 0) { parts.append("burrow \(burrowSpeed) ft.") diff --git a/iOS/MonsterCards/Models/MonsterViewModel.swift b/iOS/MonsterCards/Models/MonsterViewModel.swift index 44188ef..a99d3e7 100644 --- a/iOS/MonsterCards/Models/MonsterViewModel.swift +++ b/iOS/MonsterCards/Models/MonsterViewModel.swift @@ -24,7 +24,7 @@ class MonsterViewModel: ObservableObject { } @Published var naturalArmorBonus: Int64 @Published var customArmor: String - @Published var baseSpeed: Int64 + @Published var walkSpeed: Int64 @Published var burrowSpeed: Int64 @Published var climbSpeed: Int64 @Published var flySpeed: Int64 @@ -65,6 +65,10 @@ class MonsterViewModel: ObservableObject { @Published var abilities: [AbilityViewModel] @Published var actions: [AbilityViewModel] @Published var legendaryActions: [AbilityViewModel] + @Published var lairActions: [AbilityViewModel] + @Published var regionalActions: [AbilityViewModel] + @Published var reactions: [AbilityViewModel] + @Published var isBlind: Bool private var shieldBonus: Int private var otherArmorDescription: String @@ -97,7 +101,7 @@ class MonsterViewModel: ObservableObject { self.hasShield = false self.naturalArmorBonus = 0 self.customArmor = "" - self.baseSpeed = 0 + self.walkSpeed = 0 self.burrowSpeed = 0 self.climbSpeed = 0 self.flySpeed = 0 @@ -138,6 +142,10 @@ class MonsterViewModel: ObservableObject { self.abilities = [] self.actions = [] self.legendaryActions = [] + self.lairActions = [] + self.regionalActions = [] + self.reactions = [] + self.isBlind = false // Private properties self.shieldBonus = 0 @@ -162,7 +170,7 @@ class MonsterViewModel: ObservableObject { self.hasShield = monster.hasShield self.naturalArmorBonus = monster.naturalArmorBonus self.customArmor = monster.customArmor ?? "" - self.baseSpeed = monster.baseSpeed + self.walkSpeed = monster.walkSpeed self.burrowSpeed = monster.burrowSpeed self.climbSpeed = monster.climbSpeed self.flySpeed = monster.flySpeed @@ -193,6 +201,7 @@ class MonsterViewModel: ObservableObject { 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() @@ -230,6 +239,15 @@ class MonsterViewModel: ObservableObject { 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) @@ -249,7 +267,7 @@ class MonsterViewModel: ObservableObject { monster.hasShield = hasShield monster.naturalArmorBonus = naturalArmorBonus monster.customArmor = customArmor - monster.baseSpeed = baseSpeed + monster.walkSpeed = walkSpeed monster.burrowSpeed = burrowSpeed monster.climbSpeed = climbSpeed monster.flySpeed = flySpeed @@ -280,6 +298,7 @@ class MonsterViewModel: ObservableObject { monster.challengeRatingEnum = challengeRating monster.customChallengeRating = customChallengeRating monster.customProficiencyBonus = customProficiencyBonus + monster.isBlind = isBlind // Remove missing skills from raw monster monster.skills?.forEach {s in @@ -316,6 +335,12 @@ class MonsterViewModel: ObservableObject { 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 } @@ -382,8 +407,8 @@ class MonsterViewModel: ObservableObject { } else { var parts: [String] = [] - if (baseSpeed > 0) { - parts.append("\(baseSpeed) ft.") + if (walkSpeed > 0) { + parts.append("\(walkSpeed) ft.") } if (burrowSpeed > 0) { parts.append("burrow \(burrowSpeed) ft.") @@ -467,7 +492,6 @@ class MonsterViewModel: ObservableObject { var armorClassDescription: String { get { - let hasShield = shieldBonus != 0 var armorClassTotal = 0 if (armorType == ArmorType.none) { // 10 + dexMod + 2 for shieldBonus "15" or "17 (shield)" diff --git a/iOS/MonsterCards/Models/SkillViewModel.swift b/iOS/MonsterCards/Models/SkillViewModel.swift index eb32593..557cb2e 100644 --- a/iOS/MonsterCards/Models/SkillViewModel.swift +++ b/iOS/MonsterCards/Models/SkillViewModel.swift @@ -65,6 +65,13 @@ class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable { } } + 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 { diff --git a/iOS/MonsterCards/MonsterCards.xcdatamodeld/MonsterCards.xcdatamodel/contents b/iOS/MonsterCards/MonsterCards.xcdatamodeld/MonsterCards.xcdatamodel/contents index 39ff24a..d51cf96 100644 --- a/iOS/MonsterCards/MonsterCards.xcdatamodeld/MonsterCards.xcdatamodel/contents +++ b/iOS/MonsterCards/MonsterCards.xcdatamodeld/MonsterCards.xcdatamodel/contents @@ -5,7 +5,6 @@ - @@ -39,11 +38,14 @@ + + + @@ -57,6 +59,7 @@ + @@ -70,7 +73,7 @@ - + \ No newline at end of file diff --git a/iOS/MonsterCards/Views/EditMonster.swift b/iOS/MonsterCards/Views/EditMonster.swift index 4b27545..7a838f5 100644 --- a/iOS/MonsterCards/Views/EditMonster.swift +++ b/iOS/MonsterCards/Views/EditMonster.swift @@ -9,7 +9,11 @@ import CoreData import SwiftUI struct EditMonster: View { - // TODO: add challengeRating/challengeRatingEnum and customChallengeRating maybe in basicInfo + // TODO: Add challengeRating/challengeRatingEnum and customChallengeRating maybe in basicInfo + // TODO: Add a way to edit the monster being blind. Probably a header section to the senses section. + // TODO: Add a way to edit lair actions + // TODO: Add a way to edit regional actions + // TODO: Add a way to edit reactions @Environment(\.presentationMode) var presentationMode: Binding @Environment(\.managedObjectContext) private var viewContext @@ -141,7 +145,7 @@ struct EditMonster_Previews: PreviewProvider { monster.hitDice = 6 monster.hasCustomHP = true monster.customHP = "12 (1d10)+2" - monster.baseSpeed = 5 + monster.walkSpeed = 5 monster.burrowSpeed = 10 monster.climbSpeed = 15 monster.flySpeed = 20 diff --git a/iOS/MonsterCards/Views/EditSpeed.swift b/iOS/MonsterCards/Views/EditSpeed.swift index 6c99fb8..e935bd0 100644 --- a/iOS/MonsterCards/Views/EditSpeed.swift +++ b/iOS/MonsterCards/Views/EditSpeed.swift @@ -12,12 +12,12 @@ struct EditSpeed: View { var body: some View { List { - // Number bound to monster.baseSpeed + // Number bound to monster.walkSpeed MCStepperField( label: "Base", step: 5, suffix: " ft.", - value: $monsterViewModel.baseSpeed) + value: $monsterViewModel.walkSpeed) // Number bound to monster.burrowSpeed MCStepperField( diff --git a/iOS/MonsterCards/Views/MonsterDetail.swift b/iOS/MonsterCards/Views/MonsterDetail.swift index d3bcc5f..c6ed665 100644 --- a/iOS/MonsterCards/Views/MonsterDetail.swift +++ b/iOS/MonsterCards/Views/MonsterDetail.swift @@ -293,6 +293,10 @@ struct MonsterDetailView: View { } struct MonsterDetailWrapper: View { + // TODO: Add display for when the monster is blind + // TODO: Add display for lair actions + // TODO: Add display for regional actions + // TODO: Add display for reactions let kTextColor: Color = Color(hex: 0x982818) @ObservedObject var monster: Monster @@ -330,7 +334,7 @@ struct MonsterDetailWrapper_Previews: PreviewProvider { monster.hitDice = 6 monster.hasCustomHP = true monster.customHP = "12 (1d10)+2" - monster.baseSpeed = 5 + monster.walkSpeed = 5 monster.burrowSpeed = 10 monster.climbSpeed = 15 monster.flySpeed = 20 From a43d2f11ea9ed2d135ba1ff954ae1fe6d181efa9 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 03:27:50 -0700 Subject: [PATCH 12/13] Adds monster import helper. --- .../Helpers/MonsterImportHelper.swift | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 iOS/MonsterCards/Helpers/MonsterImportHelper.swift diff --git a/iOS/MonsterCards/Helpers/MonsterImportHelper.swift b/iOS/MonsterCards/Helpers/MonsterImportHelper.swift new file mode 100644 index 0000000..42be564 --- /dev/null +++ b/iOS/MonsterCards/Helpers/MonsterImportHelper.swift @@ -0,0 +1,111 @@ +// +// MonsterImportHelper.swift +// MonsterCards +// +// Created by Tom Hicks on 4/3/21. +// + +import Foundation + +extension MonsterViewModel { + func maybeAddSense(_ name: String, _ distance: Int) { + if (distance > 0) { + senses.append(StringViewModel("\(name): \(distance) ft.")) + } + } +} + +class MonsterImportHelper { + static func import5ESBMonster(_ monsterDTO: MonsterDTO) -> MonsterViewModel { + let monster = MonsterViewModel() + + monster.name = monsterDTO.name + monster.size = monsterDTO.size + monster.type = monsterDTO.type + monster.subType = monsterDTO.tag + monster.alignment = monsterDTO.alignment + monster.hitDice = Int64(monsterDTO.hitDice) + monster.armorType = ArmorType(rawValue: monsterDTO.armorName) ?? .none + monster.hasShield = monsterDTO.shieldBonus != 0 + monster.naturalArmorBonus = Int64(monsterDTO.natArmorBonus) + monster.customArmor = monsterDTO.otherArmorDesc + monster.walkSpeed = Int64(monsterDTO.speed) + monster.burrowSpeed = Int64(monsterDTO.burrowSpeed) + monster.climbSpeed = Int64(monsterDTO.climbSpeed) + monster.flySpeed = Int64(monsterDTO.flySpeed) + monster.swimSpeed = Int64(monsterDTO.swimSpeed) + monster.canHover = monsterDTO.hover + monster.hasCustomHP = monsterDTO.customHP + monster.customHP = monsterDTO.hpText + monster.hasCustomSpeed = monsterDTO.customSpeed + monster.customSpeed = monsterDTO.speedDesc + monster.strengthScore = Int64(monsterDTO.strPoints) + monster.dexterityScore = Int64(monsterDTO.dexPoints) + monster.constitutionScore = Int64(monsterDTO.conPoints) + monster.intelligenceScore = Int64(monsterDTO.intPoints) + monster.wisdomScore = Int64(monsterDTO.wisPoints) + monster.charismaScore = Int64(monsterDTO.chaPoints) + monster.isBlind = monsterDTO.blind + monster.maybeAddSense("blindsight", monsterDTO.blindsight) + monster.maybeAddSense("darkvision", monsterDTO.darkvision) + monster.maybeAddSense("tremorsense", monsterDTO.tremorsense) + monster.maybeAddSense("turesight", monsterDTO.truesight) + monster.telepathy = Int64(monsterDTO.telepathy) + monster.challengeRating = ChallengeRating(rawValue: monsterDTO.cr) ?? ChallengeRating.one + monster.customChallengeRating = monsterDTO.customCr + monster.customProficiencyBonus = Int64(monsterDTO.customProf) + // TODO: Think about adding legendary properties isLegendary, legendariesDescription, isLair, lairDescription, lairDescriptionEnd, isRegional, regionalDescription, regionalDescriptionEnd + monster.abilities = monsterDTO.abilities.map({AbilityViewModel($0.name, $0.desc)}) + monster.actions = monsterDTO.actions.map({AbilityViewModel($0.name, $0.desc)}) + monster.reactions = monsterDTO.reactions.map({AbilityViewModel($0.name, $0.desc)}) + monster.legendaryActions = monsterDTO.legendaries.map({AbilityViewModel($0.name, $0.desc)}) + monster.lairActions = monsterDTO.lairs.map({AbilityViewModel($0.name, $0.desc)}) + monster.regionalActions = monsterDTO.regionals.map({AbilityViewModel($0.name, $0.desc)}) + monsterDTO.sthrows.forEach({ + switch $0.name { + case "str": + monster.strengthSavingThrowProficiency = .proficient + case "dex": + monster.dexteritySavingThrowProficiency = .proficient + case "con": + monster.constitutionSavingThrowProficiency = .proficient + case "int": + monster.intelligenceSavingThrowProficiency = .proficient + case "wis": + monster.wisdomSavingThrowProficiency = .proficient + case "cha": + monster.charismaSavingThrowProficiency = .proficient + default: + break + } + }) + monster.skills = monsterDTO.skills.map({ + // TODO: consider using a lookup table to make fixing missing stats easier + SkillViewModel( + $0.name, + AbilityScore(rawValue: $0.stat) ?? .dexterity, + $0.note == " (ex)" ? .expertise : .proficient) + }) + monster.damageImmunities = monsterDTO.damageTypes + .filter({$0.type == "i" || $0.type == " (Immune)"}) + .map({StringViewModel($0.name)}) + monster.damageResistances = monsterDTO.damageTypes + .filter({$0.type == "r" || $0.type == " (Resistant)"}) + .map({StringViewModel($0.name)}) + monster.damageVulnerabilities = monsterDTO.damageTypes + .filter({$0.type == "v" || $0.type == " (Vulnerable)"}) + .map({StringViewModel($0.name)}) + monster.conditionImmunities = monsterDTO.conditions.map({StringViewModel($0.name)}) + monster.languages = monsterDTO.languages + .map({ + LanguageViewModel($0.name, $0.speaks) + }) + monster.understandsBut = monsterDTO.understandsBut + // TODO: add shortName or nickname + // monster.shortName = monsterDTO.shortName + + // TODO: look into what goes in specialdamage and damage + + return monster + } +} From ab218fbe34d3ab556ce6fbc6c2f90a52e0c83ceb Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sun, 4 Apr 2021 03:28:20 -0700 Subject: [PATCH 13/13] Reorganizes the project tree and adds a task. --- iOS/MonsterCards.xcodeproj/project.pbxproj | 18 +++++++++++------- iOS/MonsterCards/Models/MonsterViewModel.swift | 2 ++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/iOS/MonsterCards.xcodeproj/project.pbxproj b/iOS/MonsterCards.xcodeproj/project.pbxproj index 7527c24..dac0873 100644 --- a/iOS/MonsterCards.xcodeproj/project.pbxproj +++ b/iOS/MonsterCards.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ E219248A26198A5400C84E12 /* TraitDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248926198A5400C84E12 /* TraitDTO.swift */; }; E219248F26198A6A00C84E12 /* DamageTypeDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */; }; E219249426198A8200C84E12 /* LanguageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219249326198A8200C84E12 /* LanguageDTO.swift */; }; + E219249926198E0D00C84E12 /* MonsterImportHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219249826198E0D00C84E12 /* MonsterImportHelper.swift */; }; E24ACE502607326E009BF703 /* EditBasicInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */; }; E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE552607EE94009BF703 /* EditArmor.swift */; }; E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */; }; @@ -108,6 +109,7 @@ E219248926198A5400C84E12 /* TraitDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraitDTO.swift; sourceTree = ""; }; E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageTypeDTO.swift; sourceTree = ""; }; E219249326198A8200C84E12 /* LanguageDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageDTO.swift; sourceTree = ""; }; + E219249826198E0D00C84E12 /* MonsterImportHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterImportHelper.swift; sourceTree = ""; }; E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditBasicInfo.swift; sourceTree = ""; }; E24ACE552607EE94009BF703 /* EditArmor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditArmor.swift; sourceTree = ""; }; E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSpeed.swift; sourceTree = ""; }; @@ -268,12 +270,12 @@ E2B5285825B3028700AAA69E /* EditMonster.swift */, E24ACE642607F55D009BF703 /* EditSavingThrows.swift */, E2CB0DB226080C0500142591 /* EditSkill.swift */, - E21661D02616E9A800117782 /* ImportMonster.swift */, E24ACE692607F715009BF703 /* EditSkills.swift */, E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */, E2CB0DE526088CE400142591 /* EditStrings.swift */, E254F912260D1F6D009295A5 /* EditTrait.swift */, E254F90D260D19A0009295A5 /* EditTraits.swift */, + E21661D02616E9A800117782 /* ImportMonster.swift */, E2570FFE25B1AE180055B23B /* Library.swift */, E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */, E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */, @@ -294,19 +296,19 @@ children = ( E254F905260D0818009295A5 /* AbilityViewModel.swift */, E216B7B6260C5A9800FB205F /* ChallengeRatingViewModel.swift */, + E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */, + E20209E625D8DEB600EFE733 /* Enums */, + E219249326198A8200C84E12 /* LanguageDTO.swift */, E216B790260C1FE800FB205F /* LanguageViewModel.swift */, E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */, + E219247A261989B400C84E12 /* MonsterDTO.swift */, E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */, + E219247F261989F700C84E12 /* SavingThrowDTO.swift */, E20209D225D8DD9600EFE733 /* Skill+CoreDataClass.swift */, + E219248426198A1200C84E12 /* SkillDTO.swift */, E20209F925D8E19100EFE733 /* SkillViewModel.swift */, E2CB0DE0260887ED00142591 /* StringViewModel.swift */, - E20209E625D8DEB600EFE733 /* Enums */, - E219247A261989B400C84E12 /* MonsterDTO.swift */, - E219249326198A8200C84E12 /* LanguageDTO.swift */, - E219247F261989F700C84E12 /* SavingThrowDTO.swift */, - E219248426198A1200C84E12 /* SkillDTO.swift */, E219248926198A5400C84E12 /* TraitDTO.swift */, - E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */, ); path = Models; sourceTree = ""; @@ -315,6 +317,7 @@ isa = PBXGroup; children = ( E2D473FC25B532C900CB36D7 /* Color+Hex.swift */, + E219249826198E0D00C84E12 /* MonsterImportHelper.swift */, E2CB0DD62608720000142591 /* StringHelper.swift */, ); path = Helpers; @@ -477,6 +480,7 @@ E2CB0DD72608720000142591 /* StringHelper.swift in Sources */, E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */, E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */, + E219249926198E0D00C84E12 /* MonsterImportHelper.swift in Sources */, E2CB0DC026086E3C00142591 /* ChallengeRating.swift in Sources */, E257100425B1AF4A0055B23B /* SearchBar.swift in Sources */, E20209F525D8E04300EFE733 /* AdvantageType.swift in Sources */, diff --git a/iOS/MonsterCards/Models/MonsterViewModel.swift b/iOS/MonsterCards/Models/MonsterViewModel.swift index a99d3e7..61031a4 100644 --- a/iOS/MonsterCards/Models/MonsterViewModel.swift +++ b/iOS/MonsterCards/Models/MonsterViewModel.swift @@ -10,6 +10,8 @@ import CoreData class MonsterViewModel: ObservableObject { + // TODO: Determine whether to prefer Int or Int64 for these fields and switch as many as possible to the winner. + @Published var name: String @Published var size: String @Published var type: String