1use enum_iterator::Sequence;
5use js_sys::{Array, JsString, Object};
6use num_traits::*;
7use serde::{Deserialize, Serialize};
8use wasm_bindgen::prelude::*;
9
10use crate::{
11 constants::{Direction, ExitDirection},
12 enums::action_error_codes::game::map::*,
13 local::RoomName,
14 objects::RoomTerrain,
15 prelude::*,
16};
17
18#[wasm_bindgen]
19extern "C" {
20 #[wasm_bindgen(js_name = "map")]
21 type Map;
22
23 #[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = describeExits)]
24 fn describe_exits(room_name: &JsString) -> Object;
25
26 #[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = findExit)]
27 fn find_exit(from_room: &JsString, to_room: &JsString, options: &JsValue) -> i8;
28
29 #[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = findRoute)]
30 fn find_route(from_room: &JsString, to_room: &JsString, options: &JsValue) -> JsValue;
31
32 #[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getRoomLinearDistance)]
33 fn get_room_linear_distance(room_1: &JsString, room_2: &JsString, continuous: bool) -> u32;
34
35 #[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getRoomTerrain, catch)]
36 fn get_room_terrain(room_name: &JsString) -> Result<RoomTerrain, JsValue>;
37
38 #[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getWorldSize)]
39 fn get_world_size() -> u32;
40
41 #[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getRoomStatus, catch)]
42 fn get_room_status(room_name: &JsString) -> Result<JsRoomStatusResult, JsValue>;
43}
44
45pub fn describe_exits(room_name: RoomName) -> JsHashMap<Direction, RoomName> {
51 let room_name = room_name.into();
52
53 Map::describe_exits(&room_name).into()
54}
55
56pub fn get_room_linear_distance(from_room: RoomName, to_room: RoomName, continuous: bool) -> u32 {
62 let from_room = from_room.into();
63 let to_room = to_room.into();
64
65 Map::get_room_linear_distance(&from_room, &to_room, continuous)
66}
67
68pub fn get_room_terrain(room_name: RoomName) -> Option<RoomTerrain> {
73 let name = room_name.into();
74
75 Map::get_room_terrain(&name).ok()
76}
77
78pub fn get_world_size() -> u32 {
82 Map::get_world_size()
83}
84
85#[wasm_bindgen]
86extern "C" {
87 #[wasm_bindgen]
88 pub type JsRoomStatusResult;
89
90 #[wasm_bindgen(method, getter = status)]
91 pub fn status(this: &JsRoomStatusResult) -> RoomStatus;
92
93 #[wasm_bindgen(method, getter = timestamp)]
94 pub fn timestamp(this: &JsRoomStatusResult) -> Option<f64>;
95}
96
97#[derive(Clone, Debug)]
98pub struct RoomStatusResult {
99 status: RoomStatus,
100 timestamp: Option<f64>,
101}
102
103impl RoomStatusResult {
104 pub fn status(&self) -> RoomStatus {
105 self.status
106 }
107
108 pub fn timestamp(&self) -> Option<f64> {
109 self.timestamp
110 }
111}
112
113impl From<JsRoomStatusResult> for RoomStatusResult {
114 fn from(val: JsRoomStatusResult) -> Self {
115 RoomStatusResult {
116 status: val.status(),
117 timestamp: val.timestamp(),
118 }
119 }
120}
121
122#[wasm_bindgen]
123#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Sequence, Deserialize, Serialize)]
124pub enum RoomStatus {
125 Normal = "normal",
126 Closed = "closed",
127 Novice = "novice",
128 Respawn = "respawn",
129}
130
131pub fn get_room_status(room_name: RoomName) -> Option<RoomStatusResult> {
136 let name = room_name.into();
137
138 Map::get_room_status(&name).ok().map(RoomStatusResult::from)
139}
140
141#[wasm_bindgen]
142extern "C" {
143 #[wasm_bindgen]
145 pub type JsFindRouteOptions;
146
147 #[wasm_bindgen(method, setter = routeCallback)]
151 pub fn route_callback(
152 this: &JsFindRouteOptions,
153 callback: &Closure<dyn FnMut(JsString, JsString) -> f64>,
154 );
155}
156
157impl JsFindRouteOptions {
158 pub fn new() -> JsFindRouteOptions {
159 Object::new().unchecked_into()
160 }
161}
162
163impl Default for JsFindRouteOptions {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169pub struct FindRouteOptions<F>
170where
171 F: FnMut(RoomName, RoomName) -> f64,
172{
173 route_callback: F,
174}
175
176impl<F> FindRouteOptions<F>
177where
178 F: FnMut(RoomName, RoomName) -> f64,
179{
180 pub(crate) fn into_js_options<R>(self, callback: impl Fn(&JsFindRouteOptions) -> R) -> R {
181 let mut raw_callback = self.route_callback;
182
183 let mut owned_callback = move |to_room: RoomName, from_room: RoomName| -> f64 {
184 raw_callback(to_room, from_room)
185 };
186
187 let callback_type_erased: &mut (dyn FnMut(RoomName, RoomName) -> f64) = &mut owned_callback;
193
194 let callback_lifetime_erased: &'static mut (dyn FnMut(RoomName, RoomName) -> f64) =
202 unsafe { std::mem::transmute(callback_type_erased) };
203
204 let boxed_callback = Box::new(move |to_room: JsString, from_room: JsString| -> f64 {
205 let to_room = to_room
206 .try_into()
207 .expect("expected 'to' room name in route callback");
208 let from_room = from_room
209 .try_into()
210 .expect("expected 'rom' room name in route callback");
211
212 callback_lifetime_erased(to_room, from_room)
213 }) as Box<dyn FnMut(JsString, JsString) -> f64>;
214
215 let closure = Closure::wrap(boxed_callback);
216
217 let js_options = JsFindRouteOptions::new();
222
223 js_options.route_callback(&closure);
224
225 callback(&js_options)
226 }
227}
228
229impl Default for FindRouteOptions<fn(RoomName, RoomName) -> f64> {
230 fn default() -> Self {
231 const fn room_cost(_to_room: RoomName, _from_room: RoomName) -> f64 {
232 1.0
233 }
234
235 FindRouteOptions {
236 route_callback: room_cost,
237 }
238 }
239}
240
241impl FindRouteOptions<fn(RoomName, RoomName) -> f64> {
242 #[inline]
243 pub fn new() -> Self {
244 Self::default()
245 }
246}
247
248impl<F> FindRouteOptions<F>
249where
250 F: FnMut(RoomName, RoomName) -> f64,
251{
252 pub fn room_callback<F2>(self, route_callback: F2) -> FindRouteOptions<F2>
253 where
254 F2: FnMut(RoomName, RoomName) -> f64,
255 {
256 let FindRouteOptions { route_callback: _ } = self;
257
258 FindRouteOptions { route_callback }
259 }
260}
261
262#[derive(Debug, Deserialize)]
263pub struct RouteStep {
264 pub exit: ExitDirection,
265 pub room: RoomName,
266}
267
268pub fn find_route<F>(
278 from: RoomName,
279 to: RoomName,
280 options: Option<FindRouteOptions<F>>,
281) -> Result<Vec<RouteStep>, FindRouteErrorCode>
282where
283 F: FnMut(RoomName, RoomName) -> f64,
284{
285 let from: JsString = from.into();
286 let to: JsString = to.into();
287
288 let result = if let Some(options) = options {
289 options.into_js_options(|js_options| Map::find_route(&from, &to, js_options))
290 } else {
291 Map::find_route(&from, &to, &JsValue::UNDEFINED)
292 };
293
294 if result.is_object() {
295 let result: &Array = result.unchecked_ref();
296
297 let steps: Vec<RouteStep> = result
298 .iter()
299 .map(|step| serde_wasm_bindgen::from_value(step).expect("expected route step"))
300 .collect();
301
302 Ok(steps)
303 } else {
304 Err(unsafe {
306 FindRouteErrorCode::try_result_from_jsvalue(&result)
307 .expect("expected return code for pathing failure")
308 .unwrap_err_unchecked()
309 })
310 }
311}
312
313pub fn find_exit<F>(
319 from: RoomName,
320 to: RoomName,
321 options: Option<FindRouteOptions<F>>,
322) -> Result<ExitDirection, FindExitErrorCode>
323where
324 F: FnMut(RoomName, RoomName) -> f64,
325{
326 let from: JsString = from.into();
327 let to: JsString = to.into();
328
329 let result = if let Some(options) = options {
330 options.into_js_options(|js_options| Map::find_exit(&from, &to, js_options))
331 } else {
332 Map::find_exit(&from, &to, &JsValue::UNDEFINED)
333 };
334
335 if result >= 0 {
336 Ok(ExitDirection::from_i8(result).expect("expected exit direction for pathing"))
337 } else {
338 Err(unsafe { FindExitErrorCode::result_from_i8(result).unwrap_err_unchecked() })
341 }
342}