bevy_novel/
lib.rs

1pub mod messages;
2pub mod rpy_asset_loader;
3
4use std::collections::HashMap;
5
6use bevy::prelude::*;
7use bevy_kira_audio::prelude::*;
8
9use renpy_parser::parsers::{AST, inject_node};
10
11use messages::*;
12
13#[derive(Component)]
14pub struct NovelBackground;
15
16#[derive(Component)]
17pub struct NovelImage;
18
19#[derive(Component)]
20pub struct NovelText;
21
22#[derive(Component)]
23pub struct NovelTextWhat;
24
25#[derive(Component)]
26pub struct NovelTextWho;
27pub struct NovelPlugin;
28
29#[derive(Resource, Clone)]
30pub struct MusicHandle(Option<Handle<AudioInstance>>);
31
32#[derive(Resource, Default)]
33pub struct NovelData {
34    pub ast: Vec<AST>,
35    pub current_index: usize,
36    pub cached_images: HashMap<String, Sprite>,
37}
38
39impl NovelData {
40    // Manipulate Scenario
41
42    pub fn push_text_node(&mut self, who: Option<String>, what: String, index: usize) {
43        let node = AST::Say(index, who, what);
44        self.ast = inject_node(self.ast.clone(), node.clone());
45        for a in self.ast.iter_mut() {
46            if let AST::Label(node_index, label, node_ast, opts) = a {
47                let first_index = node_ast.first().unwrap().index();
48                let last_index = node_ast.last().unwrap().index();
49
50                if index >= first_index && index <= last_index {
51                    *a = AST::Label(
52                        *node_index,
53                        label.clone(),
54                        inject_node(node_ast.clone(), node.clone()).clone(),
55                        opts.clone(),
56                    )
57                }
58            }
59        }
60    }
61
62    pub fn push_show_node(&mut self, image: String, index: usize) {
63        let node = AST::Show(index, image);
64        self.ast = inject_node(self.ast.clone(), node.clone());
65        for a in self.ast.iter_mut() {
66            if let AST::Label(node_index, label, node_ast, opts) = a {
67                let first_index = node_ast.first().unwrap().index();
68                let last_index = node_ast.last().unwrap().index();
69
70                if index >= first_index && index <= last_index {
71                    *a = AST::Label(
72                        *node_index,
73                        label.clone(),
74                        inject_node(node_ast.clone(), node.clone()).clone(),
75                        opts.clone(),
76                    )
77                }
78            }
79        }
80    }
81
82    pub fn push_hide_node(&mut self, image: String, index: usize) {
83        let node = AST::Hide(index, image);
84        self.ast = inject_node(self.ast.clone(), node.clone());
85        for a in self.ast.iter_mut() {
86            if let AST::Label(node_index, label, node_ast, opts) = a {
87                let first_index = node_ast.first().unwrap().index();
88                let last_index = node_ast.last().unwrap().index();
89
90                if index >= first_index && index <= last_index {
91                    *a = AST::Label(
92                        *node_index,
93                        label.clone(),
94                        inject_node(node_ast.clone(), node.clone()).clone(),
95                        opts.clone(),
96                    )
97                }
98            }
99        }
100    }
101
102    pub fn push_scene_node(&mut self, image: String, index: usize) {
103        let node = AST::Scene(index, Some(image), "master".into());
104        self.ast = inject_node(self.ast.clone(), node.clone());
105        for a in self.ast.iter_mut() {
106            if let AST::Label(node_index, label, node_ast, opts) = a {
107                let first_index = node_ast.first().unwrap().index();
108                let last_index = node_ast.last().unwrap().index();
109
110                if index >= first_index && index <= last_index {
111                    *a = AST::Label(
112                        *node_index,
113                        label.clone(),
114                        inject_node(node_ast.clone(), node.clone()).clone(),
115                        opts.clone(),
116                    )
117                }
118            }
119        }
120    }
121
122    // Manipulate Images
123
124    pub fn write_image_cache(&mut self, image_name: String, sprite: Sprite) {
125        self.cached_images.insert(image_name, sprite);
126    }
127}
128
129#[derive(Resource, Default)]
130pub struct NovelSettings {
131    pub assets_path: String,
132    pub pause_handle_switch_node: bool,
133}
134
135impl Plugin for NovelPlugin {
136    fn build(&self, app: &mut App) {
137        app.add_systems(Startup, setup)
138            .add_systems(
139                Update,
140                (
141                    adjust_text_position,
142                    handle_start_scenario,
143                    handle_switch_next_node,
144                    handle_new_node,
145                    (handle_hide_image_node, handle_hide_text_node).chain(),
146                    (handle_show_image_node, handle_show_text_node).chain(),
147                    handle_press_key,
148                    handle_play_audio,
149                    scale_images,
150                )
151                    .chain(),
152            )
153            .add_message::<EventHandleNode>()
154            .add_message::<EventHideImageNode>()
155            .add_message::<EventHideTextNode>()
156            .add_message::<EventJump>()
157            .add_message::<EventLabel>()
158            .add_message::<EventPlayAudio>()
159            .add_message::<EventReturn>()
160            .add_message::<EventSay>()
161            .add_message::<EventShow>()
162            .add_message::<EventShowImageNode>()
163            .add_message::<EventShowTextNode>()
164            .add_message::<EventStartScenario>()
165            .add_message::<EventSwitchNextNode>()
166            .add_message::<EventNovelEnd>()
167            .init_resource::<NovelData>()
168            .insert_resource(MusicHandle(None))
169            .insert_resource(NovelSettings::default())
170            .init_asset_loader::<rpy_asset_loader::RpyAssetLoader>()
171            .init_asset::<rpy_asset_loader::Rpy>();
172    }
173}
174
175fn setup(mut commands: Commands, _novel_settings: Res<NovelSettings>) {
176    commands
177        .spawn((
178            Text2d::default(),
179            NovelText,
180            Name::new("Novel Text"),
181            ZIndex(10),
182            Transform::from_translation(Vec3::Z),
183            TextLayout::new(Justify::Left, LineBreak::WordBoundary),
184        ))
185        .with_children(|p| {
186            p.spawn((
187                TextSpan::new(""),
188                NovelTextWho {},
189                Name::new("Novel Text Who"),
190                Visibility::Visible,
191            ));
192
193            p.spawn((TextSpan::new("\n"), Name::new("Novel Text Newline")));
194
195            p.spawn((
196                TextSpan::new(""),
197                NovelTextWhat {},
198                Name::new("Novel Text What"),
199                Transform::from_translation(Vec3::Z),
200                Visibility::Visible,
201            ));
202        });
203
204    commands.spawn((
205        Name::new("Background Image"),
206        Sprite::default(),
207        NovelBackground,
208        Node {
209            position_type: PositionType::Absolute,
210            width: Val::Auto,
211            height: Val::Auto,
212            ..default()
213        },
214        Visibility::Hidden,
215    ));
216
217    commands.spawn((
218        Name::new("Character Image"),
219        Sprite::default(),
220        NovelImage,
221        // ZIndex(2),
222        Node {
223            position_type: PositionType::Absolute,
224            width: Val::Auto,
225            height: Val::Auto,
226            ..default()
227        },
228        Visibility::Hidden,
229    ));
230}
231
232fn adjust_text_position(
233    mut windows: Query<&Window>,
234    mut q_novel_text: Query<(Entity, &mut Transform, &NovelText)>,
235) {
236    let window = windows.single_mut().unwrap(); // Get window
237
238    for (_, mut transform, _) in q_novel_text.iter_mut() {
239        transform.translation.y = -window.resolution.height() / 2.0 + 50.0;
240        transform.translation.x = -window.resolution.width() / 3.0 + 50.0;
241    }
242}
243
244pub fn find_element_with_index(ast: Vec<AST>, index: usize) -> Option<AST> {
245    for ast in ast.iter() {
246        let ast_index = match ast {
247            AST::Return(i, _) => *i,
248            AST::Jump(i, _, _) => *i,
249            AST::Scene(i, _, _) => *i,
250            AST::Show(i, _) => *i,
251            AST::Hide(i, _) => *i,
252            AST::Label(i, _, _, _) => *i,
253            AST::Say(i, _, _) => *i,
254            AST::Play(i, _, _) => *i,
255            AST::Define(i, _) => *i,
256            AST::Stop(i, _, _, _) => *i,
257            AST::GameMechanic(i, _) => *i,
258            AST::LLMGenerate(i, _, _) => *i,
259            AST::Comment(i, _) => *i,
260            AST::Error => panic!(),
261        };
262
263        if index == ast_index {
264            return Some(ast.clone());
265        }
266    }
267
268    None
269}
270
271fn list_ast_indices(ast: Vec<AST>) -> Vec<usize> {
272    let mut indices: Vec<usize> = ast
273        .iter()
274        .map(|a| match a {
275            AST::Return(i, _) => *i,
276            AST::Jump(i, _, _) => *i,
277            AST::Scene(i, _, _) => *i,
278            AST::Show(i, _) => *i,
279            AST::Hide(i, _) => *i,
280            AST::Label(i, _, _, _) => *i,
281            AST::Say(i, _, _) => *i,
282            AST::Play(i, _, _) => *i,
283            AST::Define(i, _) => *i,
284            AST::Stop(i, _, _, _) => *i,
285            AST::GameMechanic(i, _) => *i,
286            AST::LLMGenerate(i, _, _) => *i,
287            AST::Comment(i, _) => *i,
288            AST::Error => panic!(),
289        })
290        .collect();
291
292    for ast in ast {
293        match ast {
294            AST::Label(_, _, vec, _) => {
295                indices.extend_from_slice(list_ast_indices(vec).as_slice());
296            }
297            _ => {}
298        }
299    }
300
301    indices
302}