demo
[kaka/rust-sdl-test.git] / src / core / object / character.rs
1 use core::controller::Controller;
2 use core::object::boll::Boll;
3 use core::level::{Level, Wall, IntersectResult::Intersection};
4 use core::object::{Object, Objects, ObjectState};
5 use core::render::Renderer;
6 use geometry::Point;
7 use point;
8 use sdl2::rect::Rect;
9 use sprites::SpriteManager;
10 use std::cell::RefCell;
11 use std::rc::Rc;
12 use time::Duration;
13
14 ////////// TRIGGER /////////////////////////////////////////////////////////////
15
16 trait Trigger {
17     fn update(&mut self, body: &Body, ctrl: &Controller, dt: Duration) -> Option<Box<dyn State>>;
18 }
19
20 ////////// STATE ///////////////////////////////////////////////////////////////
21
22 trait State {
23     fn enter(&mut self, _body: &mut Body, _ctrl: &Controller, _objects: &mut Objects) {}
24     fn exit(&self) {}
25     fn update(&mut self, body: &mut Body, ctrl: &Controller, objects: &mut Objects, lvl: &Level, dt: Duration) -> Option<Box<dyn State>>;
26 }
27
28 ////////// CHARACTER ///////////////////////////////////////////////////////////
29
30 struct Body {
31     pos: Point<f64>,
32     vel: Point<f64>,
33     standing_on: Option<Wall>,
34 }
35
36 pub struct Character {
37     ctrl: Rc<RefCell<Controller>>,
38     body: Body,
39     triggers: Vec<Box<dyn Trigger>>,
40     state: Box<dyn State>,
41 }
42
43 impl Character {
44     pub fn new(ctrl: Rc<RefCell<Controller>>) -> Self {
45         Character {
46             ctrl,
47             body: Body {
48                 pos: point!(300.0, 300.0),
49                 vel: point!(0.0, 0.0),
50                 standing_on: None,
51             },
52             triggers: vec!(Box::new(JumpTrigger)),
53             state: Box::new(FallState),
54         }
55     }
56 }
57
58 impl Object for Character {
59     fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState {
60         let ctrl = self.ctrl.borrow();
61
62         if let Some(state) = self.state.update(&mut self.body, &ctrl, objects, lvl, dt) {
63             self.state.exit();
64             self.state = state;
65             self.state.enter(&mut self.body, &ctrl, objects);
66         }
67
68         match &self.body.standing_on {
69             Some(_wall) => {
70             },
71             None => { // in air
72                 if let Intersection(wall, pos) = lvl.intersect_walls(self.body.pos - self.body.vel, self.body.pos) {
73                     self.body.standing_on = Some(wall);
74                     self.body.pos = pos;
75
76                     self.state.exit();
77                     self.state = Box::new(StandState); // eller loopa igenom alla triggers eller states för att hitta rätt state?
78                     self.state.enter(&mut self.body, &ctrl, objects);
79                 }
80             }
81         }
82
83         for trigger in &mut self.triggers {
84             if let Some(state) = trigger.update(&self.body, &ctrl, dt) {
85                 self.state.exit();
86                 self.state = state;
87                 self.state.enter(&mut self.body, &ctrl, objects);
88             }
89         }
90
91         if ctrl.shoot.is_pressed {
92             use rand::distributions::{Distribution, Normal};
93             let normal = Normal::new(0.0, 0.1);
94             let direction = if ctrl.aim.to_point().length() > 0.1 { ctrl.aim.to_point() } else { ctrl.mov.to_point() };
95             for _i in 0..100 {
96                 objects.push(Box::new(Boll::new(
97                     self.body.pos + point!(0.0, -16.0), // half the height of mario
98                     direction * (10.0 + rand::random::<f64>()) + point!(normal.sample(&mut rand::thread_rng()), normal.sample(&mut rand::thread_rng())) + self.body.vel,
99                     2,
100                 )));
101             }
102             ctrl.rumble(1.0, dt);
103             self.body.vel -= direction * 0.1;
104         }
105
106         ObjectState::Alive
107     }
108
109     fn render(&self, renderer: &mut Renderer, sprites: &SpriteManager) {
110         let block = sprites.get("mario");
111         let size = 32;
112         renderer.blit(block, None, Rect::new(self.body.pos.x as i32 - size as i32 / 2, self.body.pos.y as i32 - size as i32, size, size));
113
114         let ctrl = &self.ctrl.borrow();
115         let l = 300.0;
116         let pos = (self.body.pos.x as i32, self.body.pos.y as i32);
117         // // axis values
118         // let p = (self.body.pos + ctrl.aim.to_axis_point() * l).to_i32().into();
119         // renderer.draw_line(pos, p, (0, 255, 0));
120         // draw_cross(renderer, p);
121         // values limited to unit vector
122         let p = (self.body.pos + ctrl.aim.to_point() * l).to_i32().into();
123         renderer.draw_line(pos, p, (255, 0, 0));
124         draw_cross(renderer, p);
125         let p = (self.body.pos + ctrl.mov.to_point() * l).to_i32().into();
126         renderer.draw_line(pos, p, (0, 255, 0));
127         draw_cross(renderer, p);
128         // // circle values
129         // let p = (self.body.pos + Point::from(ctrl.aim.a) * l).to_i32().into();
130         // renderer.draw_line(pos, p, (0, 0, 255));
131         // draw_cross(renderer, p);
132     }
133 }
134
135 fn draw_cross(renderer: &mut Renderer, p: (i32, i32)) {
136     renderer.canvas().draw_line((p.0 - 5, p.1), (p.0 + 5, p.1)).unwrap();
137     renderer.canvas().draw_line((p.0, p.1 - 5), (p.0, p.1 + 5)).unwrap();
138 }
139
140 ////////// FALLING /////////////////////////////////////////////////////////////
141
142 struct FallState;
143
144 impl State for FallState {
145     fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, lvl: &Level, _dt: Duration) -> Option<Box<dyn State>> {
146         body.vel += lvl.gravity;
147         body.pos += body.vel;
148
149         match ctrl.mov.x {
150             v if v < -0.9 && body.vel.x > -5.0 => { body.vel.x -= 0.5 }
151             v if v > 0.9 && body.vel.x < 5.0 => { body.vel.x += 0.5 }
152             _ => {}
153         }
154
155         None
156     }
157 }
158
159 ////////// STANDING ////////////////////////////////////////////////////////////
160
161 struct StandState;
162
163 impl State for StandState {
164     fn enter(&mut self, body: &mut Body, _ctrl: &Controller, _objects: &mut Objects) {
165         if let Some(wall) = &body.standing_on {
166             body.vel = body.vel.project_onto(wall.angle());
167         }
168     }
169
170     fn update(&mut self, body: &mut Body, _ctrl: &Controller, _objects: &mut Objects, _lvl: &Level, _dt: Duration) -> Option<Box<dyn State>> {
171         if let Some(wall) = &body.standing_on {
172             body.vel += _ctrl.mov.to_point() * 0.2;
173             let (mut pos, mut vel) = wall.from_2d(&body.pos, &body.vel);
174             vel *= 0.99;
175             pos += vel;
176             if pos < 0.0 {
177                 let w = wall.previous();
178                 pos += w.length();
179                 let (p, v) = w.to_2d(pos, vel);
180                 body.pos = p;
181                 body.vel = v;
182                 body.standing_on = Some(w);
183             } else if pos > wall.length() {
184                 let w = wall.next();
185                 pos -= wall.length();
186                 let (p, v) = w.to_2d(pos, vel);
187                 body.pos = p;
188                 body.vel = v;
189                 body.standing_on = Some(w);
190             } else {
191                 let (p, v) = wall.to_2d(pos, vel);
192                 body.pos = p;
193                 body.vel = v;
194             }
195         }
196
197         None
198     }
199 }
200
201 ////////// WALKING /////////////////////////////////////////////////////////////
202
203 struct WalkTrigger;
204
205 impl Trigger for WalkTrigger {
206     fn update(&mut self, body: &Body, ctrl: &Controller, _dt: Duration) -> Option<Box<dyn State>> {
207         if body.standing_on.is_some() && (ctrl.mov.x < -0.1 || ctrl.mov.x > 0.1) {
208             Some(Box::new(WalkState))
209         } else {
210             None
211         }
212     }
213 }
214
215 struct WalkState;
216
217 impl State for WalkState {
218     fn enter(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects) {
219         if let Some(wall) = &body.standing_on {
220             if ctrl.mov.to_point().length() < 0.1 {
221                 body.vel = wall.normal().into();
222             } else {
223                 body.vel = ctrl.mov.to_point();
224             }
225             body.vel *= 5.0;
226             body.pos += body.vel * 0.1;
227             body.standing_on = None;
228         }
229     }
230
231     fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, lvl: &Level, _dt: Duration) -> Option<Box<dyn State>> {
232         body.vel += lvl.gravity;
233         body.pos += body.vel;
234
235         match ctrl.mov.x {
236             v if v < -0.9 && body.vel.x > -5.0 => { body.vel.x -= 0.5 }
237             v if v > 0.9 && body.vel.x < 5.0 => { body.vel.x += 0.5 }
238             _ => {}
239         }
240
241         None
242     }
243 }
244
245 ////////// JUMPING /////////////////////////////////////////////////////////////
246
247 struct JumpTrigger;
248
249 impl Trigger for JumpTrigger {
250     fn update(&mut self, body: &Body, ctrl: &Controller, _dt: Duration) -> Option<Box<dyn State>> {
251         if body.standing_on.is_some() && ctrl.jump.is_pressed && !ctrl.jump.was_pressed { // this is redundant now because JumpState needs a wall to get starting velocity, but that will probably change
252             Some(Box::new(JumpState))
253         } else {
254             None
255         }
256     }
257 }
258
259 struct JumpState;
260
261 impl State for JumpState {
262     fn enter(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects) {
263         if let Some(wall) = &body.standing_on {
264             let direction = if ctrl.mov.to_point().length() < 0.1 {
265                 wall.normal().into()
266             } else {
267                 ctrl.mov.to_point()
268             };
269             body.vel += direction * 5.0;
270             body.pos += body.vel * 0.1;
271             body.standing_on = None;
272         }
273     }
274
275     fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, lvl: &Level, _dt: Duration) -> Option<Box<dyn State>> {
276         body.vel += lvl.gravity;
277         body.pos += body.vel;
278
279         match ctrl.mov.x {
280             v if v < -0.9 && body.vel.x > -5.0 => { body.vel.x -= 0.5 }
281             v if v > 0.9 && body.vel.x < 5.0 => { body.vel.x += 0.5 }
282             _ => {}
283         }
284
285         None
286     }
287 }