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