nightshade 0.13.2

A cross-platform data-oriented game engine.
Documentation
//! The canonical verb vocabulary.
//!
//! [`Verb`] is the authoritative enumeration of every action the parser
//! recognises. English synonyms and Inform 7 standard-library aliases are
//! registered through strum's `EnumString` aliases, so `grab`, `carry`,
//! `get`, and `pick` all parse as [`Verb::Take`]. The parser matches on
//! these variants exhaustively — adding a new verb is a one-file change
//! that the compiler forces all consumers to handle.
//!
//! [`RefusalCategory`] groups verbs that share a tone of failure ("can't
//! eat this", "violence won't help", etc.). The default category for each
//! verb lives in [`Verb::default_refusal_category`]; worlds can override
//! it via
//! [`VerbResponses::refusal_overrides`](super::verb_responses::VerbResponses::refusal_overrides).
//!
//! This lives in `data` rather than `parser` so that [`VerbResponses`]
//! (also in `data`) can name verbs in its override map without pulling a
//! parser dependency into the data layer.

use serde::{Deserialize, Serialize};
use strum::EnumString;

/// Canonical verbs recognized by the parser. Every English synonym in the
/// Inform 7 standard verb library routes to one of these variants through
/// `Verb::from_str`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Serialize, Deserialize)]
#[strum(ascii_case_insensitive)]
pub enum Verb {
    // ---- Examination / room introspection ------------------------------
    #[strum(serialize = "look", serialize = "l", serialize = "gaze")]
    Look,
    #[strum(
        serialize = "examine",
        serialize = "x",
        serialize = "inspect",
        serialize = "check",
        serialize = "describe",
        serialize = "observe",
        serialize = "study",
        serialize = "watch",
        serialize = "search"
    )]
    Examine,
    #[strum(serialize = "exits")]
    Exits,

    // ---- Inventory / status --------------------------------------------
    #[strum(serialize = "inventory", serialize = "inv", serialize = "i")]
    Inventory,

    // ---- Passage of time ----------------------------------------------
    #[strum(
        serialize = "wait",
        serialize = "z",
        serialize = "pass",
        serialize = "delay",
        serialize = "sleep",
        serialize = "nap",
        serialize = "rest",
        serialize = "think",
        serialize = "meditate"
    )]
    Wait,

    // ---- Item acquisition ---------------------------------------------
    #[strum(
        serialize = "take",
        serialize = "get",
        serialize = "grab",
        serialize = "carry",
        serialize = "hold",
        serialize = "pick"
    )]
    Take,

    // ---- Item disposal ------------------------------------------------
    #[strum(serialize = "drop", serialize = "discard")]
    Drop,
    #[strum(serialize = "throw", serialize = "toss", serialize = "hurl")]
    Throw,
    #[strum(serialize = "put", serialize = "place", serialize = "set")]
    Put,

    // ---- Manipulation -------------------------------------------------
    #[strum(serialize = "use", serialize = "operate", serialize = "activate")]
    Use,
    #[strum(serialize = "open")]
    Open,
    #[strum(serialize = "close", serialize = "shut")]
    Close,
    #[strum(serialize = "unlock")]
    Unlock,
    #[strum(serialize = "lock")]
    Lock,
    #[strum(serialize = "switch")]
    Switch,
    #[strum(
        serialize = "push",
        serialize = "press",
        serialize = "shove",
        serialize = "poke",
        serialize = "prod",
        serialize = "nudge"
    )]
    Push,
    #[strum(
        serialize = "pull",
        serialize = "yank",
        serialize = "tug",
        serialize = "loosen"
    )]
    Pull,
    #[strum(serialize = "turn", serialize = "rotate", serialize = "twist")]
    Turn,
    #[strum(serialize = "squeeze")]
    Squeeze,
    #[strum(serialize = "rub", serialize = "polish", serialize = "scratch")]
    Rub,
    #[strum(serialize = "wave")]
    Wave,
    #[strum(serialize = "swing")]
    Swing,
    #[strum(serialize = "burn", serialize = "ignite")]
    Burn,
    #[strum(serialize = "cut", serialize = "slice")]
    Cut,
    #[strum(serialize = "tie", serialize = "attach")]
    Tie,
    #[strum(serialize = "wear", serialize = "don")]
    Wear,
    #[strum(serialize = "insert")]
    Insert,
    #[strum(serialize = "fill", serialize = "empty")]
    Fill,

    // ---- Removal of worn items (falls back to Drop/Use) ---------------
    #[strum(serialize = "remove", serialize = "doff")]
    Remove,

    // ---- Sensory verbs ------------------------------------------------
    #[strum(serialize = "smell", serialize = "sniff")]
    Smell,
    #[strum(serialize = "listen", serialize = "hear")]
    Listen,
    #[strum(serialize = "touch", serialize = "feel")]
    Touch,
    #[strum(serialize = "taste", serialize = "lick")]
    Taste,

    // ---- Consumption --------------------------------------------------
    #[strum(serialize = "eat", serialize = "consume")]
    Eat,
    #[strum(serialize = "drink", serialize = "sip", serialize = "quaff")]
    Drink,

    // ---- Reading ------------------------------------------------------
    #[strum(serialize = "read", serialize = "peruse")]
    Read,
    #[strum(serialize = "consult")]
    Consult,

    // ---- Movement -----------------------------------------------------
    #[strum(
        serialize = "go",
        serialize = "walk",
        serialize = "run",
        serialize = "head",
        serialize = "move",
        serialize = "proceed",
        serialize = "travel"
    )]
    Go,
    #[strum(serialize = "enter")]
    Enter,
    #[strum(serialize = "climb", serialize = "ascend")]
    Climb,
    #[strum(serialize = "jump", serialize = "leap", serialize = "hop")]
    Jump,

    // ---- Social / dialogue --------------------------------------------
    #[strum(
        serialize = "talk",
        serialize = "speak",
        serialize = "greet",
        serialize = "hail"
    )]
    Talk,
    #[strum(serialize = "ask", serialize = "tell", serialize = "answer")]
    Ask,
    #[strum(serialize = "kiss")]
    Kiss,
    #[strum(
        serialize = "attack",
        serialize = "fight",
        serialize = "hit",
        serialize = "kill",
        serialize = "punch",
        serialize = "strike",
        serialize = "break",
        serialize = "smash",
        serialize = "kick"
    )]
    Attack,
    #[strum(serialize = "wake", serialize = "awaken", serialize = "rouse")]
    Wake,
    #[strum(serialize = "give", serialize = "offer", serialize = "hand")]
    Give,
    #[strum(serialize = "show", serialize = "display", serialize = "present")]
    Show,
    #[strum(serialize = "apologise", serialize = "apologize", serialize = "sorry")]
    Apologise,
    #[strum(serialize = "buy", serialize = "purchase")]
    Buy,
    #[strum(
        serialize = "leave",
        serialize = "bye",
        serialize = "goodbye",
        serialize = "farewell"
    )]
    Leave,
}

