1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Rooms and exits.
//!
//! Rooms are kept in `World.rooms` keyed by [`crate::interactive_fiction::data::RoomId`]. Each
//! room has a list of [`Exit`]s whose directions become menu entries when the
//! engine assembles choices for a turn.
use crate::interactive_fiction::data::condition::Condition;
use crate::interactive_fiction::data::ids::RoomId;
use crate::interactive_fiction::data::text::Text;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// A location the player can occupy.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Room {
/// Short room name shown as a header.
pub name: String,
/// Long-form narrative description.
pub description: Text,
/// Available exits. Hidden and locked exits still live here; visibility
/// and lock state are expressed through conditions and messages.
pub exits: Vec<Exit>,
/// If true, the room is in darkness unless the player carries a lit light source.
pub dark: bool,
/// Text shown when in the dark (no description, just this).
pub dark_description: Option<Text>,
/// Keyword -> description lookup for "examine X" when X is a feature of
/// the room rather than an item. Keywords are matched case-insensitively
/// against substrings of the player's input.
pub examine: BTreeMap<String, Text>,
/// Alias shown when this room is referenced as an exit destination
/// but the player hasn't yet visited it. Once `RuntimeState::visited`
/// contains this room's id, exit listings show the real `name`
/// instead. `None` means no mystery: exit listings always use the
/// real name.
#[serde(default)]
pub unseen_alias: Option<String>,
/// Optional condition that, when true, forces the exit listing to
/// show `unseen_alias` regardless of visited state. Use for
/// simulation-cracking / recovered-amnesia effects where a
/// previously-familiar room becomes uncertain again.
#[serde(default)]
pub alias_when: Option<Condition>,
}
impl Room {
pub fn new(name: impl Into<String>, description: Text) -> Self {
Self {
name: name.into(),
description,
exits: Vec::new(),
dark: false,
dark_description: None,
examine: BTreeMap::new(),
unseen_alias: None,
alias_when: None,
}
}
pub fn with_unseen_alias(mut self, alias: impl Into<String>) -> Self {
self.unseen_alias = Some(alias.into());
self
}
pub fn with_alias_when(mut self, condition: Condition) -> Self {
self.alias_when = Some(condition);
self
}
pub fn with_exit(mut self, exit: Exit) -> Self {
self.exits.push(exit);
self
}
pub fn dark(mut self, description: Text) -> Self {
self.dark = true;
self.dark_description = Some(description);
self
}
pub fn with_examine(mut self, keyword: impl Into<String>, text: Text) -> Self {
self.examine.insert(keyword.into().to_lowercase(), text);
self
}
}
/// A one-way passage between rooms. Bidirectional connectivity is expressed by
/// giving both rooms an exit that targets the other.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Exit {
/// Label shown in the menu: "north", "up", "to the cellar".
pub direction: String,
/// Destination room.
pub to: RoomId,
/// If present and false, the exit appears in the menu but picking it
/// prints `locked_message` instead of moving. `None` means always passable.
pub passable_when: Option<Condition>,
/// Message shown when the player tries to pass a gated exit whose
/// condition does not hold.
pub locked_message: Option<Text>,
/// If present and false, the exit is hidden from the menu entirely.
/// Used for secret passages revealed by rules.
pub visible_when: Option<Condition>,
}
impl Exit {
pub fn new(direction: impl Into<String>, to: RoomId) -> Self {
Self {
direction: direction.into(),
to,
passable_when: None,
locked_message: None,
visible_when: None,
}
}
pub fn gated(mut self, condition: Condition, locked_message: Text) -> Self {
self.passable_when = Some(condition);
self.locked_message = Some(locked_message);
self
}
pub fn hidden_until(mut self, visible_when: Condition) -> Self {
self.visible_when = Some(visible_when);
self
}
}