1use macroquad::math::{vec2, Rect, Vec2};
2
3use std::collections::HashSet;
4
5#[derive(Debug, PartialEq, Clone, Copy)]
6pub enum Tile {
7 Empty,
8 Solid,
9 JumpThrough,
10 Collider,
11}
12
13impl Tile {
14 fn or(self, other: Tile) -> Tile {
15 match (self, other) {
16 (Tile::Empty, Tile::Empty) => Tile::Empty,
17 (Tile::JumpThrough, Tile::JumpThrough) => Tile::JumpThrough,
18 (Tile::JumpThrough, Tile::Empty) => Tile::JumpThrough,
19 (Tile::Empty, Tile::JumpThrough) => Tile::JumpThrough,
20 _ => Tile::Solid,
21 }
22 }
23}
24pub struct StaticTiledLayer {
25 static_colliders: Vec<Tile>,
26 tile_width: f32,
27 tile_height: f32,
28 width: usize,
29 tag: u8,
30}
31
32pub struct World {
33 static_tiled_layers: Vec<StaticTiledLayer>,
34 solids: Vec<(Solid, Collider)>,
35 actors: Vec<(Actor, Collider)>,
36}
37
38#[derive(Clone, Debug)]
39struct Collider {
40 collidable: bool,
41 squished: bool,
42 pos: Vec2,
43 width: i32,
44 height: i32,
45 x_remainder: f32,
46 y_remainder: f32,
47 squishers: HashSet<Solid>,
48 descent: bool,
49 seen_wood: bool,
50}
51
52impl Collider {
53 pub fn rect(&self) -> Rect {
54 Rect::new(
55 self.pos.x,
56 self.pos.y,
57 self.width as f32,
58 self.height as f32,
59 )
60 }
61}
62
63#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
64pub struct Actor(usize);
65
66#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
67pub struct Solid(usize);
68
69impl World {
70 pub fn new() -> World {
71 World {
72 static_tiled_layers: vec![],
73 actors: vec![],
74 solids: vec![],
75 }
76 }
77
78 pub fn add_static_tiled_layer(
79 &mut self,
80 static_colliders: Vec<Tile>,
81 tile_width: f32,
82 tile_height: f32,
83 width: usize,
84 tag: u8,
85 ) {
86 self.static_tiled_layers.push(StaticTiledLayer {
87 static_colliders,
88 tile_width,
89 tile_height,
90 width,
91 tag,
92 });
93 }
94 pub fn add_actor(&mut self, pos: Vec2, width: i32, height: i32) -> Actor {
95 let actor = Actor(self.actors.len());
96
97 let mut descent = false;
98 let mut seen_wood = false;
99 let tile = self.collide_solids(pos, width, height);
100 if tile == Tile::JumpThrough {
101 descent = true;
102 seen_wood = true;
103 }
104 self.actors.push((
105 actor,
106 Collider {
107 collidable: true,
108 squished: false,
109 pos,
110 width,
111 height,
112 x_remainder: 0.,
113 y_remainder: 0.,
114 squishers: HashSet::new(),
115 descent,
116 seen_wood,
117 },
118 ));
119
120 actor
121 }
122
123 pub fn add_solid(&mut self, pos: Vec2, width: i32, height: i32) -> Solid {
124 let solid = Solid(self.solids.len());
125
126 self.solids.push((
127 solid,
128 Collider {
129 collidable: true,
130 squished: false,
131 pos,
132 width,
133 height,
134 x_remainder: 0.,
135 y_remainder: 0.,
136 squishers: HashSet::new(),
137 descent: false,
138 seen_wood: false,
139 },
140 ));
141
142 solid
143 }
144
145 pub fn set_actor_position(&mut self, actor: Actor, pos: Vec2) {
146 let collider = &mut self.actors[actor.0].1;
147
148 collider.x_remainder = 0.0;
149 collider.y_remainder = 0.0;
150 collider.pos = pos;
151 }
152
153 pub fn descent(&mut self, actor: Actor) {
154 let collider = &mut self.actors[actor.0].1;
155 collider.descent = true;
156 }
157
158 pub fn move_v(&mut self, actor: Actor, dy: f32) -> bool {
159 let id = actor.0;
160 let mut collider = self.actors[id].1.clone();
161
162 collider.y_remainder += dy;
163
164 let mut move_ = collider.y_remainder.round() as i32;
165 if move_ != 0 {
166 collider.y_remainder -= move_ as f32;
167 let sign = move_.signum();
168
169 while move_ != 0 {
170 let tile = self.collide_solids(
171 collider.pos + vec2(0., sign as f32),
172 collider.width,
173 collider.height,
174 );
175
176 if tile == Tile::JumpThrough && collider.descent {
178 collider.seen_wood = true;
179 }
180 if tile == Tile::JumpThrough && sign < 0 {
182 collider.seen_wood = true;
183 collider.descent = true;
184 }
185 if tile == Tile::Empty || (tile == Tile::JumpThrough && collider.descent) {
186 collider.pos.y += sign as f32;
187 move_ -= sign;
188 } else {
189 self.actors[id].1 = collider;
190
191 return false;
192 }
193 }
194 }
195
196 let tile = self.collide_solids(collider.pos, collider.width, collider.height);
198 if tile != Tile::JumpThrough {
199 collider.seen_wood = false;
200 collider.descent = false;
201 }
202
203 self.actors[id].1 = collider;
204 true
205 }
206
207 pub fn move_h(&mut self, actor: Actor, dx: f32) -> bool {
208 let id = actor.0;
209 let mut collider = self.actors[id].1.clone();
210 collider.x_remainder += dx;
211
212 let mut move_ = collider.x_remainder.round() as i32;
213 if move_ != 0 {
214 collider.x_remainder -= move_ as f32;
215 let sign = move_.signum();
216
217 while move_ != 0 {
218 let tile = self.collide_solids(
219 collider.pos + vec2(sign as f32, 0.),
220 collider.width,
221 collider.height,
222 );
223 if tile == Tile::JumpThrough {
224 collider.descent = true;
225 collider.seen_wood = true;
226 }
227 if tile == Tile::Empty || tile == Tile::JumpThrough {
228 collider.pos.x += sign as f32;
229 move_ -= sign;
230 } else {
231 self.actors[id].1 = collider;
232 return false;
233 }
234 }
235 }
236 self.actors[id].1 = collider;
237 true
238 }
239
240 pub fn solid_move(&mut self, solid: Solid, dx: f32, dy: f32) {
241 let collider = &mut self.solids[solid.0].1;
242
243 collider.x_remainder += dx;
244 collider.y_remainder += dy;
245 let move_x = collider.x_remainder.round() as i32;
246 let move_y = collider.y_remainder.round() as i32;
247
248 let mut riding_actors = vec![];
249 let mut pushing_actors = vec![];
250
251 let riding_rect = Rect::new(
252 collider.pos.x,
253 collider.pos.y - 1.0,
254 collider.width as f32,
255 1.0,
256 );
257 let pushing_rect = Rect::new(
258 collider.pos.x + move_x as f32,
259 collider.pos.y,
260 collider.width as f32,
261 collider.height as f32,
262 );
263
264 for (actor, actor_collider) in &mut self.actors {
265 let rider_rect = Rect::new(
266 actor_collider.pos.x,
267 actor_collider.pos.y + actor_collider.height as f32 - 1.0,
268 actor_collider.width as f32,
269 1.0,
270 );
271
272 if riding_rect.overlaps(&rider_rect) {
273 riding_actors.push(*actor);
274 } else if pushing_rect.overlaps(&actor_collider.rect())
275 && actor_collider.squished == false
276 {
277 pushing_actors.push(*actor);
278 }
279
280 if pushing_rect.overlaps(&actor_collider.rect()) == false {
281 actor_collider.squishers.remove(&solid);
282 if actor_collider.squishers.len() == 0 {
283 actor_collider.squished = false;
284 }
285 }
286 }
287
288 self.solids[solid.0].1.collidable = false;
289 for actor in riding_actors {
290 self.move_h(actor, move_x as f32);
291 }
292 for actor in pushing_actors {
293 let squished = !self.move_h(actor, move_x as f32);
294 if squished {
295 self.actors[actor.0].1.squished = true;
296 self.actors[actor.0].1.squishers.insert(solid);
297 }
298 }
299 self.solids[solid.0].1.collidable = true;
300
301 let collider = &mut self.solids[solid.0].1;
302 if move_x != 0 {
303 collider.x_remainder -= move_x as f32;
304 collider.pos.x += move_x as f32;
305 }
306 if move_y != 0 {
307 collider.y_remainder -= move_y as f32;
308 collider.pos.y += move_y as f32;
309 }
310 }
311
312 pub fn solid_at(&self, pos: Vec2) -> bool {
313 self.tag_at(pos, 1)
314 }
315
316 pub fn tag_at(&self, pos: Vec2, tag: u8) -> bool {
317 for StaticTiledLayer {
318 tile_width,
319 tile_height,
320 width,
321 static_colliders,
322 tag: layer_tag,
323 } in &self.static_tiled_layers
324 {
325 let y = (pos.y / tile_width) as i32;
326 let x = (pos.x / tile_height) as i32;
327 let ix = y * (*width as i32) + x;
328
329 if ix >= 0
330 && ix < static_colliders.len() as i32
331 && static_colliders[ix as usize] != Tile::Empty
332 {
333 return *layer_tag == tag;
334 }
335 }
336
337 self.solids
338 .iter()
339 .any(|solid| solid.1.collidable && solid.1.rect().contains(pos))
340 }
341
342 pub fn collide_solids(&self, pos: Vec2, width: i32, height: i32) -> Tile {
343 let tile = self.collide_tag(1, pos, width, height);
344 if tile != Tile::Empty {
345 return tile;
346 }
347
348 self.solids
349 .iter()
350 .find(|solid| {
351 solid.1.collidable
352 && solid.1.rect().overlaps(&Rect::new(
353 pos.x,
354 pos.y,
355 width as f32,
356 height as f32,
357 ))
358 })
359 .map_or(Tile::Empty, |_| Tile::Collider)
360 }
361
362 pub fn collide_tag(&self, tag: u8, pos: Vec2, width: i32, height: i32) -> Tile {
363 for StaticTiledLayer {
364 tile_width,
365 tile_height,
366 width: layer_width,
367 static_colliders,
368 tag: layer_tag,
369 } in &self.static_tiled_layers
370 {
371 let layer_height = static_colliders.len() / layer_width + 1;
372 let check = |pos: Vec2| {
373 let y = (pos.y / tile_width) as i32;
374 let x = (pos.x / tile_height) as i32;
375 let ix = y * (*layer_width as i32) + x;
376 if y >= 0
377 && y < layer_height as i32
378 && x >= 0
379 && x < *layer_width as i32
380 && ix >= 0
381 && ix < static_colliders.len() as i32
382 && *layer_tag == tag
383 && static_colliders[ix as usize] != Tile::Empty
384 {
385 return static_colliders[ix as usize];
386 }
387 return Tile::Empty;
388 };
389
390 let tile = check(pos)
391 .or(check(pos + vec2(width as f32 - 1.0, 0.0)))
392 .or(check(pos + vec2(width as f32 - 1.0, height as f32 - 1.0)))
393 .or(check(pos + vec2(0.0, height as f32 - 1.0)));
394
395 if tile != Tile::Empty {
396 return tile;
397 }
398
399 if width > *tile_width as i32 {
400 let mut x = pos.x;
401
402 while {
403 x += tile_width;
404 x < pos.x + width as f32 - 1.
405 } {
406 let tile =
407 check(vec2(x, pos.y)).or(check(vec2(x, pos.y + height as f32 - 1.0)));
408 if tile != Tile::Empty {
409 return tile;
410 }
411 }
412 }
413
414 if height > *tile_height as i32 {
415 let mut y = pos.y;
416
417 while {
418 y += tile_height;
419 y < pos.y + height as f32 - 1.
420 } {
421 let tile = check(vec2(pos.x, y)).or(check(vec2(pos.x + width as f32 - 1., y)));
422 if tile != Tile::Empty {
423 return tile;
424 }
425 }
426 }
427 }
428 return Tile::Empty;
429 }
430
431 pub fn squished(&self, actor: Actor) -> bool {
432 self.actors[actor.0].1.squished
433 }
434
435 pub fn actor_pos(&self, actor: Actor) -> Vec2 {
436 self.actors[actor.0].1.pos
437 }
438
439 pub fn solid_pos(&self, solid: Solid) -> Vec2 {
440 self.solids[solid.0].1.pos
441 }
442
443 pub fn collide_check(&self, collider: Actor, pos: Vec2) -> bool {
444 let collider = &self.actors[collider.0];
445
446 let tile = self.collide_solids(pos, collider.1.width, collider.1.height);
447 if collider.1.descent {
448 tile == Tile::Solid || tile == Tile::Collider
449 } else {
450 tile == Tile::Solid || tile == Tile::Collider || tile == Tile::JumpThrough
451 }
452 }
453}