Skip to main content

delvewright_dsl/
mark.rs

1//! Marks: a point the campaign places relative to an anchor (spec-0066).
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::AnchorId;
7use crate::serde_fields::is_zero3;
8
9#[cfg(doc)]
10use crate::{Actor, CastPlace, Npc, SoundAt, Verb};
11
12/// **A mark** (spec-0066): an anchor and an integer block offset from it, the
13/// one declaration of a point the campaign places relative to a piece.
14///
15/// Its cell is the anchor's resolved cell plus `offset`, in world axes after
16/// the piece's placement. It is what a body stands on ([`Npc::offset`],
17/// [`Actor::offset`]), where a walk ends ([`Verb::MoveNpc`], [`Verb::MoveActor`],
18/// [`Verb::Teleport`]), where the cast ledger says a body is
19/// ([`CastPlace::Mark`]), where a sound plays ([`SoundAt::Anchor`]), and every
20/// camera position in a shot: a dolly waypoint (`path`), an aim target
21/// (`look_at`) and an anchor subject (`subject`). The roles are fields; the
22/// type is one.
23///
24/// A mark's cell lies inside the placed piece its anchor belongs to (`DW0897`):
25/// an offset says *where beside this place*, never *which place*.
26#[derive(
27    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
28)]
29#[serde(deny_unknown_fields)]
30pub struct Mark {
31    /// The anchor the mark is relative to.
32    pub anchor: AnchorId,
33    /// Integer `[x, y, z]` block offset from the anchor (default `[0, 0, 0]`).
34    #[serde(default, skip_serializing_if = "is_zero3")]
35    pub offset: [i32; 3],
36}
37
38impl Mark {
39    /// A mark at `anchor` with no offset.
40    pub fn at(anchor: AnchorId) -> Self {
41        Mark {
42            anchor,
43            offset: [0, 0, 0],
44        }
45    }
46
47    /// Whether the offset is non-zero.
48    pub fn is_offset(&self) -> bool {
49        !is_zero3(&self.offset)
50    }
51
52    /// The mark's cell, given the cell its anchor resolved to.
53    pub fn cell(&self, anchor_cell: [i32; 3]) -> [i32; 3] {
54        offset_cell(anchor_cell, self.offset)
55    }
56
57    /// The mark as a diagnostic spells it: the anchor alone at a zero offset,
58    /// else `anchor + [x, y, z]`.
59    pub fn display(&self) -> String {
60        if self.is_offset() {
61            format!(
62                "{} + [{}, {}, {}]",
63                self.anchor, self.offset[0], self.offset[1], self.offset[2]
64            )
65        } else {
66            self.anchor.as_str().to_string()
67        }
68    }
69}
70
71/// `cell + offset`, componentwise: the one arithmetic a [`Mark`] adds.
72pub fn offset_cell(cell: [i32; 3], offset: [i32; 3]) -> [i32; 3] {
73    [
74        cell[0] + offset[0],
75        cell[1] + offset[1],
76        cell[2] + offset[2],
77    ]
78}