1use indexmap::IndexMap;
2use rapier2d::prelude::*;
3use serde::{Deserialize, Serialize};
4
5use crate::config::FieldValue;
6use crate::physics::{deg_to_rad, encode_map_object, round2};
7
8const DEFAULT_FRICTION: f32 = 0.2;
11const DEFAULT_RESTITUTION: f32 = 0.0;
12
13#[derive(Clone, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct DynamicObjectConfig {
17 pub position: [f32; 2],
18 pub angle: f32,
19 pub width: f32,
20 pub height: f32,
21 pub density: f32,
22 #[serde(default)]
23 pub linear_damping: Option<f32>,
24 #[serde(default)]
25 pub angular_damping: Option<f32>,
26}
27
28#[derive(Clone, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct MapConfig {
33 #[serde(default)]
34 pub set_id: Option<String>,
35 #[serde(default)]
36 pub scale: Option<f32>,
37 pub step: f32,
38 pub map: Vec<Vec<i32>>,
39 #[serde(default)]
40 pub physics_static: Vec<i32>,
41 #[serde(default)]
42 pub physics_dynamic: Vec<DynamicObjectConfig>,
43 #[serde(default)]
44 pub respawns: IndexMap<String, Vec<[f32; 3]>>,
45}
46
47#[derive(Serialize, Deserialize)]
50pub struct GameMap {
51 pub set_id: String,
52 pub step: f32,
54 pub grid: Vec<Vec<i32>>,
56 pub physics_static: Vec<i32>,
57 pub respawns: IndexMap<String, Vec<[f32; 3]>>,
59 static_bodies: Vec<RigidBodyHandle>,
60 dynamic_bodies: Vec<RigidBodyHandle>,
61}
62
63impl GameMap {
64 pub fn create(
67 world: &mut PhysicsWorld,
68 cfg: &MapConfig,
69 default_scale: f32,
70 default_set_id: &str,
71 ) -> Self {
72 let scale = cfg.scale.unwrap_or(default_scale);
73 let step = cfg.step * scale;
74
75 let mut map = Self {
76 set_id: cfg
77 .set_id
78 .clone()
79 .unwrap_or_else(|| default_set_id.to_string()),
80 step,
81 grid: cfg.map.clone(),
82 physics_static: cfg.physics_static.clone(),
83 respawns: cfg
84 .respawns
85 .iter()
86 .map(|(team, arr)| {
87 (
88 team.clone(),
89 arr.iter()
90 .map(|[x, y, angle]| [x * scale, y * scale, *angle])
91 .collect(),
92 )
93 })
94 .collect(),
95 static_bodies: Vec::new(),
96 dynamic_bodies: Vec::new(),
97 };
98
99 map.create_static(world);
100 map.create_dynamic(world, &cfg.physics_dynamic, scale);
101
102 map
103 }
104
105 fn search_static_block(&self, work: &mut [Vec<Option<i32>>], y0: usize, x0: usize) -> (f32, f32) {
108 let mut x = x0;
109 let mut w_counter = 0;
110 let mut h_counter = 1;
111
112 while x < work[y0].len()
114 && work[y0][x].is_some_and(|tile| self.physics_static.contains(&tile))
115 {
116 work[y0][x] = None;
117 x += 1;
118 w_counter += 1;
119 }
120
121 let len_x = x;
122 let len_y = work.len();
123
124 for y in (y0 + 1)..len_y {
126 let mut empty_tile = false;
127 let mut x = x0;
128
129 while x < len_x {
130 if x < work[y].len()
131 && work[y][x].is_some_and(|tile| self.physics_static.contains(&tile))
132 {
133 x += 1;
134 } else {
135 empty_tile = true;
136 break;
137 }
138 }
139
140 if empty_tile {
141 break;
142 }
143
144 h_counter += 1;
145
146 for cell in work[y][x0..len_x].iter_mut() {
147 *cell = None;
148 }
149 }
150
151 (w_counter as f32 * self.step, h_counter as f32 * self.step)
152 }
153
154 fn create_static(&mut self, world: &mut PhysicsWorld) {
156 let mut work: Vec<Vec<Option<i32>>> = self
157 .grid
158 .iter()
159 .map(|row| row.iter().map(|&tile| Some(tile)).collect())
160 .collect();
161
162 for y in 0..work.len() {
163 for x in 0..work[y].len() {
164 let is_static = work[y][x].is_some_and(|tile| self.physics_static.contains(&tile));
165
166 if is_static {
167 let (width, height) = self.search_static_block(&mut work, y, x);
168 let pos_x = x as f32 * self.step + width / 2.0;
169 let pos_y = y as f32 * self.step + height / 2.0;
170
171 let body = world
172 .insert_body(RigidBodyBuilder::fixed().translation(Vector::new(pos_x, pos_y)));
173
174 world.insert_collider(
175 ColliderBuilder::cuboid(width / 2.0, height / 2.0)
176 .friction(DEFAULT_FRICTION)
177 .restitution(DEFAULT_RESTITUTION),
178 Some(body),
179 );
180
181 self.static_bodies.push(body);
182 }
183 }
184 }
185 }
186
187 fn create_dynamic(
189 &mut self,
190 world: &mut PhysicsWorld,
191 dynamics: &[DynamicObjectConfig],
192 scale: f32,
193 ) {
194 for data in dynamics {
195 let pos_x = data.position[0] * scale;
196 let pos_y = data.position[1] * scale;
197 let width = data.width * scale;
198 let height = data.height * scale;
199
200 let body = world.insert_body(
201 RigidBodyBuilder::dynamic()
202 .translation(Vector::new(pos_x, pos_y))
203 .rotation(deg_to_rad(data.angle))
204 .linear_damping(data.linear_damping.unwrap_or(0.0))
205 .angular_damping(data.angular_damping.unwrap_or(0.01))
206 .user_data(encode_map_object()),
207 );
208
209 world.insert_collider(
211 ColliderBuilder::cuboid(width / 2.0, height / 2.0)
212 .translation(Vector::new(width / 2.0, height / 2.0))
213 .density(data.density)
214 .friction(DEFAULT_FRICTION)
215 .restitution(DEFAULT_RESTITUTION),
216 Some(body),
217 );
218
219 self.dynamic_bodies.push(body);
220 }
221 }
222
223 pub fn destroy(&mut self, world: &mut PhysicsWorld) {
225 for handle in self.static_bodies.drain(..) {
226 world.remove_body(handle);
227 }
228
229 for handle in self.dynamic_bodies.drain(..) {
230 world.remove_body(handle);
231 }
232 }
233
234 pub fn dynamic_map_data(&self, world: &PhysicsWorld) -> Vec<(u8, Vec<FieldValue>)> {
238 self.dynamic_bodies
239 .iter()
240 .enumerate()
241 .filter_map(|(index, &handle)| {
242 world.bodies.get(handle).map(|body| {
243 let pos = body.translation();
244
245 (
246 index as u8,
247 vec![
248 FieldValue::F32(round2(pos.x)),
249 FieldValue::F32(round2(pos.y)),
250 FieldValue::F32(round2(body.rotation().angle())),
251 ],
252 )
253 })
254 })
255 .collect()
256 }
257}