screeps/objects/impls/
room_position.rs

1use js_sys::{Array, JsString, Object};
2use num_traits::*;
3use wasm_bindgen::prelude::*;
4
5use crate::{
6    constants::{find::*, look::*, Color, Direction, ErrorCode, StructureType},
7    enums::action_error_codes::room_position::*,
8    local::{Position, RoomCoordinate, RoomName},
9    objects::{CostMatrix, FindPathOptions, Path},
10    pathfinder::RoomCostResult,
11    prelude::*,
12    prototypes::ROOM_POSITION_PROTOTYPE,
13};
14
15#[wasm_bindgen]
16extern "C" {
17    /// An object representing a position in a room, stored in JavaScript
18    /// memory.
19    ///
20    /// Use [`Position`] to store and access the same data in Rust memory.
21    ///
22    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition)
23    pub type RoomPosition;
24
25    #[wasm_bindgen(constructor)]
26    fn new_internal(x: u8, y: u8, room_name: &JsString) -> RoomPosition;
27
28    #[wasm_bindgen(method, getter = roomName)]
29    fn room_name_internal(this: &RoomPosition) -> JsString;
30
31    /// Change the room the position refers to; must be a valid room name.
32    ///
33    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.roomName)
34    #[wasm_bindgen(method, setter = roomName)]
35    pub fn set_room_name(this: &RoomPosition) -> JsString;
36
37    /// X coordinate of the position within the room.
38    ///
39    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.x)
40    #[wasm_bindgen(method, getter)]
41    pub fn x(this: &RoomPosition) -> u8;
42
43    /// Set a new X coordinate; must be in the range 0..49.
44    ///
45    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.x)
46    #[wasm_bindgen(method, setter)]
47    pub fn set_x(this: &RoomPosition) -> u8;
48
49    /// Y coordinate of the position within the room.
50    ///
51    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.y)
52    #[wasm_bindgen(method, getter)]
53    pub fn y(this: &RoomPosition) -> u8;
54
55    /// Set a new Y coordinate; must be in the range 0..49.
56    ///
57    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.y)
58    #[wasm_bindgen(method, setter)]
59    pub fn set_y(this: &RoomPosition) -> u8;
60
61    // todo, get this as native
62    /// Gets the efficient internal i32 representation of the position.
63    #[wasm_bindgen(method, getter = __packedPos)]
64    pub fn packed(this: &RoomPosition) -> u32;
65
66    // todo, as native
67    /// Sets the position to a new one by passing an i32 that represents a
68    /// packed position.
69    #[wasm_bindgen(method, setter = __packedPos)]
70    pub fn set_packed(this: &RoomPosition, val: u32);
71
72    #[wasm_bindgen(method, catch, js_name = createConstructionSite)]
73    fn create_construction_site_internal(
74        this: &RoomPosition,
75        ty: StructureType,
76        name: Option<&JsString>,
77    ) -> Result<i8, JsValue>;
78
79    #[wasm_bindgen(method, catch, js_name = createFlag)]
80    fn create_flag_internal(
81        this: &RoomPosition,
82        name: Option<&JsString>,
83        color: Option<Color>,
84        secondary_color: Option<Color>,
85    ) -> Result<JsValue, JsValue>;
86
87    // todo FindOptions
88    #[wasm_bindgen(method, js_name = findClosestByPath)]
89    fn find_closest_by_path_internal(
90        this: &RoomPosition,
91        goal: Find,
92        options: Option<&Object>,
93    ) -> Option<Object>;
94
95    // todo FindOptions
96    #[wasm_bindgen(method, js_name = findClosestByRange)]
97    fn find_closest_by_range_internal(
98        this: &RoomPosition,
99        goal: Find,
100        options: Option<&Object>,
101    ) -> Option<Object>;
102
103    // todo FindOptions
104    #[wasm_bindgen(method, js_name = findInRange)]
105    fn find_in_range_internal(
106        this: &RoomPosition,
107        goal: Find,
108        range: u8,
109        options: Option<&Object>,
110    ) -> Option<Array>;
111
112    #[wasm_bindgen(method, js_name = findPathTo)]
113    fn find_path_to_internal(
114        this: &RoomPosition,
115        target: &JsValue,
116        options: Option<&Object>,
117    ) -> JsValue;
118
119    #[wasm_bindgen(method, js_name = findPathTo)]
120    fn find_path_to_xy_internal(
121        this: &RoomPosition,
122        x: u8,
123        y: u8,
124        options: Option<&Object>,
125    ) -> JsValue;
126
127    /// Get the [`Direction`] toward a position or room object.
128    ///
129    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.getDirectionTo)
130    #[wasm_bindgen(method, js_name = getDirectionTo)]
131    pub fn get_direction_to(this: &RoomPosition, goal: &JsValue) -> Direction;
132
133    /// Get the [`Direction`] toward the given coordinates in the same room.
134    ///
135    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.getDirectionTo)
136    #[wasm_bindgen(method, js_name = getDirectionTo)]
137    pub fn get_direction_to_xy(this: &RoomPosition, x: u8, y: u8) -> Direction;
138
139    /// Get the range to a position or room object in the same room.
140    ///
141    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.getRangeTo)
142    #[wasm_bindgen(method, js_name = getRangeTo)]
143    pub fn get_range_to(this: &RoomPosition, goal: &JsValue) -> u32;
144
145    /// Get the range to the given coordinates in the same room.
146    ///
147    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.getRangeTo)
148    #[wasm_bindgen(method, js_name = getRangeTo)]
149    pub fn get_range_to_xy(this: &RoomPosition, x: u8, y: u8) -> u32;
150
151    /// Get the range to a position or room object in the same room.
152    ///
153    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.inRangeTo)
154    #[wasm_bindgen(method, js_name = inRangeTo)]
155    pub fn in_range_to(this: &RoomPosition, goal: &JsValue, range: u8) -> bool;
156
157    /// Get the range to the given coordinates in the same room.
158    ///
159    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.inRangeTo)
160    #[wasm_bindgen(method, js_name = inRangeTo)]
161    pub fn in_range_to_xy(this: &RoomPosition, x: u8, y: u8, range: u8) -> bool;
162
163    /// Determine whether this position is at the same position as another
164    /// position or room object.
165    ///
166    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.isEqualTo)
167    #[wasm_bindgen(method, js_name = isEqualTo)]
168    pub fn is_equal_to(this: &RoomPosition, goal: &JsValue) -> bool;
169
170    /// Determine whether this position is at the given coordinates in the room.
171    ///
172    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.isEqualTo)
173    #[wasm_bindgen(method, js_name = isEqualTo)]
174    pub fn is_equal_to_xy(this: &RoomPosition, x: u8, y: u8) -> bool;
175
176    /// Determine whether this position is within 1 range of another position or
177    /// room object.
178    ///
179    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.isNearTo)
180    #[wasm_bindgen(method, js_name = isNearTo)]
181    pub fn is_near_to(this: &RoomPosition, goal: &JsValue) -> bool;
182
183    /// Determine whether this position is within 1 range of the given
184    /// coordinates in the room.
185    ///
186    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.isNearTo)
187    #[wasm_bindgen(method, js_name = isNearTo)]
188    pub fn is_near_to_xy(this: &RoomPosition, x: u8, y: u8) -> bool;
189
190    #[wasm_bindgen(method, catch, js_name = look)]
191    fn look_internal(this: &RoomPosition) -> Result<Array, JsValue>;
192
193    #[wasm_bindgen(method, catch, js_name = lookFor)]
194    fn look_for_internal(this: &RoomPosition, ty: Look) -> Result<Option<Array>, JsValue>;
195}
196
197impl RoomPosition {
198    /// Create a new RoomPosition using the normal constructor, taking
199    /// coordinates and the room name.
200    ///
201    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.constructor)
202    pub fn new(x: u8, y: u8, room_name: RoomName) -> RoomPosition {
203        let room_name = room_name.into();
204
205        Self::new_internal(x, y, &room_name)
206    }
207
208    /// Name of the room the position is in, as an owned [`JsString`] reference
209    /// to a string in Javascript memory.
210    ///
211    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.roomName)
212    pub fn room_name(&self) -> RoomName {
213        Self::room_name_internal(self)
214            .try_into()
215            .expect("expected parseable room name")
216    }
217
218    /// Creates a [`ConstructionSite`] at this position. If it's a
219    /// [`StructureSpawn`], a name can optionally be assigned for the structure.
220    ///
221    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.createConstructionSite)
222    ///
223    /// [`ConstructionSite`]: crate::objects::ConstructionSite
224    /// [`StructureSpawn`]: crate::objects::StructureSpawn
225    pub fn create_construction_site(
226        &self,
227        ty: StructureType,
228        name: Option<&JsString>,
229    ) -> Result<(), RoomPositionCreateConstructionSiteErrorCode> {
230        match Self::create_construction_site_internal(self, ty, name) {
231            Ok(result) => RoomPositionCreateConstructionSiteErrorCode::result_from_i8(result),
232            Err(_) => {
233                // js code threw an exception; this happens when the room is not visible
234                Err(RoomPositionCreateConstructionSiteErrorCode::NotInRange)
235            }
236        }
237    }
238
239    /// Creates a [`Flag`] at this position. If successful, returns the name of
240    /// the created flag.
241    ///
242    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.createFlag)
243    ///
244    /// [`Flag`]: crate::objects::Flag
245    pub fn create_flag(
246        &self,
247        name: Option<&JsString>,
248        color: Option<Color>,
249        secondary_color: Option<Color>,
250    ) -> Result<JsString, RoomPositionCreateFlagErrorCode> {
251        match self.create_flag_internal(name, color, secondary_color) {
252            Ok(result) => {
253                if result.is_string() {
254                    Ok(result.unchecked_into())
255                } else {
256                    Err(RoomPositionCreateFlagErrorCode::from_f64(
257                        result
258                            .as_f64()
259                            .expect("expected non-string flag return to be a number"),
260                    )
261                    .expect("expected valid error code"))
262                }
263            }
264            Err(_) => {
265                // js code threw an exception; this only happens for a non-visible room.
266                Err(RoomPositionCreateFlagErrorCode::NotInRange)
267            }
268        }
269    }
270
271    // todo typed options and version that allows passing target roomobjects
272    /// Find the closest object by path among a list of objects, or use
273    /// a [`find` constant] to search for all objects of that type in the room.
274    ///
275    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.findClosestByPath)
276    ///
277    /// [`find` constant]: crate::constants::find
278    pub fn find_closest_by_path<T>(&self, find: T, options: Option<&Object>) -> Option<T::Item>
279    where
280        T: FindConstant,
281    {
282        self.find_closest_by_path_internal(find.find_code(), options)
283            .map(|reference| T::convert_and_check_item(reference.into()))
284    }
285
286    // todo version for passing target roomobjects
287    /// Find the closest object by range among a list of objects, or use
288    /// a [`find` constant] to search for all objects of that type in the room.
289    /// Will not work for objects in other rooms.
290    ///
291    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.findClosestByRange)
292    ///
293    /// [`find` constant]: crate::constants::find
294    pub fn find_closest_by_range<T>(&self, find: T) -> Option<T::Item>
295    where
296        T: FindConstant,
297    {
298        self.find_closest_by_range_internal(find.find_code(), None)
299            .map(|reference| T::convert_and_check_item(reference.into()))
300    }
301
302    // todo version for passing target roomobjects
303    /// Find all relevant objects within a certain range among a list of
304    /// objects, or use a [`find` constant] to search all objects of that type
305    /// in the room.
306    ///
307    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.findInRange)
308    ///
309    /// [`find` constant]: crate::constants::find
310    pub fn find_in_range<T>(&self, find: T, range: u8) -> Vec<T::Item>
311    where
312        T: FindConstant,
313    {
314        self.find_in_range_internal(find.find_code(), range, None)
315            .map(|arr| arr.iter().map(T::convert_and_check_item).collect())
316            .unwrap_or_default()
317    }
318
319    /// Find a path from this position to a position or room object, with an
320    /// optional options object
321    ///
322    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.findPathTo)
323    pub fn find_path_to<T, F, R>(&self, target: &T, options: Option<FindPathOptions<F, R>>) -> Path
324    where
325        T: HasPosition,
326        F: FnMut(RoomName, CostMatrix) -> R,
327        R: RoomCostResult,
328    {
329        let target: RoomPosition = target.pos().into();
330
331        if let Some(options) = options {
332            options.into_js_options(|js_options| {
333                serde_wasm_bindgen::from_value(
334                    self.find_path_to_internal(&target, Some(js_options.unchecked_ref())),
335                )
336                .expect("invalid path from RoomPosition.findPathTo")
337            })
338        } else {
339            serde_wasm_bindgen::from_value(self.find_path_to_internal(&target, None))
340                .expect("invalid path from RoomPosition.findPathTo")
341        }
342    }
343
344    /// Find a path from this position to the given coordinates in the same
345    /// room, with an optional options object.
346    ///
347    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.findPathTo)
348    pub fn find_path_to_xy<F, R>(
349        &self,
350        x: RoomCoordinate,
351        y: RoomCoordinate,
352        options: Option<FindPathOptions<F, R>>,
353    ) -> Path
354    where
355        F: FnMut(RoomName, CostMatrix) -> R,
356        R: RoomCostResult,
357    {
358        if let Some(options) = options {
359            options.into_js_options(|js_options| {
360                serde_wasm_bindgen::from_value(self.find_path_to_xy_internal(
361                    x.into(),
362                    y.into(),
363                    Some(js_options.unchecked_ref()),
364                ))
365                .expect("invalid path from RoomPosition.findPathTo")
366            })
367        } else {
368            serde_wasm_bindgen::from_value(self.find_path_to_xy_internal(x.into(), y.into(), None))
369                .expect("invalid path from RoomPosition.findPathTo")
370        }
371    }
372
373    /// Get all objects at this position. Will fail if the position is in a room
374    /// that's not visible during the current tick.
375    ///
376    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.look)
377    pub fn look(&self) -> Result<Vec<LookResult>, ErrorCode> {
378        match self.look_internal() {
379            Ok(array) => Ok(array
380                .iter()
381                .map(LookResult::from_jsvalue_unknown_type)
382                .collect()),
383            Err(_) => Err(ErrorCode::NotInRange),
384        }
385    }
386
387    /// Get all objects of a given type at this position, if any. Will fail if
388    /// the position is in a room that's not visible during the current tick.
389    ///
390    /// [Screeps documentation](https://docs.screeps.com/api/#RoomPosition.lookFor)
391    pub fn look_for<T>(&self, _ty: T) -> Result<Vec<T::Item>, ErrorCode>
392    where
393        T: LookConstant,
394    {
395        match self.look_for_internal(T::look_code()) {
396            Ok(array) => Ok(array
397                .map(|arr| arr.iter().map(T::convert_and_check_item).collect())
398                .unwrap_or_else(Vec::new)),
399            Err(_) => Err(ErrorCode::NotInRange),
400        }
401    }
402}
403
404impl Clone for RoomPosition {
405    fn clone(&self) -> Self {
406        let new_pos = RoomPosition::from(JsValue::from(Object::create(&ROOM_POSITION_PROTOTYPE)));
407        new_pos.set_packed(self.packed());
408        new_pos
409    }
410}
411
412impl HasPosition for RoomPosition {
413    fn pos(&self) -> Position {
414        self.into()
415    }
416}
417
418impl From<Position> for RoomPosition {
419    fn from(pos: Position) -> Self {
420        let js_pos = RoomPosition::from(JsValue::from(Object::create(&ROOM_POSITION_PROTOTYPE)));
421        js_pos.set_packed(pos.packed_repr());
422        js_pos
423    }
424}
425
426impl From<&Position> for RoomPosition {
427    fn from(pos: &Position) -> Self {
428        let js_pos = RoomPosition::from(JsValue::from(Object::create(&ROOM_POSITION_PROTOTYPE)));
429        js_pos.set_packed(pos.packed_repr());
430        js_pos
431    }
432}