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