/// Families of refusal a verb falls into when nothing in the current menu
/// accepts it. The parser picks one of the `VerbResponses::refusal_*`
/// templates from this category.
///
/// Authors re-route verbs to different categories via
/// `VerbResponses::refusal_overrides` — no parser edit needed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RefusalCategory {
    /// Catch-all for verbs without a specific category.
    Default,
    /// Taste / lick.
    Taste,
    /// Eat / drink.
    Consume,
    /// Attack / hit / kick / break / smash / cut / burn — destructive.
    Violence,
    /// Kiss / apologise — social overtures.
    Affection,
    /// Open / close / push / pull / turn / take / drop — physical attempts.
    Manipulation,
    /// Bare `jump` or `jump {noun}`.
    Jump,
    /// Give / show / buy — commerce / multi-object.
    Exchange,
    /// Wake / rouse.
    Wake,
}

impl Verb {
    /// The default refusal category for this verb. World authors can override
    /// it per-verb via `VerbResponses::refusal_overrides`.
    pub const fn default_refusal_category(self) -> RefusalCategory {
        match self {
            Verb::Taste => RefusalCategory::Taste,
            Verb::Eat | Verb::Drink => RefusalCategory::Consume,
            Verb::Attack | Verb::Burn | Verb::Cut => RefusalCategory::Violence,
            Verb::Kiss | Verb::Apologise => RefusalCategory::Affection,
            Verb::Open
            | Verb::Close
            | Verb::Unlock
            | Verb::Lock
            | Verb::Switch
            | Verb::Push
            | Verb::Pull
            | Verb::Turn
            | Verb::Squeeze
            | Verb::Rub
            | Verb::Wave
            | Verb::Swing
            | Verb::Tie
            | Verb::Wear
            | Verb::Remove
            | Verb::Insert
            | Verb::Fill
            | Verb::Use
            | Verb::Take
            | Verb::Drop
            | Verb::Throw
            | Verb::Put => RefusalCategory::Manipulation,
            Verb::Jump => RefusalCategory::Jump,
            Verb::Give | Verb::Show | Verb::Buy => RefusalCategory::Exchange,
            Verb::Wake => RefusalCategory::Wake,
            Verb::Look
            | Verb::Examine
            | Verb::Exits
            | Verb::Inventory
            | Verb::Wait
            | Verb::Smell
            | Verb::Listen
            | Verb::Touch
            | Verb::Read
            | Verb::Consult
            | Verb::Go
            | Verb::Enter
            | Verb::Climb
            | Verb::Talk
            | Verb::Ask
            | Verb::Leave => RefusalCategory::Default,
        }
    }

    /// True iff this verb should route through the examine-choice search
    /// when its primary dispatch fails. `open the closet` sensibly falls
    /// back to the closet's examine text; `eat the bed` absolutely does
    /// not.
    pub const fn accepts_examine_fallback(self) -> bool {
        matches!(
            self,
            Verb::Use
                | Verb::Open
                | Verb::Close
                | Verb::Unlock
                | Verb::Lock
                | Verb::Switch
                | Verb::Push
                | Verb::Pull
                | Verb::Turn
                | Verb::Squeeze
                | Verb::Rub
                | Verb::Wave
                | Verb::Swing
                | Verb::Tie
                | Verb::Wear
                | Verb::Remove
                | Verb::Insert
                | Verb::Fill
                | Verb::Read
                | Verb::Consult
                | Verb::Talk
                | Verb::Ask
        )
    }
}