Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Sources/DHKit/AdversaryState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Foundation
/// which replaces values wholesale (copy-with-update pattern).
nonisolated public struct AdversaryState: CombatParticipant, Codable, Sendable, Equatable, Hashable
{
/// Stable slot identifier unique within the session.
public let id: UUID
/// The slug that identifies this adversary in the ``Compendium``.
public let adversaryID: String
Expand All @@ -34,17 +35,39 @@ nonisolated public struct AdversaryState: CombatParticipant, Codable, Sendable,
public let customName: String?

// MARK: Stat Snapshot (from catalog at creation time)

/// Maximum HP snapshotted from the catalog entry at slot creation time.
public let maxHP: Int
/// Maximum Stress snapshotted from the catalog entry at slot creation time.
public let maxStress: Int

// MARK: Tracked Stats

/// Current HP; clamped to `0...maxHP` by ``EncounterSession``.
public let currentHP: Int
/// Current Stress; clamped to `0...maxStress` by ``EncounterSession``.
public let currentStress: Int
/// `true` once HP reaches 0.
public let isDefeated: Bool
/// Active conditions on this adversary slot.
public let conditions: Set<Condition>

// MARK: - Init

/// Creates an adversary slot with explicit stat values.
///
/// Prefer ``init(from:customName:)`` when constructing from a catalog entry.
///
/// - Parameters:
/// - id: Slot identifier; defaults to a new UUID.
/// - adversaryID: Catalog slug for the source adversary.
/// - customName: Optional display name override.
/// - maxHP: Maximum HP (snapshotted from catalog).
/// - maxStress: Maximum Stress (snapshotted from catalog).
/// - currentHP: Starting HP; defaults to `maxHP`.
/// - currentStress: Starting Stress; defaults to `0`.
/// - isDefeated: Whether the slot starts defeated; defaults to `false`.
/// - conditions: Initial condition set; defaults to empty.
public init(
id: UUID = UUID(),
adversaryID: String,
Expand Down
2 changes: 2 additions & 0 deletions Sources/DHKit/Compendium.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import Observation

/// Errors that can occur while loading compendium data.
nonisolated public enum CompendiumError: Error, LocalizedError {
/// The specified bundle resource could not be located.
case fileNotFound(resourceName: String)
/// A resource was found but JSON decoding failed.
case decodingFailed(resourceName: String, underlying: Error)

public var errorDescription: String? {
Expand Down
13 changes: 13 additions & 0 deletions Sources/DHKit/EncounterSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ public final class EncounterSession: Identifiable, Hashable {
private let logger = Logger(label: "EncounterSession")

// MARK: Identity

/// Stable session identifier.
public let id: UUID
/// The display name for this encounter.
public var name: String

/// The ID of the ``EncounterDefinition`` this session was created from.
Expand Down Expand Up @@ -98,10 +101,16 @@ public final class EncounterSession: Identifiable, Hashable {
public var spotlightCount: Int

// MARK: Notes

/// Freeform notes visible to the GM during the encounter.
public var gmNotes: String

// MARK: - Init

/// Creates an encounter session with the given initial state.
///
/// Prefer ``make(from:using:)`` for sessions backed by a saved definition.
/// Use this initializer for blank or test sessions.
public init(
id: UUID = UUID(),
name: String,
Expand Down Expand Up @@ -316,18 +325,22 @@ public final class EncounterSession: Identifiable, Hashable {

// MARK: - Fear & Hope

/// Increase the GM's Fear pool by `amount`.
public func incrementFear(by amount: Int = 1) {
fearPool += amount
}

/// Spend from the GM's Fear pool, clamping to zero.
public func spendFear(by amount: Int = 1) {
fearPool = max(0, fearPool - amount)
}

/// Increase the party's Hope pool by `amount`.
public func incrementHope(by amount: Int = 1) {
hopePool += amount
}

/// Spend from the party's Hope pool, clamping to zero.
public func spendHope(by amount: Int = 1) {
hopePool = max(0, hopePool - amount)
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/DHKit/EncounterStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ import Observation

/// Errors thrown by ``EncounterStore`` operations.
nonisolated public enum EncounterStoreError: Error, LocalizedError, Sendable {
/// No definition with the given ID exists in the store.
case notFound(UUID)
/// Writing the definition's JSON file to disk failed.
case saveFailed(UUID, String)
/// Deleting the definition's JSON file from disk failed.
case deleteFailed(UUID, String)

public var errorDescription: String? {
Expand Down Expand Up @@ -93,6 +96,11 @@ public final class EncounterStore {

// MARK: - Init

/// Creates a store backed by the given directory.
///
/// The directory is not created automatically; call ``load()`` to read existing
/// files. Use ``defaultDirectory()`` to resolve the preferred iCloud or local
/// path at runtime.
public init(directory: URL) {
self.directory = directory
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/DHKit/EnvironmentState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ import Foundation
nonisolated public struct EnvironmentState: EncounterParticipant, Codable, Sendable, Equatable,
Hashable
{
/// Stable slot identifier unique within the session.
public let id: UUID
/// The slug identifying this environment in the ``Compendium``.
public let environmentID: String
/// Whether this environment element is currently active/visible to players.
public let isActive: Bool

/// Creates an environment slot.
///
/// - Parameters:
/// - id: Slot identifier; defaults to a new UUID.
/// - environmentID: Catalog slug for the source environment.
/// - isActive: Whether the element is initially active; defaults to `true`.
public init(
id: UUID = UUID(),
environmentID: String,
Expand Down
30 changes: 30 additions & 0 deletions Sources/DHKit/PlayerState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,27 @@ import Foundation
/// Tracks combat-relevant PC stats the GM needs to resolve hits and
/// track health during play. The full character sheet remains with the player.
nonisolated public struct PlayerState: CombatParticipant, Codable, Sendable, Equatable, Hashable {
/// Stable slot identifier unique within the session.
public let id: UUID
/// The player character's name.
public let name: String

// MARK: Hit Points

/// Maximum hit points.
public let maxHP: Int
/// Current HP; clamped to `0...maxHP` by ``EncounterSession``.
public let currentHP: Int

// MARK: Stress

/// Maximum stress.
public let maxStress: Int
/// Current Stress; clamped to `0...maxStress` by ``EncounterSession``.
public let currentStress: Int

// MARK: Defense

/// The DC for all rolls made against this PC.
public let evasion: Int
/// Damage at or above this triggers a Major hit (mark 2 HP).
Expand All @@ -45,16 +54,37 @@ nonisolated public struct PlayerState: CombatParticipant, Codable, Sendable, Equ
public let thresholdSevere: Int

// MARK: Armor

/// Total Armor Score (number of Armor Slots available).
public let armorSlots: Int
/// Remaining unused Armor Slots.
public let currentArmorSlots: Int

// MARK: Conditions

/// Active conditions on this player slot.
public let conditions: Set<Condition>

// MARK: - Init

/// Creates a player slot with explicit stat values.
///
/// In most cases prefer creating a ``PlayerConfig`` and letting
/// ``EncounterSession/make(from:using:)`` build the slot automatically.
///
/// - Parameters:
/// - id: Slot identifier; defaults to a new UUID.
/// - name: The player character's name.
/// - maxHP: Maximum hit points.
/// - currentHP: Starting HP; defaults to `maxHP`.
/// - maxStress: Maximum stress.
/// - currentStress: Starting Stress; defaults to `0`.
/// - evasion: The DC for rolls made against this PC.
/// - thresholdMajor: Damage threshold for a Major hit.
/// - thresholdSevere: Damage threshold for a Severe hit.
/// - armorSlots: Total Armor Score.
/// - currentArmorSlots: Remaining Armor Slots; defaults to `armorSlots`.
/// - conditions: Initial condition set; defaults to empty.
public init(
id: UUID = UUID(),
name: String,
Expand Down
2 changes: 2 additions & 0 deletions Sources/DHKit/SessionRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ import Observation
@MainActor
@Observable
public final class SessionRegistry {
/// All live sessions currently held by this registry, keyed by definition ID.
public private(set) var sessions: [UUID: EncounterSession] = [:]

/// Creates an empty session registry.
public init() {}

/// Return the existing session for `definition.id`, or create and store a new one.
Expand Down
5 changes: 5 additions & 0 deletions Sources/DHModels/DaggerheartModels.docc/DaggerheartModels.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ ecosystem.

- ``DaggerheartEnvironment``

### Players and Parties

- ``Player``
- ``Party``

### Encounters

- ``EncounterDefinition``
Expand Down
8 changes: 8 additions & 0 deletions Sources/DHModels/Party.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ import Foundation
/// from those IDs is the store's responsibility. A player may belong to
/// multiple parties.
nonisolated public struct Party: Codable, Sendable, Equatable, Hashable, Identifiable {
/// A stable identifier for this party.
public let id: UUID
/// The party's display name.
public var name: String
/// Ordered list of player IDs; order determines display order.
public var playerIDs: [UUID]

/// Creates a party.
///
/// - Parameters:
/// - id: Stable identifier; defaults to a new UUID.
/// - name: The party's display name.
/// - playerIDs: Ordered list of player IDs. Order determines display order.
public init(
id: UUID = UUID(),
name: String,
Expand Down
19 changes: 19 additions & 0 deletions Sources/DHModels/Player.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,34 @@ import Foundation
/// Use ``asConfig()`` to snapshot a `Player` into a ``PlayerConfig`` for
/// use in an ``EncounterDefinition``.
nonisolated public struct Player: Codable, Sendable, Equatable, Hashable, Identifiable {
/// A stable identifier for this player record.
public let id: UUID
/// The player character's name.
public var name: String
/// Maximum hit points for this character.
public var maxHP: Int
/// Maximum stress for this character.
public var maxStress: Int
/// The difficulty class for rolls made against this character.
public var evasion: Int
/// Damage threshold for a Major hit (marks 2 HP).
public var thresholdMajor: Int
/// Damage threshold for a Severe hit (marks 3 HP).
public var thresholdSevere: Int
/// Total number of Armor Slots available to this character.
public var armorSlots: Int

/// Creates a player record.
///
/// - Parameters:
/// - id: Stable identifier; defaults to a new UUID.
/// - name: The player character's name.
/// - maxHP: Maximum hit points.
/// - maxStress: Maximum stress.
/// - evasion: The DC for rolls made against this PC.
/// - thresholdMajor: Damage threshold for a Major hit.
/// - thresholdSevere: Damage threshold for a Severe hit.
/// - armorSlots: Total Armor Score (number of Armor Slots).
public init(
id: UUID = UUID(),
name: String,
Expand Down
Loading