Mercurial > touhou
comparison interpreters/src/th06/enemy.rs @ 757:21b186be2590
Split the Rust version into multiple crates.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Tue, 05 Jan 2021 02:16:32 +0100 |
parents | src/th06/enemy.rs@3555845f8cf4 |
children |
comparison
equal
deleted
inserted
replaced
756:4d91790cf8ab | 757:21b186be2590 |
---|---|
1 //! Module providing an Enemy struct, to be changed by EclRunner. | |
2 | |
3 use touhou_formats::th06::anm0::Anm0; | |
4 use touhou_formats::th06::ecl::Rank; | |
5 use crate::th06::anm0::{Sprite, AnmRunner}; | |
6 use crate::th06::interpolator::{Interpolator1, Interpolator2}; | |
7 use touhou_utils::prng::Prng; | |
8 use std::cell::RefCell; | |
9 use std::collections::HashMap; | |
10 use std::rc::{Rc, Weak}; | |
11 | |
12 /// The 2D position of an object in the game. | |
13 #[derive(Debug, Clone, Copy, Default, PartialEq)] | |
14 pub struct Position { | |
15 pub(crate) x: f32, | |
16 pub(crate) y: f32, | |
17 } | |
18 | |
19 /// An offset which can be added to a Position. | |
20 #[derive(Debug, Clone, Copy, Default, PartialEq)] | |
21 pub struct Offset { | |
22 pub(crate) dx: f32, | |
23 pub(crate) dy: f32, | |
24 } | |
25 | |
26 impl Position { | |
27 /// Create said position. | |
28 pub fn new(x: f32, y: f32) -> Position { | |
29 Position { x, y } | |
30 } | |
31 } | |
32 | |
33 impl Offset { | |
34 /// Create said offset. | |
35 pub fn new(dx: f32, dy: f32) -> Offset { | |
36 Offset { dx, dy } | |
37 } | |
38 } | |
39 | |
40 impl std::ops::Add<Offset> for Position { | |
41 type Output = Position; | |
42 fn add(self, offset: Offset) -> Position { | |
43 Position { | |
44 x: self.x + offset.dx, | |
45 y: self.y + offset.dy, | |
46 } | |
47 } | |
48 } | |
49 | |
50 impl std::ops::Sub<Position> for Position { | |
51 type Output = Offset; | |
52 fn sub(self, other: Position) -> Offset { | |
53 Offset { | |
54 dx: other.x - self.x, | |
55 dy: other.y - self.y, | |
56 } | |
57 } | |
58 } | |
59 | |
60 type Callback = i32; | |
61 | |
62 #[derive(Debug, Clone)] | |
63 /// XXX | |
64 pub struct Laser { | |
65 /// XXX | |
66 pub placeholder: u32 | |
67 } | |
68 | |
69 #[derive(Debug, Clone, Default)] | |
70 struct Process; | |
71 | |
72 /// Struct representing the player. | |
73 pub struct Player { | |
74 pos: Position, | |
75 } | |
76 | |
77 /// Struct representing an enemy bullet. | |
78 pub struct Bullet { | |
79 /// Current position of the bullet. | |
80 pub pos: Position, | |
81 | |
82 /// Current speed of the bullet. | |
83 pub speed: f32, | |
84 | |
85 /// Current XXX of the bullet. | |
86 pub dpos: [f32; 3], | |
87 | |
88 /// Current XXX of the bullet. | |
89 pub flags: u32, | |
90 | |
91 /// Current frame of the bullet. | |
92 pub frame: i32, | |
93 | |
94 /// Current attributes of the bullet. | |
95 pub attributes: [f32; 2], | |
96 | |
97 /// TODO: what are the values? | |
98 pub state: i8, | |
99 } | |
100 | |
101 /// God struct of our game. | |
102 pub struct Game { | |
103 enemies: Vec<Rc<RefCell<Enemy>>>, | |
104 anmrunners: Vec<Rc<RefCell<AnmRunner>>>, | |
105 pub(crate) bullets: Vec<Rc<RefCell<Bullet>>>, | |
106 player: Rc<RefCell<Player>>, | |
107 pub(crate) prng: Rc<RefCell<Prng>>, | |
108 rank: Rank, | |
109 difficulty: i32, | |
110 } | |
111 | |
112 impl Game { | |
113 /// Create said god struct. | |
114 pub fn new(prng: Rc<RefCell<Prng>>, rank: Rank) -> Game { | |
115 Game { | |
116 enemies: Vec::new(), | |
117 anmrunners: Vec::new(), | |
118 bullets: Vec::new(), | |
119 player: Rc::new(RefCell::new(Player { pos: Position { x: 192., y: 384. } })), | |
120 prng, | |
121 rank, | |
122 difficulty: 0, | |
123 } | |
124 } | |
125 | |
126 /// Run the simulation for a single frame. | |
127 pub fn run_frame(&mut self) { | |
128 /* | |
129 for eclrunner in self.eclrunners { | |
130 eclrunner.run_frame(); | |
131 } | |
132 */ | |
133 | |
134 for anmrunner in self.anmrunners.iter() { | |
135 let mut anmrunner = anmrunner.borrow_mut(); | |
136 anmrunner.run_frame(); | |
137 } | |
138 } | |
139 | |
140 /// Returns a list of all sprites currently being displayed on screen. | |
141 pub fn get_sprites(&self) -> Vec<(f32, f32, f32, Rc<RefCell<Sprite>>)> { | |
142 let mut sprites = vec![]; | |
143 for enemy in self.enemies.iter() { | |
144 let enemy = enemy.borrow(); | |
145 let anmrunner = enemy.anmrunner.upgrade().unwrap(); | |
146 let anmrunner = anmrunner.borrow(); | |
147 let sprite = anmrunner.get_sprite(); | |
148 sprites.push((enemy.pos.x, enemy.pos.y, enemy.z, sprite)); | |
149 } | |
150 sprites | |
151 } | |
152 | |
153 // TODO: Fix this function so we can stop making Game::bullets pub. | |
154 /* | |
155 /// Apply a function on all bullets. | |
156 pub fn iter_bullets(&mut self, mut f: impl FnMut(Bullet)) { | |
157 self.bullets.iter().map(|bullet| { | |
158 let mut bullet = bullet.borrow_mut(); | |
159 f(*bullet) | |
160 }); | |
161 } | |
162 */ | |
163 | |
164 pub(crate) fn get_player(&self) -> Rc<RefCell<Player>> { | |
165 self.player.clone() | |
166 } | |
167 } | |
168 | |
169 /// Common to all elements in game. | |
170 struct Element { | |
171 pos: Position, | |
172 removed: bool, | |
173 anmrunner: AnmRunner, | |
174 } | |
175 | |
176 #[derive(PartialEq)] | |
177 pub(crate) struct DifficultyCoeffs { | |
178 pub(crate) speed_a: f32, | |
179 pub(crate) speed_b: f32, | |
180 pub(crate) nb_a: i16, | |
181 pub(crate) nb_b: i16, | |
182 pub(crate) shots_a: i16, | |
183 pub(crate) shots_b: i16, | |
184 } | |
185 | |
186 impl Default for DifficultyCoeffs { | |
187 fn default() -> DifficultyCoeffs { | |
188 DifficultyCoeffs { | |
189 speed_a: -0.5, | |
190 speed_b: 0.5, | |
191 nb_a: 0, | |
192 nb_b: 0, | |
193 shots_a: 0, | |
194 shots_b: 0, | |
195 } | |
196 } | |
197 } | |
198 | |
199 #[derive(Debug, Clone, Default, PartialEq)] | |
200 pub(crate) struct BulletAttributes { | |
201 pub(crate) anim: i16, | |
202 pub(crate) sprite_index_offset: i16, | |
203 pub(crate) pos: Position, // Doesn’t have a z field. | |
204 pub(crate) launch_angle: f32, | |
205 pub(crate) angle: f32, | |
206 pub(crate) speed: f32, | |
207 pub(crate) speed2: f32, | |
208 pub(crate) extended_attributes: (i32, i32, i32, i32, f32, f32, f32, f32), | |
209 // unknown: x32, | |
210 pub(crate) bullets_per_shot: i16, | |
211 pub(crate) number_of_shots: i16, | |
212 pub(crate) bullet_type: i16, | |
213 // zero: x32, | |
214 pub(crate) flags: u32, | |
215 | |
216 /// Which sound to play when the bullet gets fired. | |
217 pub sound: Option<u8>, | |
218 } | |
219 | |
220 impl BulletAttributes { | |
221 /// Fire! | |
222 pub fn fire(&mut self) { | |
223 println!("PAN!"); | |
224 } | |
225 } | |
226 | |
227 #[derive(PartialEq)] | |
228 pub(crate) enum Direction { | |
229 Left, | |
230 Center, | |
231 Right, | |
232 } | |
233 | |
234 impl Default for Direction { | |
235 fn default() -> Direction { | |
236 Direction::Center | |
237 } | |
238 } | |
239 | |
240 /// The enemy struct, containing everything pertaining to an enemy. | |
241 #[derive(Default)] | |
242 pub struct Enemy { | |
243 // Common to all elements in game. | |
244 pub(crate) pos: Position, | |
245 pub(crate) removed: bool, | |
246 pub(crate) anmrunner: Weak<RefCell<AnmRunner>>, | |
247 | |
248 // Specific to enemy. | |
249 // Floats. | |
250 pub(crate) z: f32, | |
251 pub(crate) angle: f32, | |
252 pub(crate) speed: f32, | |
253 pub(crate) rotation_speed: f32, | |
254 pub(crate) acceleration: f32, | |
255 | |
256 // Ints. | |
257 pub(crate) type_: u32, | |
258 pub(crate) bonus_dropped: u32, | |
259 pub(crate) die_score: u32, | |
260 /// XXX | |
261 pub frame: u32, | |
262 pub(crate) life: u32, | |
263 pub(crate) death_flags: u32, | |
264 pub(crate) current_laser_id: u32, | |
265 pub(crate) low_life_trigger: Option<u32>, | |
266 pub(crate) timeout: Option<u32>, | |
267 pub(crate) remaining_lives: u32, | |
268 bullet_launch_interval: u32, | |
269 bullet_launch_timer: u32, | |
270 pub(crate) death_anim: i32, | |
271 pub(crate) direction: Direction, | |
272 pub(crate) update_mode: u32, | |
273 | |
274 // Bools. | |
275 pub(crate) visible: bool, | |
276 pub(crate) was_visible: bool, | |
277 pub(crate) touchable: bool, | |
278 pub(crate) collidable: bool, | |
279 pub(crate) damageable: bool, | |
280 pub(crate) boss: bool, | |
281 pub(crate) automatic_orientation: bool, | |
282 pub(crate) delay_attack: bool, | |
283 // Actually part of type_ atm. | |
284 pub(crate) mirror: bool, | |
285 | |
286 // Tuples. | |
287 pub(crate) difficulty_coeffs: DifficultyCoeffs, | |
288 pub(crate) bullet_attributes: BulletAttributes, | |
289 pub(crate) bullet_offset: Offset, | |
290 pub(crate) movement_dependant_sprites: Option<(u8, u8, u8, u8)>, | |
291 pub(crate) screen_box: Option<(f32, f32, f32, f32)>, | |
292 | |
293 // Callbacks. | |
294 pub(crate) death_callback: Option<Callback>, | |
295 pub(crate) boss_callback: Option<Callback>, | |
296 pub(crate) low_life_callback: Option<Callback>, | |
297 pub(crate) timeout_callback: Option<Callback>, | |
298 | |
299 // Laser. | |
300 pub(crate) laser_by_id: HashMap<u32, Laser>, | |
301 | |
302 // Options. | |
303 // TODO: actually a 8 element array. | |
304 options: Vec<Element>, | |
305 | |
306 // Interpolators. | |
307 pub(crate) interpolator: Option<Interpolator2<f32>>, | |
308 pub(crate) speed_interpolator: Option<Interpolator1<f32>>, | |
309 | |
310 // Misc stuff, do we need them? | |
311 pub(crate) anm0: Weak<RefCell<[Anm0; 2]>>, | |
312 process: Rc<RefCell<Process>>, | |
313 pub(crate) game: Weak<RefCell<Game>>, | |
314 pub(crate) prng: Weak<RefCell<Prng>>, | |
315 pub(crate) hitbox_half_size: [f32; 2], | |
316 } | |
317 | |
318 impl Enemy { | |
319 /// Create a new enemy. | |
320 pub fn new(pos: Position, life: i16, bonus_dropped: i16, die_score: u32, mirror: bool, anm0: Weak<RefCell<[Anm0; 2]>>, game: Weak<RefCell<Game>>) -> Rc<RefCell<Enemy>> { | |
321 let game_rc = game.upgrade().unwrap(); | |
322 let mut enemy = Enemy { | |
323 pos, | |
324 anm0, | |
325 game, | |
326 visible: true, | |
327 // XXX: shouldn’t be u32, since that can be -1. | |
328 bonus_dropped: bonus_dropped as u32, | |
329 die_score, | |
330 life: if life < 0 { 1 } else { life as u32 }, | |
331 touchable: true, | |
332 collidable: true, | |
333 damageable: true, | |
334 mirror, | |
335 ..Default::default() | |
336 }; | |
337 let mut game = game_rc.borrow_mut(); | |
338 enemy.prng = Rc::downgrade(&game.prng); | |
339 let enemy = Rc::new(RefCell::new(enemy)); | |
340 game.enemies.push(enemy.clone()); | |
341 enemy | |
342 } | |
343 | |
344 /// Sets the animation to the one indexed by index in the current anm0. | |
345 pub fn set_anim(&mut self, index: u8) { | |
346 let anm0 = self.anm0.upgrade().unwrap(); | |
347 let game = self.game.upgrade().unwrap(); | |
348 let sprite = Rc::new(RefCell::new(Sprite::new())); | |
349 let anmrunner = AnmRunner::new(anm0, index, sprite, self.prng.clone(), 0); | |
350 let anmrunner = Rc::new(RefCell::new(anmrunner)); | |
351 self.anmrunner = Rc::downgrade(&anmrunner); | |
352 (*game.borrow_mut()).anmrunners.push(anmrunner); | |
353 } | |
354 | |
355 /// Sets the current position of the enemy. | |
356 pub fn set_pos(&mut self, x: f32, y: f32, z: f32) { | |
357 self.pos.x = x; | |
358 self.pos.y = y; | |
359 self.z = z; | |
360 } | |
361 | |
362 /// Sets the hitbox around the enemy. | |
363 pub fn set_hitbox(&mut self, width: f32, height: f32) { | |
364 self.hitbox_half_size = [width / 2., height / 2.]; | |
365 } | |
366 | |
367 /// Defines the attributes for the next bullet fired, and fire it if delay_attack isn’t set! | |
368 pub fn set_bullet_attributes(&mut self, opcode: u16, anim: i16, sprite_index_offset: i16, | |
369 bullets_per_shot: i16, number_of_shots: i16, speed: f32, | |
370 speed2: f32, launch_angle: f32, angle: f32, flags: u32) { | |
371 // Get the coeffs for the current difficulty. | |
372 let difficulty = self.get_difficulty() as i16; | |
373 let coeff_nb = self.difficulty_coeffs.nb_a + (self.difficulty_coeffs.nb_b - self.difficulty_coeffs.nb_a) * difficulty / 32; | |
374 let coeff_shots = self.difficulty_coeffs.shots_a + (self.difficulty_coeffs.shots_b - self.difficulty_coeffs.shots_a) * difficulty / 32; | |
375 let coeff_speed = self.difficulty_coeffs.speed_a + (self.difficulty_coeffs.speed_b - self.difficulty_coeffs.speed_a) * difficulty as f32 / 32.; | |
376 | |
377 let opcode = 67; | |
378 let mut bullet = &mut self.bullet_attributes; | |
379 | |
380 bullet.anim = anim; | |
381 bullet.bullet_type = opcode - 67; | |
382 bullet.sprite_index_offset = sprite_index_offset; | |
383 | |
384 bullet.bullets_per_shot = bullets_per_shot + coeff_nb; | |
385 if bullet.bullets_per_shot < 1 { | |
386 bullet.bullets_per_shot = 1; | |
387 } | |
388 | |
389 bullet.number_of_shots = number_of_shots + coeff_shots; | |
390 if bullet.number_of_shots < 1 { | |
391 bullet.number_of_shots = 1; | |
392 } | |
393 | |
394 bullet.pos = self.pos + self.bullet_offset; | |
395 | |
396 bullet.speed = speed + coeff_speed; | |
397 if bullet.speed < 0.3 { | |
398 bullet.speed = 0.3; | |
399 } | |
400 | |
401 bullet.speed2 = speed2 + coeff_speed / 2.; | |
402 if bullet.speed2 < 0.3 { | |
403 bullet.speed2 = 0.3; | |
404 } | |
405 | |
406 bullet.launch_angle = launch_angle.atan2(0.); | |
407 bullet.angle = angle; | |
408 bullet.flags = flags; | |
409 | |
410 if !self.delay_attack { | |
411 bullet.fire(); | |
412 } | |
413 } | |
414 | |
415 /// Sets the bullet launch interval. | |
416 pub(crate) fn set_bullet_launch_interval(&mut self, rand_start: u32, interval: i32) { | |
417 let coeff_interval = interval / 5; | |
418 let difficulty_modifier = coeff_interval + (-coeff_interval * 2) * self.get_difficulty() / 32; | |
419 self.bullet_launch_interval = (interval + difficulty_modifier) as u32; | |
420 if self.bullet_launch_interval > 0 { | |
421 self.bullet_launch_timer = rand_start % self.bullet_launch_interval; | |
422 } | |
423 } | |
424 | |
425 /// Stubbed for now. | |
426 pub(crate) fn play_sound(&self, sound_index: i32) { | |
427 println!("Playing sound {}!", sound_index); | |
428 } | |
429 | |
430 /// Stubbed for now. | |
431 pub(crate) fn set_boss(&self, enable: bool) { | |
432 match enable { | |
433 true => println!("Enemy is now boss!"), | |
434 false => println!("Enemy is not boss anymore."), | |
435 } | |
436 } | |
437 | |
438 /// Run all interpolators and such, and update internal variables once per | |
439 /// frame. | |
440 pub fn update(&mut self) { | |
441 let Position { mut x, mut y } = self.pos; | |
442 | |
443 let speed = if self.update_mode == 1 { | |
444 let mut speed = 0.; | |
445 if let Some(interpolator) = &self.interpolator { | |
446 let values = interpolator.values(self.frame); | |
447 x = values[0]; | |
448 y = values[1]; | |
449 } | |
450 if let Some(interpolator) = &self.speed_interpolator { | |
451 let values = interpolator.values(self.frame); | |
452 speed = values[0]; | |
453 } | |
454 speed | |
455 } else { | |
456 let speed = self.speed; | |
457 self.speed += self.acceleration; | |
458 self.angle += self.rotation_speed; | |
459 speed | |
460 }; | |
461 | |
462 let dx = self.angle.cos() * speed; | |
463 let dy = self.angle.sin() * speed; | |
464 if self.mirror { | |
465 x -= dx; | |
466 } else { | |
467 x += dx; | |
468 } | |
469 y += dy; | |
470 | |
471 if let Some((end_left, end_right, left, right)) = self.movement_dependant_sprites { | |
472 if x < self.pos.x && self.direction != Direction::Left { | |
473 self.set_anim(left); | |
474 self.direction = Direction::Left; | |
475 } else if x > self.pos.x && self.direction != Direction::Right { | |
476 self.set_anim(right); | |
477 self.direction = Direction::Right; | |
478 } else if x == self.pos.x && self.direction != Direction::Center { | |
479 let anim = if self.direction == Direction::Left { | |
480 end_left | |
481 } else { | |
482 end_right | |
483 }; | |
484 self.set_anim(anim); | |
485 self.direction = Direction::Center; | |
486 } | |
487 } | |
488 | |
489 self.pos = Position { x, y }; | |
490 | |
491 if self.bullet_launch_interval != 0 { | |
492 if self.bullet_launch_timer == 0 { | |
493 self.bullet_attributes.fire(); | |
494 self.bullet_launch_timer = self.bullet_launch_interval; | |
495 } | |
496 self.bullet_launch_timer += 1; | |
497 self.bullet_launch_timer %= self.bullet_launch_interval; | |
498 } | |
499 | |
500 self.frame += 1; | |
501 } | |
502 | |
503 pub(crate) fn get_rank(&self) -> Rank { | |
504 let game = self.game.upgrade().unwrap(); | |
505 let game = game.borrow(); | |
506 game.rank | |
507 } | |
508 | |
509 pub(crate) fn get_difficulty(&self) -> i32 { | |
510 let game = self.game.upgrade().unwrap(); | |
511 let game = game.borrow(); | |
512 game.difficulty | |
513 } | |
514 | |
515 // TODO: use a trait for positionable entities. | |
516 pub(crate) fn get_angle_to(&self, player: Rc<RefCell<Player>>) -> f32 { | |
517 let player = player.borrow(); | |
518 let offset = self.pos - player.pos; | |
519 offset.dy.atan2(offset.dx) | |
520 } | |
521 | |
522 pub(crate) fn set_aux_anm(&self, number: i32, script: i32) { | |
523 println!("TODO: Spawn aux anm {} with script {}.", number, script); | |
524 } | |
525 } | |
526 | |
527 trait Renderable { | |
528 fn get_sprites(&self) -> Vec<Rc<RefCell<Sprite>>>; | |
529 } | |
530 | |
531 impl Renderable for Enemy { | |
532 fn get_sprites(&self) -> Vec<Rc<RefCell<Sprite>>> { | |
533 let anmrunner = self.anmrunner.upgrade().unwrap(); | |
534 let anmrunner = anmrunner.borrow(); | |
535 vec![anmrunner.get_sprite()] | |
536 } | |
537 } | |
538 | |
539 #[cfg(test)] | |
540 mod tests { | |
541 use super::*; | |
542 use std::io::{self, Read}; | |
543 use std::fs::File; | |
544 | |
545 #[test] | |
546 fn enemy() { | |
547 let file = File::open("EoSD/ST/stg1enm.anm").unwrap(); | |
548 let mut file = io::BufReader::new(file); | |
549 let mut buf = vec![]; | |
550 file.read_to_end(&mut buf).unwrap(); | |
551 let (_, mut anms) = Anm0::from_slice(&buf).unwrap(); | |
552 let anm0 = anms.pop().unwrap(); | |
553 | |
554 let file = File::open("EoSD/ST/stg1enm2.anm").unwrap(); | |
555 let mut file = io::BufReader::new(file); | |
556 let mut buf = vec![]; | |
557 file.read_to_end(&mut buf).unwrap(); | |
558 let (_, mut anms) = Anm0::from_slice(&buf).unwrap(); | |
559 let anm0_bis = anms.pop().unwrap(); | |
560 | |
561 let anm0 = Rc::new(RefCell::new([anm0, anm0_bis])); | |
562 let prng = Rc::new(RefCell::new(Prng::new(0))); | |
563 let game = Game::new(prng, Rank::EASY); | |
564 let game = Rc::new(RefCell::new(game)); | |
565 let enemy = Enemy::new(Position::new(0., 0.), 500, 0, 640, Rc::downgrade(&anm0), Rc::downgrade(&game)); | |
566 let mut enemy = enemy.borrow_mut(); | |
567 assert!(enemy.anmrunner.upgrade().is_none()); | |
568 enemy.set_anim(0); | |
569 assert!(enemy.anmrunner.upgrade().is_some()); | |
570 } | |
571 } |