Skip to content

Commit 9e38e29

Browse files
committed
Implement damage vulnerability, resistance, and immunity and condition immunity
The schema permits these to go recursive, and the bestiary data uses it in a few places, so make sure that's fully supported and tested.
1 parent ec9ae3d commit 9e38e29

4 files changed

Lines changed: 788 additions & 2 deletions

File tree

Sources/FifthEdition/Bestiary.swift

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ public struct Creature: Equatable, Sendable {
4646
public var xpLair: Int? = nil
4747
}
4848

49+
public enum ConditionImmunity: Equatable, Hashable, Sendable {
50+
case condition(Condition)
51+
case conditional(Set<ConditionImmunity>, preNote: String? = nil, note: String? = nil, conditional: Bool? = nil)
52+
case special(String)
53+
}
54+
4955
@MemberwiseInit(.public)
5056
public struct CreatureType: Equatable, Sendable {
5157
public enum Choice: Equatable, Sendable {
@@ -70,6 +76,24 @@ public struct Creature: Equatable, Sendable {
7076
public var note: String? = nil
7177
}
7278

79+
public enum DamageImmunity: Equatable, Hashable, Sendable {
80+
case damage(DamageType)
81+
case conditional(Set<DamageImmunity>, preNote: String? = nil, note: String? = nil, conditional: Bool? = nil)
82+
case special(String)
83+
}
84+
85+
public enum DamageResistance: Equatable, Hashable, Sendable {
86+
case damage(DamageType)
87+
case conditional(Set<DamageResistance>, preNote: String? = nil, note: String? = nil, conditional: Bool? = nil)
88+
case special(String)
89+
}
90+
91+
public enum DamageVulnerability: Equatable, Hashable, Sendable {
92+
case damage(DamageType)
93+
case conditional(Set<DamageVulnerability>, preNote: String? = nil, note: String? = nil, conditional: Bool? = nil)
94+
case special(String)
95+
}
96+
7397
@MemberwiseInit(.public)
7498
public struct Gear: Equatable, Sendable {
7599
@Init(label: "_")
@@ -185,6 +209,11 @@ public struct Creature: Equatable, Sendable {
185209
public var languages: Set<String>? = nil
186210
public var proficiencyBonus: String? = nil
187211
public var challengeRating: ChallengeRating? = nil
212+
213+
public var damageVulnerability: Set<DamageVulnerability>? = nil
214+
public var damageResistance: Set<DamageResistance>? = nil
215+
public var damageImmunity: Set<DamageImmunity>? = nil
216+
public var conditionImmunity: Set<ConditionImmunity>? = nil
188217
}
189218

190219
// MARK: - Codable
@@ -227,6 +256,10 @@ extension Creature: Codable {
227256
case languages
228257
case proficiencyBonus = "pbNote"
229258
case challengeRating = "cr"
259+
case damageVulnerability = "vulnerable"
260+
case damageResistance = "resist"
261+
case damageImmunity = "immune"
262+
case conditionImmunity = "conditionImmune"
230263
}
231264
}
232265

@@ -377,6 +410,57 @@ extension Creature.ChallengeRating: Codable {
377410

378411
}
379412

413+
extension Creature.ConditionImmunity: Codable {
414+
enum CodingKeys: String, CodingKey {
415+
case conditions = "conditionImmune"
416+
case preNote
417+
case note
418+
case conditional = "cond"
419+
}
420+
421+
enum SpecialCodingKeys: String, CodingKey {
422+
case special
423+
}
424+
425+
public init(from decoder: any Decoder) throws {
426+
if let container = try? decoder.singleValueContainer(),
427+
let value = try? container.decode(Condition.self)
428+
{
429+
self = .condition(value)
430+
} else if let container = try? decoder.container(keyedBy: SpecialCodingKeys.self),
431+
let value = try? container.decode(String.self, forKey: .special)
432+
{
433+
self = .special(value)
434+
} else {
435+
let container = try decoder.container(keyedBy: CodingKeys.self)
436+
self = .conditional(
437+
try container.decode(Set<Creature.ConditionImmunity>.self, forKey: .conditions),
438+
preNote: try container.decodeIfPresent(String.self, forKey: .preNote),
439+
note: try container.decodeIfPresent(String.self, forKey: .note),
440+
conditional: try container.decodeIfPresent(Bool.self, forKey: .conditional)
441+
)
442+
}
443+
}
444+
445+
public func encode(to encoder: any Encoder) throws {
446+
switch self {
447+
case .condition(let value):
448+
var container = encoder.singleValueContainer()
449+
try container.encode(value)
450+
case .special(let value):
451+
var container = encoder.container(keyedBy: SpecialCodingKeys.self)
452+
try container.encode(value, forKey: .special)
453+
case .conditional(let conditions, let preNote, let note, let cond):
454+
var container = encoder.container(keyedBy: CodingKeys.self)
455+
try container.encode(conditions, forKey: .conditions)
456+
try container.encodeIfPresent(preNote, forKey: .preNote)
457+
try container.encodeIfPresent(note, forKey: .note)
458+
try container.encodeIfPresent(cond, forKey: .conditional)
459+
}
460+
}
461+
462+
}
463+
380464
extension Creature.CreatureType.Choice: Codable {
381465
enum ChoiceCodingKeys: String, CodingKey {
382466
case choose
@@ -448,6 +532,159 @@ extension Creature.CreatureType: Codable {
448532
}
449533
}
450534

535+
extension Creature.DamageImmunity: Codable {
536+
enum CodingKeys: String, CodingKey {
537+
case damageTypes = "immune"
538+
case preNote
539+
case note
540+
case conditional = "cond"
541+
}
542+
543+
enum SpecialCodingKeys: String, CodingKey {
544+
case special
545+
}
546+
547+
public init(from decoder: any Decoder) throws {
548+
if let container = try? decoder.singleValueContainer(),
549+
let value = try? container.decode(DamageType.self)
550+
{
551+
self = .damage(value)
552+
} else if let container = try? decoder.container(keyedBy: SpecialCodingKeys.self),
553+
let value = try? container.decode(String.self, forKey: .special)
554+
{
555+
self = .special(value)
556+
} else {
557+
let container = try decoder.container(keyedBy: CodingKeys.self)
558+
self = .conditional(
559+
try container.decode(Set<Creature.DamageImmunity>.self, forKey: .damageTypes),
560+
preNote: try container.decodeIfPresent(String.self, forKey: .preNote),
561+
note: try container.decodeIfPresent(String.self, forKey: .note),
562+
conditional: try container.decodeIfPresent(Bool.self, forKey: .conditional)
563+
)
564+
}
565+
}
566+
567+
public func encode(to encoder: any Encoder) throws {
568+
switch self {
569+
case .damage(let value):
570+
var container = encoder.singleValueContainer()
571+
try container.encode(value)
572+
case .special(let value):
573+
var container = encoder.container(keyedBy: SpecialCodingKeys.self)
574+
try container.encode(value, forKey: .special)
575+
case .conditional(let damageTypes, let preNote, let note, let cond):
576+
var container = encoder.container(keyedBy: CodingKeys.self)
577+
try container.encode(damageTypes, forKey: .damageTypes)
578+
try container.encodeIfPresent(preNote, forKey: .preNote)
579+
try container.encodeIfPresent(note, forKey: .note)
580+
try container.encodeIfPresent(cond, forKey: .conditional)
581+
}
582+
}
583+
584+
}
585+
586+
extension Creature.DamageResistance: Codable {
587+
enum CodingKeys: String, CodingKey {
588+
case damageTypes = "resist"
589+
case preNote
590+
case note
591+
case conditional = "cond"
592+
}
593+
594+
enum SpecialCodingKeys: String, CodingKey {
595+
case special
596+
}
597+
598+
public init(from decoder: any Decoder) throws {
599+
if let container = try? decoder.singleValueContainer(),
600+
let value = try? container.decode(DamageType.self)
601+
{
602+
self = .damage(value)
603+
} else if let container = try? decoder.container(keyedBy: SpecialCodingKeys.self),
604+
let value = try? container.decode(String.self, forKey: .special)
605+
{
606+
self = .special(value)
607+
} else {
608+
let container = try decoder.container(keyedBy: CodingKeys.self)
609+
self = .conditional(
610+
try container.decode(Set<Creature.DamageResistance>.self, forKey: .damageTypes),
611+
preNote: try container.decodeIfPresent(String.self, forKey: .preNote),
612+
note: try container.decodeIfPresent(String.self, forKey: .note),
613+
conditional: try container.decodeIfPresent(Bool.self, forKey: .conditional)
614+
)
615+
}
616+
}
617+
618+
public func encode(to encoder: any Encoder) throws {
619+
switch self {
620+
case .damage(let value):
621+
var container = encoder.singleValueContainer()
622+
try container.encode(value)
623+
case .special(let value):
624+
var container = encoder.container(keyedBy: SpecialCodingKeys.self)
625+
try container.encode(value, forKey: .special)
626+
case .conditional(let damageTypes, let preNote, let note, let cond):
627+
var container = encoder.container(keyedBy: CodingKeys.self)
628+
try container.encode(damageTypes, forKey: .damageTypes)
629+
try container.encodeIfPresent(preNote, forKey: .preNote)
630+
try container.encodeIfPresent(note, forKey: .note)
631+
try container.encodeIfPresent(cond, forKey: .conditional)
632+
}
633+
}
634+
635+
}
636+
637+
extension Creature.DamageVulnerability: Codable {
638+
enum CodingKeys: String, CodingKey {
639+
case damageTypes = "vulnerable"
640+
case preNote
641+
case note
642+
case conditional = "cond"
643+
}
644+
645+
enum SpecialCodingKeys: String, CodingKey {
646+
case special
647+
}
648+
649+
public init(from decoder: any Decoder) throws {
650+
if let container = try? decoder.singleValueContainer(),
651+
let value = try? container.decode(DamageType.self)
652+
{
653+
self = .damage(value)
654+
} else if let container = try? decoder.container(keyedBy: SpecialCodingKeys.self),
655+
let value = try? container.decode(String.self, forKey: .special)
656+
{
657+
self = .special(value)
658+
} else {
659+
let container = try decoder.container(keyedBy: CodingKeys.self)
660+
self = .conditional(
661+
try container.decode(Set<Creature.DamageVulnerability>.self, forKey: .damageTypes),
662+
preNote: try container.decodeIfPresent(String.self, forKey: .preNote),
663+
note: try container.decodeIfPresent(String.self, forKey: .note),
664+
conditional: try container.decodeIfPresent(Bool.self, forKey: .conditional)
665+
)
666+
}
667+
}
668+
669+
public func encode(to encoder: any Encoder) throws {
670+
switch self {
671+
case .damage(let value):
672+
var container = encoder.singleValueContainer()
673+
try container.encode(value)
674+
case .special(let value):
675+
var container = encoder.container(keyedBy: SpecialCodingKeys.self)
676+
try container.encode(value, forKey: .special)
677+
case .conditional(let damageTypes, let preNote, let note, let cond):
678+
var container = encoder.container(keyedBy: CodingKeys.self)
679+
try container.encode(damageTypes, forKey: .damageTypes)
680+
try container.encodeIfPresent(preNote, forKey: .preNote)
681+
try container.encodeIfPresent(note, forKey: .note)
682+
try container.encodeIfPresent(cond, forKey: .conditional)
683+
}
684+
}
685+
686+
}
687+
451688
extension Creature.Gear: Codable {
452689
enum CodingKeys: String, CodingKey {
453690
case item

Sources/FifthEdition/Util.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,25 @@ extension Alignment: Hashable {
2727
static let any: Self = .all
2828
}
2929

30+
public enum Condition: String, CaseIterable, Codable, Sendable {
31+
case blinded
32+
case charmed
33+
case deafened
34+
case exhaustion
35+
case frightened
36+
case grappled
37+
case incapacitated
38+
case invisible
39+
case paralyzed
40+
case petrified
41+
case poisoned
42+
case prone
43+
case restrained
44+
case stunned
45+
case unconscious
46+
case disease
47+
}
48+
3049
public enum CreatureType: String, CaseIterable, Codable, Sendable {
3150
case aberration
3251
case beast
@@ -44,6 +63,22 @@ public enum CreatureType: String, CaseIterable, Codable, Sendable {
4463
case undead
4564
}
4665

66+
public enum DamageType: String, CaseIterable, Codable, Sendable {
67+
case acid
68+
case bludgeoning
69+
case cold
70+
case fire
71+
case force
72+
case lightning
73+
case necrotic
74+
case piercing
75+
case poison
76+
case psychic
77+
case radiant
78+
case slashing
79+
case thunder
80+
}
81+
4782
public enum Edition: String, CaseIterable, Codable, Sendable {
4883
case classic
4984
case one

0 commit comments

Comments
 (0)