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 pub mem: Memory,
28 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 pub fn new(map: String, sprites: String, flags: String, fontatlas: String) -> Celeste {
68 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
123 }
124
125 pub fn next_tick(&mut self) {
128 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 self.start_game_flash = 50.0;
189 self.start_game = true;
190 }
192 }
193 }
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 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; }
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 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, );
293 for v in self.objects.clone() {
294 let mut r = v.borrow_mut();
296 if let ObjectType::Platform(_) = r.obj_type {
297 } else {
298 r.draw(self);
299 }
300 }
301
302 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("maddy thorson", 42, 96, 5);
341 self.mem.print("noel berry", 46, 102, 5);
342 }
343
344 }
346 pub fn next_room(&mut self) {
348 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 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 }
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 pub fn save_state(&mut self) -> Result<String, serde_json::Error> {
456 serde_json::to_string(self)
457 }
458
459 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}