screeps/objects/impls/
room.rs

1use std::fmt;
2
3use js_sys::{Array, JsString, Object};
4use num_traits::*;
5use serde::{
6    de::{self, MapAccess, Visitor},
7    Deserialize, Deserializer, Serialize,
8};
9use serde_repr::{Deserialize_repr, Serialize_repr};
10use wasm_bindgen::prelude::*;
11
12use crate::{
13    constants::{
14        find::*, look::*, Color, Direction, ExitDirection, PowerType, ResourceType, StructureType,
15    },
16    enums::action_error_codes::room::*,
17    local::{LodashFilter, RoomName},
18    objects::*,
19    pathfinder::RoomCostResult,
20    prelude::*,
21};
22
23#[wasm_bindgen]
24extern "C" {
25    /// A reference to a [`Room`] object, a 50x50 chunk of the Screeps game
26    /// world.
27    ///
28    /// [Screeps documentation](https://docs.screeps.com/api/#Room)
29    #[derive(Clone, Debug)]
30    pub type Room;
31
32    /// The [`StructureController`] for the room, or `None` in rooms that cannot
33    /// be claimed.
34    ///
35    /// [Screeps documentation](https://docs.screeps.com/api/#Room.controller)
36    #[wasm_bindgen(method, getter)]
37    pub fn controller(this: &Room) -> Option<StructureController>;
38
39    /// Energy available for spawning at the start of the current tick.
40    ///
41    /// [Screeps documentation](https://docs.screeps.com/api/#Room.energyAvailable)
42    #[wasm_bindgen(method, getter = energyAvailable)]
43    pub fn energy_available(this: &Room) -> u32;
44
45    /// Total energy capacity of all spawns and extensions in the room.
46    ///
47    /// [Screeps documentation](https://docs.screeps.com/api/#Room.energyCapacityAvailable)
48    #[wasm_bindgen(method, getter = energyCapacityAvailable)]
49    pub fn energy_capacity_available(this: &Room) -> u32;
50
51    /// A shortcut to `Memory.rooms[room.name]`.
52    ///
53    /// [Screeps documentation](https://docs.screeps.com/api/#Room.memory)
54    #[wasm_bindgen(method, getter)]
55    pub fn memory(this: &Room) -> JsValue;
56
57    /// Sets a new value to `Memory.rooms[room.name]`.
58    ///
59    /// [Screeps documentation](https://docs.screeps.com/api/#Room.memory)
60    #[wasm_bindgen(method, setter)]
61    pub fn set_memory(this: &Room, val: &JsValue);
62
63    #[wasm_bindgen(method, getter = name)]
64    fn name_internal(this: &Room) -> JsString;
65
66    /// The [`StructureStorage`] built in the room, or `None` in rooms where
67    /// there isn't one.
68    ///
69    /// [Screeps documentation](https://docs.screeps.com/api/#Room.storage)
70    #[wasm_bindgen(method, getter)]
71    pub fn storage(this: &Room) -> Option<StructureStorage>;
72
73    /// The [`StructureTerminal`] built in the room, or `None` in rooms where
74    /// there isn't one.
75    ///
76    /// [Screeps documentation](https://docs.screeps.com/api/#Room.terminal)
77    #[wasm_bindgen(method, getter)]
78    pub fn terminal(this: &Room) -> Option<StructureTerminal>;
79
80    #[wasm_bindgen(static_method_of = Room, js_name = serializePath)]
81    fn serialize_path_internal(path: &Array) -> JsString;
82
83    #[wasm_bindgen(static_method_of = Room, js_name = deserializePath)]
84    fn deserialize_path_internal(path: &JsString) -> Array;
85
86    #[wasm_bindgen(final, method, js_name = createConstructionSite)]
87    fn create_construction_site_internal(
88        this: &Room,
89        x: u8,
90        y: u8,
91        ty: StructureType,
92        name: Option<&JsString>,
93    ) -> i8;
94
95    #[wasm_bindgen(final, method, js_name = createFlag)]
96    fn create_flag_internal(
97        this: &Room,
98        x: u8,
99        y: u8,
100        name: Option<&JsString>,
101        color: Option<Color>,
102        secondary_color: Option<Color>,
103    ) -> JsValue;
104
105    #[wasm_bindgen(method, js_name = find)]
106    fn find_internal(this: &Room, ty: Find, options: Option<&FindOptions>) -> Array;
107
108    #[wasm_bindgen(final, method, js_name = findExitTo)]
109    fn find_exit_to_internal(this: &Room, room: &JsString) -> i8;
110
111    #[wasm_bindgen(final, method, js_name = findPath)]
112    fn find_path_internal(
113        this: &Room,
114        origin: &RoomPosition,
115        goal: &RoomPosition,
116        options: Option<&Object>,
117    ) -> JsValue;
118
119    #[wasm_bindgen(final, method, js_name = getEventLog)]
120    fn get_event_log_internal(this: &Room, raw: bool) -> JsValue;
121
122    /// Gets the [`RoomPosition`] for the given coordinates.
123    ///
124    /// [Screeps documentation](https://docs.screeps.com/api/#Room.getPositionAt)
125    #[wasm_bindgen(final, method, js_name = getPositionAt)]
126    pub fn get_position_at(this: &Room, x: u8, y: u8) -> RoomPosition;
127
128    /// Gets the [`RoomTerrain`] object for this room.
129    ///
130    /// [Screeps documentation](https://docs.screeps.com/api/#Room.getTerrain)
131    #[wasm_bindgen(final, method, js_name = getTerrain)]
132    pub fn get_terrain(this: &Room) -> RoomTerrain;
133
134    #[wasm_bindgen(final, method, js_name = lookAt)]
135    fn look_at_internal(this: &Room, target: &RoomPosition) -> Array;
136
137    #[wasm_bindgen(final, method, js_name = lookAt)]
138    fn look_at_xy_internal(this: &Room, x: u8, y: u8) -> Array;
139
140    // only calling this in array mode currently due to more complex return type
141    // without
142    #[wasm_bindgen(final, method, js_name = lookAtArea)]
143    fn look_at_area_internal(
144        this: &Room,
145        top_y: u8,
146        left_x: u8,
147        bottom_y: u8,
148        right_x: u8,
149        as_array: bool,
150    ) -> Option<Array>;
151
152    #[wasm_bindgen(final, method, js_name = lookForAt)]
153    fn look_for_at_internal(this: &Room, ty: Look, target: &RoomPosition) -> Option<Array>;
154
155    #[wasm_bindgen(final, method, js_name = lookForAt)]
156    fn look_for_at_xy_internal(this: &Room, ty: Look, x: u8, y: u8) -> Option<Array>;
157
158    // only calling this in array mode currently due to more complex return type
159    // without
160    #[wasm_bindgen(final, method, js_name = lookForAtArea)]
161    fn look_for_at_area_internal(
162        this: &Room,
163        ty: Look,
164        top_y: u8,
165        left_x: u8,
166        bottom_y: u8,
167        right_x: u8,
168        as_array: bool,
169    ) -> Option<Array>;
170}
171
172impl Room {
173    /// The room's name.
174    ///
175    /// [Screeps documentation](https://docs.screeps.com/api/#Room.name)
176    pub fn name(&self) -> RoomName {
177        self.name_internal()
178            .try_into()
179            .expect("expected parseable room name")
180    }
181
182    /// Serialize a path array from [`Room::find_path`] into a string
183    /// representation safe to store in memory.
184    ///
185    /// [Screeps documentation](https://docs.screeps.com/api/#Room.serializePath)
186    pub fn serialize_path(path: &[Step]) -> String {
187        Self::serialize_path_internal(
188            serde_wasm_bindgen::to_value(path)
189                .expect("invalid path passed to serialize")
190                .unchecked_ref(),
191        )
192        .into()
193    }
194
195    /// Deserialize a string representation from [`Room::serialize_path`] back
196    /// to a path array.
197    ///
198    /// [Screeps documentation](https://docs.screeps.com/api/#Room.deserializePath)
199    pub fn deserialize_path(path: &str) -> Vec<Step> {
200        serde_wasm_bindgen::from_value(
201            Self::deserialize_path_internal(&JsString::from(path)).unchecked_into(),
202        )
203        .expect("invalid deserialized path")
204    }
205
206    pub fn visual(&self) -> RoomVisual {
207        RoomVisual::new(Some(self.name()))
208    }
209
210    /// Creates a construction site at given coordinates within this room. If
211    /// it's a [`StructureSpawn`], a name can optionally be assigned for the
212    /// structure.
213    ///
214    /// See [`RoomPosition::create_construction_site`] to create at a specified
215    /// position.
216    ///
217    /// [Screeps documentation](https://docs.screeps.com/api/#Room.createConstructionSite)
218    ///
219    /// [`StructureSpawn`]: crate::objects::StructureSpawn
220    /// [`RoomPosition::create_construction_site`]:
221    /// crate::objects::RoomPosition::create_construction_site
222    pub fn create_construction_site(
223        &self,
224        x: u8,
225        y: u8,
226        ty: StructureType,
227        name: Option<&JsString>,
228    ) -> Result<(), RoomCreateConstructionSiteErrorCode> {
229        RoomCreateConstructionSiteErrorCode::result_from_i8(
230            self.create_construction_site_internal(x, y, ty, name),
231        )
232    }
233
234    /// Creates a [`Flag`] at given coordinates within this room. The name of
235    /// the flag is returned if the creation is successful.
236    ///
237    /// [Screeps documentation](https://docs.screeps.com/api/#Room.createFlag)
238    pub fn create_flag(
239        &self,
240        x: u8,
241        y: u8,
242        name: Option<&JsString>,
243        color: Option<Color>,
244        secondary_color: Option<Color>,
245    ) -> Result<JsString, RoomCreateFlagErrorCode> {
246        let result = self.create_flag_internal(x, y, name, color, secondary_color);
247        if result.is_string() {
248            Ok(result.unchecked_into())
249        } else {
250            Err(RoomCreateFlagErrorCode::from_f64(
251                result
252                    .as_f64()
253                    .expect("expected non-string flag return to be a number"),
254            )
255            .expect("expected valid error code"))
256        }
257    }
258
259    /// Find all objects of the specified type in the room, without passing
260    /// additional options.
261    ///
262    /// Returns an [`Vec`] containing the found objects, which should be
263    /// converted into the type of object you searched for.
264    ///
265    /// [Screeps documentation](https://docs.screeps.com/api/#Room.find)
266    pub fn find<T>(&self, ty: T, options: Option<&FindOptions>) -> Vec<T::Item>
267    where
268        T: FindConstant,
269    {
270        self.find_internal(ty.find_code(), options)
271            .iter()
272            .map(T::convert_and_check_item)
273            .collect()
274    }
275
276    /// Find an exit from the current room which leads to a target room.
277    ///
278    /// [Screeps documentation](https://docs.screeps.com/api/#Room.findExitTo)
279    pub fn find_exit_to(&self, room: RoomName) -> Result<ExitDirection, FindExitToErrorCode> {
280        let result = self.find_exit_to_internal(&room.into());
281        if result >= 0 {
282            Ok(ExitDirection::from_i8(result)
283                .expect("expect find exit return to be a valid exit constant"))
284        } else {
285            // The exit direction results should have been caught by the other branch,
286            // therefore this should always be an error and never actually panic
287            Err(FindExitToErrorCode::result_from_i8(result).unwrap_err())
288        }
289    }
290
291    /// Find a path within the room from one position to another.
292    ///
293    /// [Screeps documentation](https://docs.screeps.com/api/#Room.findPath)
294    pub fn find_path<F, R>(
295        &self,
296        origin: &RoomPosition,
297        goal: &RoomPosition,
298        options: Option<FindPathOptions<F, R>>,
299    ) -> Path
300    where
301        F: FnMut(RoomName, CostMatrix) -> R,
302        R: RoomCostResult,
303    {
304        if let Some(options) = options {
305            options.into_js_options(|js_options| {
306                serde_wasm_bindgen::from_value(self.find_path_internal(
307                    origin,
308                    goal,
309                    Some(js_options.unchecked_ref()),
310                ))
311                .expect("invalid path from Room.findPath")
312            })
313        } else {
314            serde_wasm_bindgen::from_value(self.find_path_internal(origin, goal, None))
315                .expect("invalid path from Room.findPath")
316        }
317    }
318
319    pub fn get_event_log(&self) -> Vec<Event> {
320        serde_json::from_str(&self.get_event_log_raw()).expect("Malformed Event Log")
321    }
322
323    pub fn get_event_log_raw(&self) -> String {
324        let js_log: JsString = Room::get_event_log_internal(self, true).into();
325        js_log.into()
326    }
327
328    /// Get all objects at a position.
329    ///
330    /// [Screeps documentation](https://docs.screeps.com/api/#Room.lookAt)
331    pub fn look_at(&self, target: &RoomPosition) -> Vec<LookResult> {
332        self.look_at_internal(target)
333            .iter()
334            .map(LookResult::from_jsvalue_unknown_type)
335            .collect()
336    }
337
338    /// Get all objects at the given room coordinates.
339    ///
340    /// [Screeps documentation](https://docs.screeps.com/api/#Room.lookAt)
341    pub fn look_at_xy(&self, x: u8, y: u8) -> Vec<LookResult> {
342        self.look_at_xy_internal(x, y)
343            .iter()
344            .map(LookResult::from_jsvalue_unknown_type)
345            .collect()
346    }
347
348    /// Get all objects in a certain area.
349    ///
350    /// [Screeps documentation](https://docs.screeps.com/api/#Room.lookAtArea)
351    pub fn look_at_area(
352        &self,
353        top_y: u8,
354        left_x: u8,
355        bottom_y: u8,
356        right_x: u8,
357    ) -> Vec<PositionedLookResult> {
358        self.look_at_area_internal(top_y, left_x, bottom_y, right_x, true)
359            .map(|arr| {
360                arr.iter()
361                    .map(PositionedLookResult::from_jsvalue_unknown_type)
362                    .collect()
363            })
364            .unwrap_or_default()
365    }
366
367    /// Get all objects of a given type at this position, if any.
368    ///
369    /// [Screeps documentation](https://docs.screeps.com/api/#Room.lookForAt)
370    pub fn look_for_at<T, U>(&self, _ty: T, target: &U) -> Vec<T::Item>
371    where
372        T: LookConstant,
373        U: HasPosition,
374    {
375        let pos = target.pos().into();
376
377        self.look_for_at_internal(T::look_code(), &pos)
378            .map(|arr| arr.iter().map(T::convert_and_check_item).collect())
379            .unwrap_or_default()
380    }
381
382    /// Get all objects of a given type at the given coordinates, if
383    /// any.
384    ///
385    /// [Screeps documentation](https://docs.screeps.com/api/#Room.lookForAt)
386    pub fn look_for_at_xy<T>(&self, _ty: T, x: u8, y: u8) -> Vec<T::Item>
387    where
388        T: LookConstant,
389    {
390        self.look_for_at_xy_internal(T::look_code(), x, y)
391            .map(|arr| arr.iter().map(T::convert_and_check_item).collect())
392            .unwrap_or_default()
393    }
394
395    /// Get all objects of a certain type in a certain area.
396    ///
397    /// [Screeps documentation](https://docs.screeps.com/api/#Room.lookForAtArea)
398    pub fn look_for_at_area<T>(
399        &self,
400        _ty: T,
401        top_y: u8,
402        left_x: u8,
403        bottom_y: u8,
404        right_x: u8,
405    ) -> Vec<PositionedLookResult>
406    where
407        T: LookConstant,
408    {
409        self.look_for_at_area_internal(T::look_code(), top_y, left_x, bottom_y, right_x, true)
410            .map(|arr| {
411                arr.iter()
412                    .map(|lr| PositionedLookResult::from_jsvalue_with_type(lr, T::look_code()))
413                    .collect()
414            })
415            .unwrap_or_default()
416    }
417}
418
419impl PartialEq for Room {
420    fn eq(&self, other: &Room) -> bool {
421        self.name() == other.name()
422    }
423}
424
425impl Eq for Room {}
426
427impl JsCollectionFromValue for Room {
428    fn from_value(val: JsValue) -> Self {
429        val.unchecked_into()
430    }
431}
432
433#[wasm_bindgen]
434extern "C" {
435    #[wasm_bindgen]
436    pub type FindOptions;
437
438    #[wasm_bindgen(method, setter = filter)]
439    pub fn filter(this: &FindOptions, filter: LodashFilter);
440}
441
442#[wasm_bindgen]
443extern "C" {
444    #[wasm_bindgen]
445    pub type JsFindPathOptions;
446
447    #[wasm_bindgen(method, setter = ignoreCreeps)]
448    pub fn ignore_creeps(this: &JsFindPathOptions, ignore: bool);
449
450    #[wasm_bindgen(method, setter = ignoreDestructibleStructures)]
451    pub fn ignore_destructible_structures(this: &JsFindPathOptions, ignore: bool);
452
453    #[wasm_bindgen(method, setter = costCallback)]
454    pub fn cost_callback(
455        this: &JsFindPathOptions,
456        callback: &Closure<dyn FnMut(JsString, CostMatrix) -> JsValue>,
457    );
458
459    #[wasm_bindgen(method, setter = maxOps)]
460    pub fn max_ops(this: &JsFindPathOptions, ops: u32);
461
462    #[wasm_bindgen(method, setter = heuristicWeight)]
463    pub fn heuristic_weight(this: &JsFindPathOptions, weight: f64);
464
465    #[wasm_bindgen(method, setter = serialize)]
466    pub fn serialize(this: &JsFindPathOptions, serialize: bool);
467
468    #[wasm_bindgen(method, setter = maxRooms)]
469    pub fn max_rooms(this: &JsFindPathOptions, max: u8);
470
471    #[wasm_bindgen(method, setter = range)]
472    pub fn range(this: &JsFindPathOptions, range: u32);
473
474    #[wasm_bindgen(method, setter = plainCost)]
475    pub fn plain_cost(this: &JsFindPathOptions, cost: u8);
476
477    #[wasm_bindgen(method, setter = swampCost)]
478    pub fn swamp_cost(this: &JsFindPathOptions, cost: u8);
479}
480
481impl JsFindPathOptions {
482    pub fn new() -> JsFindPathOptions {
483        Object::new().unchecked_into()
484    }
485}
486
487impl Default for JsFindPathOptions {
488    fn default() -> Self {
489        Self::new()
490    }
491}
492
493pub struct FindPathOptions<F, R>
494where
495    F: FnMut(RoomName, CostMatrix) -> R,
496    R: RoomCostResult,
497{
498    pub(crate) ignore_creeps: Option<bool>,
499    pub(crate) ignore_destructible_structures: Option<bool>,
500    pub(crate) cost_callback: F,
501    pub(crate) max_ops: Option<u32>,
502    pub(crate) heuristic_weight: Option<f64>,
503    pub(crate) serialize: Option<bool>,
504    pub(crate) max_rooms: Option<u8>,
505    pub(crate) range: Option<u32>,
506    pub(crate) plain_cost: Option<u8>,
507    pub(crate) swamp_cost: Option<u8>,
508}
509
510impl<R> Default for FindPathOptions<fn(RoomName, CostMatrix) -> R, R>
511where
512    R: RoomCostResult + Default,
513{
514    fn default() -> Self {
515        FindPathOptions {
516            ignore_creeps: None,
517            ignore_destructible_structures: None,
518            cost_callback: |_, _| R::default(),
519            max_ops: None,
520            heuristic_weight: None,
521            serialize: None,
522            max_rooms: None,
523            range: None,
524            plain_cost: None,
525            swamp_cost: None,
526        }
527    }
528}
529
530impl<R> FindPathOptions<fn(RoomName, CostMatrix) -> R, R>
531where
532    R: RoomCostResult + Default,
533{
534    pub fn new() -> Self {
535        Self::default()
536    }
537}
538
539impl<F, R> FindPathOptions<F, R>
540where
541    F: FnMut(RoomName, CostMatrix) -> R,
542    R: RoomCostResult,
543{
544    /// Sets whether the algorithm considers creeps as walkable. Default: False.
545    pub fn ignore_creeps(mut self, ignore: bool) -> Self {
546        self.ignore_creeps = Some(ignore);
547        self
548    }
549
550    /// Sets whether the algorithm considers destructible structure as
551    /// walkable. Default: False.
552    pub fn ignore_destructible_structures(mut self, ignore: bool) -> Self {
553        self.ignore_destructible_structures = Some(ignore);
554        self
555    }
556
557    /// Sets cost callback - default `|_, _| {}`.
558    pub fn cost_callback<F2, R2>(self, cost_callback: F2) -> FindPathOptions<F2, R2>
559    where
560        F2: FnMut(RoomName, CostMatrix) -> R2,
561        R2: RoomCostResult,
562    {
563        let FindPathOptions {
564            ignore_creeps,
565            ignore_destructible_structures,
566            max_ops,
567            heuristic_weight,
568            serialize,
569            max_rooms,
570            range,
571            plain_cost,
572            swamp_cost,
573            ..
574        } = self;
575
576        FindPathOptions {
577            ignore_creeps,
578            ignore_destructible_structures,
579            cost_callback,
580            max_ops,
581            heuristic_weight,
582            serialize,
583            max_rooms,
584            range,
585            plain_cost,
586            swamp_cost,
587        }
588    }
589
590    /// Sets maximum ops - default `2000`.
591    pub fn max_ops(mut self, ops: u32) -> Self {
592        self.max_ops = Some(ops);
593        self
594    }
595
596    /// Sets heuristic weight - default `1.2`.
597    pub fn heuristic_weight(mut self, weight: f64) -> Self {
598        self.heuristic_weight = Some(weight);
599        self
600    }
601
602    /// Sets whether the returned path should be passed to `Room.serializePath`.
603    pub fn serialize(mut self, s: bool) -> Self {
604        self.serialize = Some(s);
605        self
606    }
607
608    /// Sets maximum rooms - default `16`, max `16`.
609    pub fn max_rooms(mut self, rooms: u8) -> Self {
610        self.max_rooms = Some(rooms);
611        self
612    }
613
614    pub fn range(mut self, k: u32) -> Self {
615        self.range = Some(k);
616        self
617    }
618
619    /// Sets plain cost - default `1`.
620    pub fn plain_cost(mut self, cost: u8) -> Self {
621        self.plain_cost = Some(cost);
622        self
623    }
624
625    /// Sets swamp cost - default `5`.
626    pub fn swamp_cost(mut self, cost: u8) -> Self {
627        self.swamp_cost = Some(cost);
628        self
629    }
630
631    pub(crate) fn into_js_options<CR>(self, callback: impl Fn(&JsFindPathOptions) -> CR) -> CR {
632        let mut raw_callback = self.cost_callback;
633
634        let mut owned_callback = move |room: RoomName, cost_matrix: CostMatrix| -> JsValue {
635            raw_callback(room, cost_matrix).into()
636        };
637
638        //
639        // Type erased and boxed callback: no longer a type specific to the closure
640        // passed in, now unified as &Fn
641        //
642
643        let callback_type_erased: &mut dyn FnMut(RoomName, CostMatrix) -> JsValue =
644            &mut owned_callback;
645
646        // Overwrite lifetime of reference so it can be passed to javascript.
647        // It's now pretending to be static data. This should be entirely safe
648        // because we control the only use of it and it remains valid during the
649        // pathfinder callback. This transmute is necessary because "some lifetime
650        // above the current scope but otherwise unknown" is not a valid lifetime.
651        //
652
653        let callback_lifetime_erased: &'static mut dyn FnMut(RoomName, CostMatrix) -> JsValue =
654            unsafe { std::mem::transmute(callback_type_erased) };
655
656        let boxed_callback = Box::new(move |room: JsString, cost_matrix: CostMatrix| -> JsValue {
657            let room = room
658                .try_into()
659                .expect("expected room name in cost callback");
660
661            callback_lifetime_erased(room, cost_matrix)
662        }) as Box<dyn FnMut(JsString, CostMatrix) -> JsValue>;
663
664        let closure = Closure::wrap(boxed_callback);
665
666        //
667        // Create JS object and set properties.
668        //
669
670        let js_options = JsFindPathOptions::new();
671
672        js_options.cost_callback(&closure);
673
674        if let Some(ignore_creeps) = self.ignore_creeps {
675            js_options.ignore_creeps(ignore_creeps);
676        }
677
678        if let Some(ignore_destructible_structures) = self.ignore_destructible_structures {
679            js_options.ignore_destructible_structures(ignore_destructible_structures);
680        }
681
682        if let Some(max_ops) = self.max_ops {
683            js_options.max_ops(max_ops);
684        }
685
686        if let Some(heuristic_weight) = self.heuristic_weight {
687            js_options.heuristic_weight(heuristic_weight);
688        }
689
690        if let Some(serialize) = self.serialize {
691            js_options.serialize(serialize);
692        }
693
694        if let Some(max_rooms) = self.max_rooms {
695            js_options.max_rooms(max_rooms);
696        }
697
698        if let Some(range) = self.range {
699            js_options.range(range);
700        }
701
702        if let Some(plain_cost) = self.plain_cost {
703            js_options.plain_cost(plain_cost);
704        }
705
706        if let Some(swamp_cost) = self.swamp_cost {
707            js_options.swamp_cost(swamp_cost);
708        }
709
710        callback(&js_options)
711    }
712}
713
714#[derive(Clone, Debug, Deserialize, Serialize)]
715pub struct Step {
716    pub x: u32,
717    pub y: u32,
718    pub dx: i32,
719    pub dy: i32,
720    pub direction: Direction,
721}
722
723#[derive(Debug, Deserialize)]
724#[serde(untagged)]
725pub enum Path {
726    Vectorized(Vec<Step>),
727    Serialized(String),
728}
729
730#[derive(Clone, Debug, PartialEq, Eq)]
731pub struct Event {
732    pub event: EventType,
733    pub object_id: String,
734}
735
736impl<'de> Deserialize<'de> for Event {
737    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
738    where
739        D: Deserializer<'de>,
740    {
741        #[derive(Deserialize)]
742        #[serde(field_identifier, rename_all = "camelCase")]
743        enum Field {
744            Event,
745            ObjectId,
746            Data,
747        }
748
749        struct EventVisitor;
750
751        impl<'de> Visitor<'de> for EventVisitor {
752            type Value = Event;
753
754            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
755                formatter.write_str("struct Event")
756            }
757
758            fn visit_map<V>(self, mut map: V) -> Result<Event, V::Error>
759            where
760                V: MapAccess<'de>,
761            {
762                let mut event_type = None;
763                let mut obj_id = None;
764                let mut data_buffer: Option<serde_json::Value> = None;
765
766                while let Some(key) = map.next_key()? {
767                    match key {
768                        Field::Event => {
769                            if event_type.is_some() {
770                                return Err(de::Error::duplicate_field("event"));
771                            }
772                            event_type = Some(map.next_value()?);
773                        }
774                        Field::ObjectId => {
775                            if obj_id.is_some() {
776                                return Err(de::Error::duplicate_field("objectId"));
777                            }
778                            obj_id = Some(map.next_value()?);
779                        }
780                        Field::Data => {
781                            if data_buffer.is_some() {
782                                return Err(de::Error::duplicate_field("data"));
783                            }
784
785                            data_buffer = map.next_value()?;
786                        }
787                    }
788                }
789
790                let event_id = event_type.ok_or_else(|| de::Error::missing_field("event"))?;
791
792                let err = |e| {
793                    de::Error::custom(format_args!(
794                        "can't parse event data due to inner error {e}"
795                    ))
796                };
797
798                let data = if let Some(val) = data_buffer {
799                    match event_id {
800                        1 => Some(EventType::Attack(serde_json::from_value(val).map_err(err)?)),
801                        2 => Some(EventType::ObjectDestroyed(
802                            serde_json::from_value(val).map_err(err)?,
803                        )),
804                        3 => Some(EventType::AttackController),
805                        4 => Some(EventType::Build(serde_json::from_value(val).map_err(err)?)),
806                        5 => Some(EventType::Harvest(
807                            serde_json::from_value(val).map_err(err)?,
808                        )),
809                        6 => Some(EventType::Heal(serde_json::from_value(val).map_err(err)?)),
810                        7 => Some(EventType::Repair(serde_json::from_value(val).map_err(err)?)),
811                        8 => Some(EventType::ReserveController(
812                            serde_json::from_value(val).map_err(err)?,
813                        )),
814                        9 => Some(EventType::UpgradeController(
815                            serde_json::from_value(val).map_err(err)?,
816                        )),
817                        10 => Some(EventType::Exit(serde_json::from_value(val).map_err(err)?)),
818                        11 => Some(EventType::Power(serde_json::from_value(val).map_err(err)?)),
819                        12 => Some(EventType::Transfer(
820                            serde_json::from_value(val).map_err(err)?,
821                        )),
822                        _ => {
823                            return Err(de::Error::custom(format!(
824                                "Event Type Unrecognized: {event_id}"
825                            )));
826                        }
827                    }
828                } else {
829                    // These events do not contain a data field, currently only AttackController
830                    match event_id {
831                        3 => Some(EventType::AttackController),
832                        _ => None,
833                    }
834                };
835
836                let data = data.ok_or_else(|| de::Error::missing_field("data"))?;
837                let obj_id = obj_id.ok_or_else(|| de::Error::missing_field("objectId"))?;
838
839                Ok(Event {
840                    event: data,
841                    object_id: obj_id,
842                })
843            }
844        }
845
846        const FIELDS: &[&str] = &["event", "objectId", "data"];
847        deserializer.deserialize_struct("Event", FIELDS, EventVisitor)
848    }
849}
850
851#[derive(Clone, Debug, PartialEq, Eq)]
852pub enum EventType {
853    Attack(AttackEvent),
854    ObjectDestroyed(ObjectDestroyedEvent),
855    AttackController,
856    Build(BuildEvent),
857    Harvest(HarvestEvent),
858    Heal(HealEvent),
859    Repair(RepairEvent),
860    ReserveController(ReserveControllerEvent),
861    UpgradeController(UpgradeControllerEvent),
862    Exit(ExitEvent),
863    Power(PowerEvent),
864    Transfer(TransferEvent),
865}
866
867#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
868#[serde(rename_all = "camelCase")]
869pub struct AttackEvent {
870    pub target_id: String,
871    pub damage: u32,
872    pub attack_type: AttackType,
873}
874
875#[derive(Clone, Debug, PartialEq, Eq, Deserialize_repr, Serialize_repr)]
876#[repr(u8)]
877pub enum AttackType {
878    Melee = 1,
879    Ranged = 2,
880    RangedMass = 3,
881    Dismantle = 4,
882    HitBack = 5,
883    Nuke = 6,
884}
885
886#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
887pub struct ObjectDestroyedEvent {
888    #[serde(rename = "type")]
889    pub object_type: String,
890}
891
892#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
893#[serde(rename_all = "camelCase")]
894pub struct BuildEvent {
895    pub target_id: String,
896    pub amount: u32,
897    // energySpent is in documentation but is not present
898    //pub energy_spent: u32,
899    // undocumented fields; reference:
900    // https://github.com/screeps/engine/blob/78631905d975700d02786d9b666b9f97b1f6f8f9/src/processor/intents/creeps/build.js#L94
901    pub structure_type: StructureType,
902    pub x: u8,
903    pub y: u8,
904    pub incomplete: bool,
905}
906
907#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
908#[serde(rename_all = "camelCase")]
909pub struct HarvestEvent {
910    pub target_id: String,
911    pub amount: u32,
912}
913
914#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
915#[serde(rename_all = "camelCase")]
916pub struct HealEvent {
917    pub target_id: String,
918    pub amount: u32,
919    pub heal_type: HealType,
920}
921
922#[derive(Clone, Debug, PartialEq, Eq, Deserialize_repr, Serialize_repr)]
923#[repr(u8)]
924pub enum HealType {
925    Melee = 1,
926    Ranged = 2,
927}
928
929#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
930#[serde(rename_all = "camelCase")]
931pub struct RepairEvent {
932    pub target_id: String,
933    pub amount: u32,
934    pub energy_spent: u32,
935}
936
937#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
938#[serde(rename_all = "camelCase")]
939pub struct ReserveControllerEvent {
940    pub amount: u32,
941}
942
943#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
944#[serde(rename_all = "camelCase")]
945pub struct UpgradeControllerEvent {
946    pub amount: u32,
947    pub energy_spent: u32,
948}
949
950#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
951#[serde(rename_all = "camelCase")]
952pub struct ExitEvent {
953    pub room: String,
954    pub x: u32,
955    pub y: u32,
956}
957
958#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
959#[serde(rename_all = "camelCase")]
960pub struct TransferEvent {
961    pub target_id: String,
962    pub resource_type: ResourceType,
963    pub amount: u32,
964}
965
966#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
967#[serde(rename_all = "camelCase")]
968pub struct PowerEvent {
969    pub target_id: String,
970    pub power: PowerType,
971}