shuyi_assets/dialogue/present.rs
1//! Who is there when a scene plays. An `@if` block's steps play only for a
2//! character who is [`Present`]; [`Scene::resolved`] gives the scene as it
3//! plays for those present. Who that is, and which scripts must keep someone
4//! inside an `@if` block, is the game's to say.
5use crate::prelude::*;
6
7use alloc::collections::BTreeSet;
8
9use super::{CharacterId, ChoiceOption, Scene, Step};
10
11/// Who is there when a scene plays.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Present {
14 /// Everyone: every `@if` block plays its first part (a scene viewer; a
15 /// scene played outside a story).
16 Everyone,
17 /// Only these characters.
18 Only(BTreeSet<CharacterId>),
19}
20
21impl Present {
22 /// Only `characters`.
23 pub fn only(characters: impl IntoIterator<Item = CharacterId>) -> Self {
24 Self::Only(characters.into_iter().collect())
25 }
26
27 /// Whether `character` is there.
28 pub fn has(&self, character: &CharacterId) -> bool {
29 match self {
30 Present::Everyone => true,
31 Present::Only(characters) => characters.contains(character),
32 }
33 }
34}
35
36impl<D: Clone> Scene<D> {
37 /// The scene as it plays for those `present`: every `@if` block
38 /// replaced by the steps of the part that holds, in reactions and in
39 /// other blocks too. The lines keep their ids.
40 #[must_use]
41 pub fn resolved(&self, present: &Present) -> Scene<D> {
42 Scene {
43 id: self.id.clone(),
44 steps: resolved(&self.steps, present),
45 }
46 }
47}
48
49impl<D> Scene<D> {
50 /// Whether the scene has a speech or narration line to play, whichever
51 /// reply is picked. Run on a [`Scene::resolved`] scene: one with
52 /// nothing left to say for those present isn't played.
53 pub fn has_text(&self) -> bool {
54 self.steps.iter().any(Step::has_text)
55 }
56}
57
58/// `steps` with every `@if` block replaced by the part that holds for
59/// those `present`.
60fn resolved<D: Clone>(steps: &[Step<D>], present: &Present) -> Vec<Step<D>> {
61 let mut out = Vec::with_capacity(steps.len());
62 for step in steps {
63 match step {
64 Step::If {
65 character,
66 then,
67 otherwise,
68 } => {
69 let part = if present.has(character) {
70 then
71 } else {
72 otherwise
73 };
74 out.extend(resolved(part, present));
75 }
76 Step::Choice { options } => {
77 let option = |o: &ChoiceOption<D>| ChoiceOption {
78 steps: resolved(&o.steps, present),
79 ..o.clone()
80 };
81 out.push(Step::Choice {
82 options: options.iter().map(option).collect(),
83 });
84 }
85 other => out.push(other.clone()),
86 }
87 }
88 out
89}