rustic_mountain_core/
lib.rs

1pub mod assign_skipped_consts;
2pub mod memory;
3pub mod objects;
4pub mod structures;
5pub mod utils;
6use std::{cell::RefCell, rc::Rc, vec};
7
8use assign_skipped_consts::assign_skipped_consts;
9use memory::Memory;
10use objects::{
11    balloon::Balloon, bigchest::BigChest, chest::Chest, fakewall::FakeWall, fallfloor::FallFloor,
12    flag::Flag, flyfruit::FlyFruit, fruit::Fruit, key::Key, lifeup::LifeUp, message::Message,
13    platform::Platform, player::Player, playerspawn::PlayerSpawn, roomtitle::RoomTitle,
14    spring::Spring,
15};
16use serde::{Deserialize, Serialize};
17use serde_json;
18use serde_json::json;
19use structures::*;
20
21use rand::prelude::*;
22use utils::{max, sin};
23#[derive(Serialize, Deserialize)]
24
25pub struct Celeste {
26    /// Represents the pico-8 display buffers and memory. Go through this for any drawing
27    pub mem: Memory,
28    // #[serde(skip)]
29    pub objects: Vec<Rc<RefCell<Object>>>,
30    pub got_fruit: Vec<bool>,
31    pub max_djump: u8,
32    pub deaths: u64,
33    pub frames: u8,
34    pub room: Vector,
35    pub level: u8,
36    pub has_dashed: bool,
37    pub has_key: bool,
38    pub freeze: u8,
39    pub particles: Vec<Particle>,
40    pub dead_particles: Vec<DeadParticle>,
41    pub delay_restart: u8,
42    pub shake: u8,
43    pub seconds: u8,
44    pub minutes: u64,
45    pub clouds: Vec<Cloud>,
46    pub start_game_flash: f32,
47    pub music_timer: i32,
48    pub start_game: bool,
49    pub flash_bg: bool,
50    pub new_bg: bool,
51    pub pause_player: bool,
52}
53impl Celeste {
54    /// Returns a new celeste object
55    /// # Arguments
56    ///
57    /// * `map` - A string containing the "mapdata" section of a pico-8 cart
58    /// * `sprites` - A string containing the "sprites" section of the pico-8 cart
59    /// * `flags` - A string containing the "flags" section of the pico-8 carts
60    /// * `fontatlas` - A string with the bitmap data of the pico-8 font
61    ///
62    /// # Examples
63    /// ```
64    /// // see https://github.com/CoolElectronics/rustic-mountain/blob/main/standalone/src/consts.rs for example values of these constants
65    /// let celeste = Celeste::new(MAPDATA,SPRITES,FLAGS,FONTATLAS);
66    /// ```
67    pub fn new(map: String, sprites: String, flags: String, fontatlas: String) -> Celeste {
68        // let v: Box<dyn Fn(&mut Celeste) -> Box<dyn Object>> =
69        //     ;
70
71        let mut mem = Memory::new(map, sprites, flags, fontatlas);
72        let mut clouds = vec![];
73        for _ in 0..16 {
74            clouds.push(Cloud {
75                x: mem.rng.gen_range(0..128),
76                y: mem.rng.gen_range(0..128),
77                spd: mem.rng.gen_range(1..4),
78                w: mem.rng.gen_range(32..64),
79            })
80        }
81        let mut particles = vec![];
82        for _i in 0..24 {
83            let size: f32 = mem.rng.gen_range(0.0..1.25);
84            particles.push(Particle {
85                x: mem.rng.gen_range(0.0..128.0),
86                y: mem.rng.gen_range(0.0..128.0),
87                s: size.floor(),
88                spd: mem.rng.gen_range(0.25..5.25),
89                off: mem.rng.gen_range(0.0..1.0),
90                c: mem.rng.gen_range(6..8),
91            })
92        }
93
94        let mut cel = Celeste {
95            room: Vector { x: 0f32, y: 0f32 },
96            mem,
97            objects: vec![],
98            got_fruit: vec![],
99            max_djump: 1,
100            deaths: 0,
101            frames: 0,
102            seconds: 0,
103            minutes: 0,
104            level: 0,
105            has_key: false,
106            has_dashed: false,
107            freeze: 0,
108            clouds,
109            particles,
110            dead_particles: vec![],
111            delay_restart: 0,
112            shake: 0,
113            start_game: false,
114            music_timer: 0,
115            start_game_flash: 0.0,
116            flash_bg: false,
117            pause_player: false,
118            new_bg: false,
119        };
120        cel.title_screen();
121        // cel.load_room(0, 0);
122        cel
123    }
124
125    /// Advances a game tick. Does not draw the screen buffer. Analagous to calling `_update()` in
126    /// the original pico-8 cart. Should be called 30 times a second for real-time gameplay
127    pub fn next_tick(&mut self) {
128        // summit
129        self.frames += 1;
130
131        if self.level < 30 {
132            self.seconds += self.frames / 30;
133            self.minutes += (self.seconds / 60) as u64;
134            self.seconds %= 60;
135        }
136        self.frames %= 30;
137
138        if self.freeze > 0 {
139            self.freeze -= 1;
140            return;
141        }
142
143        if self.shake > 0 {
144            self.shake -= 1;
145            self.mem.camera(0.0, 0.0);
146            if self.shake != 0 {
147                self.mem.camera = Vector {
148                    x: self.mem.rng.gen_range(-2.0..3.0),
149                    y: self.mem.rng.gen_range(-2.0..3.0),
150                }
151            }
152        }
153
154        if self.delay_restart > 0 {
155            self.delay_restart -= 1;
156            if self.delay_restart == 0 {
157                self.load_room(self.room.x as u8, self.room.y as u8);
158            }
159        }
160
161        let mut i = 0;
162        let mut lastlen = self.objects.len();
163        loop {
164            if i >= self.objects.len() {
165                break;
166            }
167            let v = self.objects[i].clone();
168
169            let obj = v.borrow_mut();
170            let spd = Vector {
171                x: obj.spd.x,
172                y: obj.spd.y,
173            };
174            drop(obj);
175
176            v.borrow_mut().do_move(self, spd.x, spd.y, 0f32);
177            v.borrow_mut().update(self);
178            i += 1;
179        }
180        if self.is_title() {
181            if self.start_game {
182                self.start_game_flash -= 1.0;
183                if self.start_game_flash <= -30.0 {
184                    self.begin_game();
185                }
186            } else if self.mem.buttons[4] || self.mem.buttons[5] {
187                // music -1
188                self.start_game_flash = 50.0;
189                self.start_game = true;
190                // sfx 38
191            }
192        }
193        // let graph = &mut rself.borrow_mut().mem.graphics;
194        // for i in 0..128 * 128 {
195        //     // graphics[(i % 15) as u8] = i as ;
196        // }
197    }
198    pub fn is_title(&self) -> bool {
199        self.level == 32
200    }
201    pub fn begin_game(&mut self) {
202        self.max_djump = 1;
203        self.deaths = 0;
204        self.frames = 0;
205        self.seconds = 0;
206        self.minutes = 0;
207        self.music_timer = 0;
208        // music 007
209        self.level = 0;
210        self.load_room(0, 0);
211    }
212    pub fn draw(&mut self) {
213        if self.freeze > 0 {
214            return;
215        }
216        for i in 0..128 * 128 {
217            self.mem.graphics[i] = 0; // (i % 15) as u8;
218        }
219
220        self.mem.pal_reset();
221
222        if self.is_title() && self.start_game {
223            for i in 1..16 {
224                self.mem.pal(
225                    i,
226                    if self.start_game_flash <= 10.0 {
227                        f32::ceil(max(self.start_game_flash, 0.0) / 5.0) as u8
228                    } else {
229                        if self.frames % 10 < 5 {
230                            7
231                        } else {
232                            i as u8
233                        }
234                    },
235                );
236            }
237        }
238
239        //clearing screen
240        let bg_col = if self.flash_bg {
241            self.frames as u8 / 5
242        } else {
243            if self.new_bg {
244                2
245            } else {
246                0
247            }
248        };
249        self.mem.rrectfill(-1, -1, 128, 128, bg_col);
250
251        if !self.is_title() {
252            for cloud in &mut self.clouds {
253                cloud.x += cloud.spd;
254                self.mem.rectfill(
255                    cloud.x,
256                    cloud.y,
257                    cloud.x + cloud.w,
258                    cloud.y + 16 - (cloud.w as f32 * 0.1875) as i32,
259                    if self.new_bg { 14 } else { 1 },
260                );
261                if cloud.x > 128 {
262                    cloud.x = -cloud.w;
263                    cloud.y = self.mem.rng.gen_range(0..120);
264                }
265            }
266        }
267
268        self.mem.map(
269            self.room.x as u8 * 16,
270            self.room.y as u8 * 16,
271            0,
272            0,
273            16,
274            16,
275            4,
276        );
277
278        for v in self.objects.clone() {
279            let mut r = v.borrow_mut();
280            if let ObjectType::Platform(_) = r.obj_type {
281                r.draw(self);
282            }
283        }
284        self.mem.map(
285            self.room.x as u8 * 16,
286            self.room.y as u8 * 16,
287            0,
288            0,
289            16,
290            16,
291            2, //2
292        );
293        for v in self.objects.clone() {
294            // cloning is fine here, it's just a vector of pointers
295            let mut r = v.borrow_mut();
296            if let ObjectType::Platform(_) = r.obj_type {
297            } else {
298                r.draw(self);
299            }
300        }
301
302        // do particles here
303        for particle in &mut self.particles {
304            particle.x += particle.spd;
305            particle.y += sin(particle.off);
306
307            self.mem.rectfill(
308                particle.x as i32,
309                particle.y as i32,
310                (particle.x + particle.s) as i32,
311                (particle.y + particle.s) as i32,
312                particle.c,
313            );
314            if particle.x > 132.0 {
315                particle.x = -4.0;
316                particle.y = self.mem.rng.gen_range(0.0..128.0);
317            }
318        }
319        for particle in &mut self.dead_particles {
320            particle.x += particle.dx;
321            particle.y += particle.dy;
322
323            particle.t -= 0.2;
324
325            if particle.t > 0.0 {
326                self.mem.rectfill(
327                    (particle.x - particle.t) as i32,
328                    (particle.y - particle.t) as i32,
329                    (particle.x + particle.t) as i32,
330                    (particle.y + particle.t) as i32,
331                    14 + ((particle.t * 5.0) % 2.0) as u8,
332                );
333            }
334        }
335        self.dead_particles.retain(|f| f.t > 0.0);
336
337        if self.is_title() {
338            self.mem.print("z+x", 58, 80, 5);
339            // self.mem.print("(rust edition)", 41, 91, 5);
340            self.mem.print("maddy thorson", 42, 96, 5);
341            self.mem.print("noel berry", 46, 102, 5);
342        }
343
344        // todo: summit blinds
345    }
346    /// advances to the next room
347    pub fn next_room(&mut self) {
348        // do sound at some point
349        self.level += 1;
350        self.load_room(self.level % 8, self.level / 8);
351    }
352    pub fn title_screen(&mut self) {
353        self.frames = 0;
354        self.start_game_flash = 0.0;
355        self.level = 32;
356        // music
357        self.load_room(7, 3);
358    }
359    pub fn load_room(&mut self, x: u8, y: u8) {
360        self.objects.clear();
361
362        self.room = Vector {
363            x: x as f32,
364            y: y as f32,
365        };
366
367        self.has_dashed = false;
368        self.has_key = false;
369
370        for i in 0..16 {
371            for j in 0..16 {
372                let tile = self.mem.mget(x * 16 + i, y * 16 + j);
373                let x = i as f32 * 8.0;
374                let y = j as f32 * 8.0;
375                match match tile {
376                    1 => Some(PlayerSpawn::init(self, x, y)),
377                    11 | 12 => Some(Platform::init(self, x, y, tile)),
378                    18 => Some(Spring::init(self, x, y)),
379                    22 => Some(Balloon::init(self, x, y)),
380                    23 => Some(FallFloor::init(self, x, y)),
381                    26 | 64 | 28 | 8 | 20 => {
382                        if self.got_fruit.len() > self.level as usize
383                            && self.got_fruit[self.level as usize]
384                        {
385                            None
386                        } else {
387                            Some(match tile {
388                                8 => Key::init(self, x, y),
389                                20 => Chest::init(self, x, y),
390                                26 => Fruit::init(self, x, y),
391                                64 => FakeWall::init(self, x, y),
392                                28 => FlyFruit::init(self, x, y),
393                                _ => unreachable!(),
394                            })
395                        }
396                    }
397
398                    86 => Some(Message::init(self, x, y)),
399                    96 => Some(BigChest::init(self, x, y)),
400                    118 => Some(Flag::init(self, x, y)),
401                    _ => None,
402                } {
403                    Some(o) => {
404                        self.objects.push(Rc::new(RefCell::new(o)));
405                    }
406                    None => (),
407                }
408
409                //
410            }
411        }
412        if !self.is_title() {
413            let obj = RoomTitle::init(self, 0.0, 0.0);
414            self.objects.push(Rc::new(RefCell::new(obj)));
415        }
416    }
417    pub fn tile_at(&self, x: f32, y: f32) -> u8 {
418        return self.mem.mget(
419            (self.room.x as f32 * 16.0 + x) as u8,
420            (self.room.y as f32 * 16.0 + y) as u8,
421        );
422    }
423    pub fn spikes_at(&self, x1: f32, y1: f32, x2: f32, y2: f32, xspd: f32, yspd: f32) -> bool {
424        let mut i = 0f32.max((x1 / 8.0).floor()) as u8;
425        loop {
426            let mut j = 0f32.max((y1 / 8.0).floor()) as u8;
427            loop {
428                if match self.tile_at(i as f32, j as f32) {
429                    17 => yspd >= 0.0 && y2.rem_euclid(8.0) >= 6.0,
430                    27 => yspd <= 0.0 && y1.rem_euclid(8.0) <= 2.0,
431                    43 => xspd <= 0.0 && x1.rem_euclid(8.0) <= 2.0,
432                    59 => xspd >= 0.0 && x2.rem_euclid(8.0) >= 6.0,
433                    _ => false,
434                } {
435                    return true;
436                }
437                if j >= 15f32.min((y2 / 8.0).floor()) as u8 {
438                    break;
439                }
440                j += 1;
441            }
442            if i >= utils::min(15f32, (x2 / 8f32).floor()) as u8 {
443                break;
444            }
445            i += 1;
446        }
447        return false;
448    }
449
450    /// returns a savestate in JSON format, as a string
451    /// ```
452    /// let savestate = celeste.save_state();
453    /// celeste.load_state(savestate)
454    /// ```
455    pub fn save_state(&mut self) -> Result<String, serde_json::Error> {
456        serde_json::to_string(self)
457    }
458
459    /// loads a savestate in JSON format, as a string slice
460    /// ```
461    /// let savestate = celeste.save_state();
462    /// celeste.load_state(savestate)
463    /// ```
464    pub fn load_state(&mut self, json: &str) {
465        let deserialized: Self = serde_json::from_str(json).unwrap();
466        for i in 0..deserialized.objects.len() {
467            let o = &deserialized.objects[i];
468            let mut obj = o.borrow_mut();
469            assign_skipped_consts(&mut obj);
470        }
471        *self = deserialized;
472    }
473}
474pub fn draw_time(celeste: &mut Celeste, x: i32, y: i32) {
475    celeste.mem.rectfill(x, y, x + 33, y + 7, 0);
476    let time = format!(
477        "{}:{}:{}",
478        two_digit_str(celeste.minutes / 60),
479        two_digit_str(celeste.minutes % 60),
480        two_digit_str(celeste.seconds as u64)
481    );
482    celeste.mem.print(&time, x + 1, y + 1, 7);
483}
484fn two_digit_str(n: u64) -> String {
485    if n < 10 {
486        format!("0{}", n)
487    } else {
488        format!("{}", n)
489    }
490}
491
492#[derive(Serialize, Deserialize)]
493
494pub struct Cloud {
495    pub x: i32,
496    pub y: i32,
497    pub spd: i32,
498    pub w: i32,
499}
500#[derive(Serialize, Deserialize)]
501
502pub struct Particle {
503    pub x: f32,
504    pub y: f32,
505    pub s: f32,
506    pub spd: f32,
507    pub off: f32,
508    pub c: u8,
509}
510
511#[derive(Serialize, Deserialize)]
512pub struct DeadParticle {
513    pub x: f32,
514    pub y: f32,
515    pub t: f32,
516    pub dx: f32,
517    pub dy: f32,
518}