diff --git a/MonsterCards.xcodeproj/project.pbxproj b/MonsterCards.xcodeproj/project.pbxproj index fe26fee..6478340 100644 --- a/MonsterCards.xcodeproj/project.pbxproj +++ b/MonsterCards.xcodeproj/project.pbxproj @@ -42,6 +42,10 @@ E2BD703125B3BBB90058ED69 /* MCStepperField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2BD703025B3BBB90058ED69 /* MCStepperField.swift */; }; E2CB0DB326080C0500142591 /* EditSkill.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DB226080C0500142591 /* EditSkill.swift */; }; E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */; }; + E2CB0DC026086E3C00142591 /* ChallengeRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DBF26086E3C00142591 /* ChallengeRating.swift */; }; + E2CB0DC526086E5F00142591 /* SizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DC426086E5F00142591 /* SizeType.swift */; }; + E2CB0DCA26086E8300142591 /* ArmorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DC926086E8300142591 /* ArmorType.swift */; }; + E2CB0DD72608720000142591 /* StringHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DD62608720000142591 /* StringHelper.swift */; }; E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D473FC25B532C900CB36D7 /* Color+Hex.swift */; }; /* End PBXBuildFile section */ @@ -104,6 +108,10 @@ E2BD703025B3BBB90058ED69 /* MCStepperField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCStepperField.swift; sourceTree = ""; }; E2CB0DB226080C0500142591 /* EditSkill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSkill.swift; sourceTree = ""; }; E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCAbilityScorePicker.swift; sourceTree = ""; }; + E2CB0DBF26086E3C00142591 /* ChallengeRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeRating.swift; sourceTree = ""; }; + E2CB0DC426086E5F00142591 /* SizeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeType.swift; sourceTree = ""; }; + E2CB0DC926086E8300142591 /* ArmorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArmorType.swift; sourceTree = ""; }; + E2CB0DD62608720000142591 /* StringHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringHelper.swift; sourceTree = ""; }; E2D473FC25B532C900CB36D7 /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -137,7 +145,10 @@ children = ( E20209E725D8DEC100EFE733 /* AbilityScore.swift */, E20209F325D8E04300EFE733 /* AdvantageType.swift */, + E2CB0DC926086E8300142591 /* ArmorType.swift */, + E2CB0DBF26086E3C00142591 /* ChallengeRating.swift */, E20209F225D8E04300EFE733 /* ProficiencyType.swift */, + E2CB0DC426086E5F00142591 /* SizeType.swift */, ); path = Enums; sourceTree = ""; @@ -248,6 +259,7 @@ isa = PBXGroup; children = ( E2D473FC25B532C900CB36D7 /* Color+Hex.swift */, + E2CB0DD62608720000142591 /* StringHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -393,8 +405,10 @@ E257100925B1B2480055B23B /* MonsterDetail.swift in Sources */, E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */, E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */, + E2CB0DD72608720000142591 /* StringHelper.swift in Sources */, E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */, E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */, + E2CB0DC026086E3C00142591 /* ChallengeRating.swift in Sources */, E257100425B1AF4A0055B23B /* SearchBar.swift in Sources */, E20209F525D8E04300EFE733 /* AdvantageType.swift in Sources */, E24ACE6A2607F715009BF703 /* EditSkills.swift in Sources */, @@ -402,8 +416,10 @@ E2570FFF25B1AE180055B23B /* Library.swift in Sources */, E2BD703125B3BBB90058ED69 /* MCStepperField.swift in Sources */, E2CB0DB326080C0500142591 /* EditSkill.swift in Sources */, + E2CB0DCA26086E8300142591 /* ArmorType.swift in Sources */, E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */, E20209F425D8E04300EFE733 /* ProficiencyType.swift in Sources */, + E2CB0DC526086E5F00142591 /* SizeType.swift in Sources */, E2570FFA25B1AE020055B23B /* Collections.swift in Sources */, E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */, E2570FB925B1AC520055B23B /* MonsterCardsApp.swift in Sources */, diff --git a/MonsterCards/Helpers/StringHelper.swift b/MonsterCards/Helpers/StringHelper.swift new file mode 100644 index 0000000..2c0fe6e --- /dev/null +++ b/MonsterCards/Helpers/StringHelper.swift @@ -0,0 +1,40 @@ +// +// StringHelper.swift +// MonsterCards +// +// Created by Tom Hicks on 3/21/21. +// + +import Foundation + +class StringHelper { + static func oxfordJoin( + _ strings: [String], + _ separator: String, + _ lastSeparator: String, + _ onlySeparator: String + ) -> String { + let numStrings = strings.count + if (numStrings < 1) { + return ""; + } else if (numStrings == 2) { + return strings[0] + onlySeparator + strings[1] + } else { + var joined = "" + var index = 0 + let lastIndex = numStrings - 1 + + strings.forEach { + if index > 0 && index < lastIndex { + joined.append(separator) + } else if (index > 0 && index >= lastIndex) { + joined.append(lastSeparator) + } + joined.append($0) + index = index + 1 + } + + return joined + } + } +} diff --git a/MonsterCards/Models/Enums/ArmorType.swift b/MonsterCards/Models/Enums/ArmorType.swift new file mode 100644 index 0000000..5c9a58d --- /dev/null +++ b/MonsterCards/Models/Enums/ArmorType.swift @@ -0,0 +1,50 @@ +// +// ArmorType.swift +// MonsterCards +// +// Created by Tom Hicks on 3/21/21. +// + +import Foundation + +enum ArmorType: String, CaseIterable, Identifiable { + case none = "none" + case naturalArmor = "natural armor" + case mageArmor = "mage armor" + case padded = "padded" + case leather = "leather" + case studdedLeather = "studded" + case hide = "hide" + case chainShirt = "chain shirt" + case scaleMail = "scale mail" + case breastplate = "breastplate" + case halfPlate = "half plate" + case ringMail = "ring mail" + case chainMail = "chain mail" + case splintMail = "splint" + case plateMail = "plate" + case other = "other" + + var id: ArmorType { self } + + var displayName: String { + switch self { + case .none: return "None" + case .naturalArmor: return "Natural Armor" + case .mageArmor: return "Mage Armor" + case .padded: return "Padded" + case .leather: return "Leather" + case .studdedLeather: return "Studded Leather" + case .hide: return "Hide" + case .chainShirt: return "Chain Shirt" + case .scaleMail: return "Scale Mail" + case .breastplate: return "Breastplate" + case .halfPlate: return "Half Plate" + case .ringMail: return "Ring Mail" + case .chainMail: return "Chain Mail" + case .splintMail: return "Splint Mail" + case .plateMail: return "Plate Mail" + case .other: return "Other" + } + } +} diff --git a/MonsterCards/Models/Enums/ChallengeRating.swift b/MonsterCards/Models/Enums/ChallengeRating.swift new file mode 100644 index 0000000..2150d80 --- /dev/null +++ b/MonsterCards/Models/Enums/ChallengeRating.swift @@ -0,0 +1,53 @@ +// +// ChallengeRating.swift +// MonsterCards +// +// Created by Tom Hicks on 3/21/21. +// + +import Foundation + +enum ChallengeRating: String, CaseIterable, Identifiable { + case zero = "0" + case oneEighth = "1/8" + case oneQuarter = "1/4" + case oneHalf = "1/2" + case one = "1" + case two = "2" + case three = "3" + case four = "4" + case five = "5" + case six = "6" + case seven = "7" + case eight = "8" + case nine = "9" + case ten = "10" + case eleven = "11" + case twelve = "12" + case thirteen = "13" + case fourteen = "14" + case fifteen = "15" + case sixteen = "16" + case seventeen = "17" + case eighteen = "18" + case nineteen = "19" + case twenty = "20" + case twentyOne = "21" + case twentyTwo = "22" + case twentyThree = "23" + case twentyFour = "24" + case twentyFive = "25" + case twentySix = "26" + case twentySeven = "27" + case twentyEight = "28" + case twentyNine = "29" + case thirty = "30" + case custom = "*" + + var id: ChallengeRating { self } + + // Probably don't need this + var displayName: String { + return rawValue + } +} diff --git a/MonsterCards/Models/Enums/SizeType.swift b/MonsterCards/Models/Enums/SizeType.swift new file mode 100644 index 0000000..98d227d --- /dev/null +++ b/MonsterCards/Models/Enums/SizeType.swift @@ -0,0 +1,30 @@ +// +// SizeType.swift +// MonsterCards +// +// Created by Tom Hicks on 3/21/21. +// + +import Foundation + +enum SizeType: String, CaseIterable, Identifiable { + case tiny = "tiny" + case small = "small" + case medium = "medium" + case large = "large" + case huge = "huge" + case gargantuan = "gargantuan" + + var id: SizeType { self } + + var displayName: String { + switch self { + case .tiny: return "Tiny" + case .small: return "Small" + case .medium: return "Medium" + case .large: return "Large" + case .huge: return "Huge" + case .gargantuan: return "gargantuan" + } + } +} diff --git a/MonsterCards/Models/Monster+CoreDataClass.swift b/MonsterCards/Models/Monster+CoreDataClass.swift index 2aa019b..ddbbbc5 100644 --- a/MonsterCards/Models/Monster+CoreDataClass.swift +++ b/MonsterCards/Models/Monster+CoreDataClass.swift @@ -625,115 +625,84 @@ public class Monster: NSManagedObject { } } + // MARK: OTHER + + var damageVulnerabilitiesArray: [String] { + get { + return ["Fire", "Poison", "Psychic"] + } + } + + var damageVulnerabilitiesDescription: String { + get { + let sortedVulnerabilities = self.damageVulnerabilitiesArray.sorted() + + return StringHelper.oxfordJoin(sortedVulnerabilities, ", ", ", and ", " and ") + } + } + + var damageResistancesArray: [String] { + get { + return ["Ice", "Electric"] + } + } + + var damageResistancesDescription: String { + get { + let sortedResistances = self.damageResistancesArray.sorted() + return StringHelper.oxfordJoin(sortedResistances, ", ", ", and ", " and ") + } + } + + var damageImmunitiesArray: [String] { + get { + return ["Slashing"] + } + } + + var damageImmunitiesDescription: String { + get { + let sortedImmunities = self.damageImmunitiesArray.sorted() + return StringHelper.oxfordJoin(sortedImmunities, ", ", ", and ", " and ") + } + } + + var conditionImmunitiesArray: [String] { + get { + return [] + } + } + + var conditionImmunitiesDescription: String { + get { + let sortedImmunities = self.conditionImmunitiesArray.sorted() + return StringHelper.oxfordJoin(sortedImmunities, ", ", ", and ", " and ") + } + } + + var languagesArray: [String] { + get { + return ["Common", "Goblin"] + } + } + + var languagesDescription: String { + get { + let sortedLanguages = self.languagesArray.sorted() + return StringHelper.oxfordJoin(sortedLanguages, ", ", ", and ", " and ") + } + } + + var challengeRatingDescription: String { + get { + return ""; + } + } + // MARK: End } -enum ArmorType: String, CaseIterable, Identifiable { - case none = "none" - case naturalArmor = "natural armor" - case mageArmor = "mage armor" - case padded = "padded" - case leather = "leather" - case studdedLeather = "studded" - case hide = "hide" - case chainShirt = "chain shirt" - case scaleMail = "scale mail" - case breastplate = "breastplate" - case halfPlate = "half plate" - case ringMail = "ring mail" - case chainMail = "chain mail" - case splintMail = "splint" - case plateMail = "plate" - case other = "other" - - var id: ArmorType { self } - - var displayName: String { - switch self { - case .none: return "None" - case .naturalArmor: return "Natural Armor" - case .mageArmor: return "Mage Armor" - case .padded: return "Padded" - case .leather: return "Leather" - case .studdedLeather: return "Studded Leather" - case .hide: return "Hide" - case .chainShirt: return "Chain Shirt" - case .scaleMail: return "Scale Mail" - case .breastplate: return "Breastplate" - case .halfPlate: return "Half Plate" - case .ringMail: return "Ring Mail" - case .chainMail: return "Chain Mail" - case .splintMail: return "Splint Mail" - case .plateMail: return "Plate Mail" - case .other: return "Other" - } - } -} -enum SizeType: String, CaseIterable, Identifiable { - case tiny = "tiny" - case small = "small" - case medium = "medium" - case large = "large" - case huge = "huge" - case gargantuan = "gargantuan" - - var id: SizeType { self } - - var displayName: String { - switch self { - case .tiny: return "Tiny" - case .small: return "Small" - case .medium: return "Medium" - case .large: return "Large" - case .huge: return "Huge" - case .gargantuan: return "gargantuan" - } - } -} -enum ChallengeRating: String, CaseIterable, Identifiable { - case zero = "0" - case oneEighth = "1/8" - case oneQuarter = "1/4" - case oneHalf = "1/2" - case one = "1" - case two = "2" - case three = "3" - case four = "4" - case five = "5" - case six = "6" - case seven = "7" - case eight = "8" - case nine = "9" - case ten = "10" - case eleven = "11" - case twelve = "12" - case thirteen = "13" - case fourteen = "14" - case fifteen = "15" - case sixteen = "16" - case seventeen = "17" - case eighteen = "18" - case nineteen = "19" - case twenty = "20" - case twentyOne = "21" - case twentyTwo = "22" - case twentyThree = "23" - case twentyFour = "24" - case twentyFive = "25" - case twentySix = "26" - case twentySeven = "27" - case twentyEight = "28" - case twentyNine = "29" - case thirty = "30" - case custom = "*" - - var id: ChallengeRating { self } - - // Probably don't need this - var displayName: String { - return rawValue - } -} + diff --git a/MonsterCards/Views/MonsterDetail.swift b/MonsterCards/Views/MonsterDetail.swift index bfed12c..b2786b7 100644 --- a/MonsterCards/Views/MonsterDetail.swift +++ b/MonsterCards/Views/MonsterDetail.swift @@ -70,6 +70,127 @@ struct SmallAbilityScore: View { } } +struct BasicInfoView: View { + @ObservedObject var monster: Monster + + var body: some View { + let monsterMeta = monster.meta + let monsterArmorClassDescription = monster.armorClassDescription + let monsterHitPoints = monster.hitPoints + let monsterSpeed = monster.speed + + // meta: "(large humanoid (elf) lawful evil" + if (!monsterMeta.isEmpty) { + Text(monsterMeta) + .font(.subheadline) + .foregroundColor(.secondary) + } + + SectionDivider() + + // AC + if (!monsterArmorClassDescription.isEmpty) { + LabeledField("Armor Class") { + Text(monsterArmorClassDescription)// armor class + } + } + + // HP + if (!monsterHitPoints.isEmpty) { + LabeledField("Hit Points") { + Text(monsterHitPoints) // hit points + } + } + + // Speed + if (!monsterSpeed.isEmpty) { + LabeledField("Speed") { + Text(monsterSpeed) // speed + } + } + } +} + +struct AbilityScoresView: View { + @ObservedObject var monster: Monster + + var body: some View { + SectionDivider() + + // Ability Scores + HStack { + SmallAbilityScore("STR", monster.strengthScore, monster.strengthModifier) + SmallAbilityScore("DEX", monster.dexterityScore, monster.dexterityModifier) + SmallAbilityScore("CON", monster.constitutionScore, monster.constitutionModifier) + SmallAbilityScore("INT", monster.intelligenceScore, monster.intelligenceModifier) + SmallAbilityScore("WIS", monster.wisdomScore, monster.wisdomModifier) + SmallAbilityScore("CHA", monster.charismaScore, monster.charismaModifier) + } + } +} + +struct ResistancesAndImmunitiesView: View { + @ObservedObject var monster: Monster + + var body: some View { + let monsterDamageVulnerabilitiesDescription = monster.damageVulnerabilitiesDescription + let monsterDamageResistancesDescription = monster.damageResistancesDescription + let monsterDamageImmunitiesDescription = monster.damageImmunitiesDescription + let monsterConditionImmunitiesDescription = monster.conditionImmunitiesDescription + + // Damage Vulnerabilities + if (!monsterDamageVulnerabilitiesDescription.isEmpty) { + LabeledField("Damage Vulnerabilities") { + Text(monsterDamageVulnerabilitiesDescription) + } + } + + // Damage Resistances + if (!monsterDamageResistancesDescription.isEmpty) { + LabeledField("Damage Resistances") { + Text(monsterDamageResistancesDescription) + } + } + + // Damage Immunities + if (!monsterDamageImmunitiesDescription.isEmpty) { + LabeledField("Damage Immunities") { + Text(monsterDamageImmunitiesDescription) + } + } + + // Condition Immunities + if (!monsterConditionImmunitiesDescription.isEmpty) { + LabeledField("Condition Immunities") { + Text(monsterConditionImmunitiesDescription) + } + } + } +} + +struct SavingThrowsAndSkillsView: View { + @ObservedObject var monster: Monster + + var body: some View { + let savingThrowsDescription = monster.savingThrowsDescription + let skillsDescription = monster.skillsDescription + + // Saving Throws + if (!savingThrowsDescription.isEmpty) { + LabeledField("Saving Throws") { + Text(savingThrowsDescription) + } + } + + // Skills + if (!skillsDescription.isEmpty) { + LabeledField("Skills") { + Text(skillsDescription) + } + } + } +} + struct MonsterDetail: View { let kTextColor: Color = Color(hex: 0x982818) @@ -78,72 +199,45 @@ struct MonsterDetail: View { var body: some View { ScrollView { VStack (alignment: .leading) { - let monsterMeta = monster.meta - let monsterArmorClassDescription = monster.armorClassDescription - let monsterHitPoints = monster.hitPoints - let monsterSpeed = monster.speed + let monsterLanguagesDescription = monster.languagesDescription + let monsterChallengeRatingDescription = monster.challengeRatingDescription - if (!monsterMeta.isEmpty) { - // meta: "(large humanoid (elf) lawful evil" - Text(monsterMeta) - .font(.subheadline) - .foregroundColor(.secondary) - } + 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 - SectionDivider() + - if (!monsterArmorClassDescription.isEmpty) { - // AC - LabeledField("Armor Class") { - Text(monsterArmorClassDescription)// armor class - } - } - - if (!monsterHitPoints.isEmpty) { - // HP - LabeledField("Hit Points") { - Text(monsterHitPoints) // hit points - } - } - - // Speed - if (!monsterSpeed.isEmpty) { - LabeledField("Speed") { - Text(monsterSpeed) // speed - } - } + AbilityScoresView(monster: monster) SectionDivider() - // Ability Scores - HStack { - SmallAbilityScore("STR", monster.strengthScore, monster.strengthModifier) - SmallAbilityScore("DEX", monster.dexterityScore, monster.dexterityModifier) - SmallAbilityScore("CON", monster.constitutionScore, monster.constitutionModifier) - SmallAbilityScore("INT", monster.intelligenceScore, monster.intelligenceModifier) - SmallAbilityScore("WIS", monster.wisdomScore, monster.wisdomModifier) - SmallAbilityScore("CHA", monster.charismaScore, monster.charismaModifier) - } - - SectionDivider() + SavingThrowsAndSkillsView(monster: monster) - let savingThrowsDescription = monster.savingThrowsDescription - if (!savingThrowsDescription.isEmpty) { - LabeledField("Saving Throws") { - Text(savingThrowsDescription) + ResistancesAndImmunitiesView(monster: monster) + + // Languages + if (!monsterLanguagesDescription.isEmpty) { + LabeledField("Languages") { + Text(monsterLanguagesDescription) } } - let skillsDescription = monster.skillsDescription - if (!skillsDescription.isEmpty) { - LabeledField("Skills") { - Text(skillsDescription) + // Challenge Rating + if (!monsterChallengeRatingDescription.isEmpty) { + LabeledField("Challenge Rating") { + Text(monsterChallengeRatingDescription) } } + + // Abilities + + // Actions + + // Legendary Actions + } .padding(.horizontal) .foregroundColor(kTextColor)