Skip to main content

screeps/constants/
small_enums.rs

1//! Various constants translated as small enums.
2use std::{borrow::Cow, fmt, slice::Iter};
3
4use enum_iterator::Sequence;
5use js_sys::JsString;
6use num_derive::FromPrimitive;
7use num_traits::FromPrimitive;
8use serde::{
9    de::{Error as _, Unexpected},
10    Deserialize, Serialize,
11};
12use serde_repr::{Deserialize_repr, Serialize_repr};
13use wasm_bindgen::prelude::*;
14
15use super::{macros::named_enum_serialize_deserialize, InvalidConstantString};
16use crate::{
17    constants::find::{Exit, Find},
18    prelude::*,
19};
20
21/// Translates non-OK return codes.
22#[derive(
23    Debug, PartialEq, Eq, Clone, Copy, Hash, FromPrimitive, Deserialize_repr, Serialize_repr,
24)]
25#[repr(i8)]
26pub enum ErrorCode {
27    NotOwner = -1,
28    NoPath = -2,
29    NameExists = -3,
30    Busy = -4,
31    NotFound = -5,
32    NotEnough = -6,
33    InvalidTarget = -7,
34    Full = -8,
35    NotInRange = -9,
36    InvalidArgs = -10,
37    Tired = -11,
38    NoBodypart = -12,
39    RclNotEnough = -14,
40    GclNotEnough = -15,
41    AccessDenied = -16,
42}
43
44impl FromReturnCode for ErrorCode {
45    type Error = Self;
46
47    fn result_from_i8(val: i8) -> Result<(), Self::Error> {
48        match val {
49            0 => Ok(()),
50            -1 => Err(ErrorCode::NotOwner),
51            -2 => Err(ErrorCode::NoPath),
52            -3 => Err(ErrorCode::NameExists),
53            -4 => Err(ErrorCode::Busy),
54            -5 => Err(ErrorCode::NotFound),
55            -6 => Err(ErrorCode::NotEnough),
56            -7 => Err(ErrorCode::InvalidTarget),
57            -8 => Err(ErrorCode::Full),
58            -9 => Err(ErrorCode::NotInRange),
59            -10 => Err(ErrorCode::InvalidArgs),
60            -11 => Err(ErrorCode::Tired),
61            -12 => Err(ErrorCode::NoBodypart),
62            -14 => Err(ErrorCode::RclNotEnough),
63            -15 => Err(ErrorCode::GclNotEnough),
64            -16 => Err(ErrorCode::AccessDenied),
65            // SAFETY: Return codes must always be one of the values already covered
66            #[cfg(feature = "unsafe-return-conversion")]
67            _ => unsafe { std::hint::unreachable_unchecked() },
68            #[cfg(not(feature = "unsafe-return-conversion"))]
69            _ => unreachable!(),
70        }
71    }
72
73    fn try_result_from_i8(val: i8) -> Option<Result<(), Self::Error>> {
74        match val {
75            0 => Some(Ok(())),
76            -1 => Some(Err(ErrorCode::NotOwner)),
77            -2 => Some(Err(ErrorCode::NoPath)),
78            -3 => Some(Err(ErrorCode::NameExists)),
79            -4 => Some(Err(ErrorCode::Busy)),
80            -5 => Some(Err(ErrorCode::NotFound)),
81            -6 => Some(Err(ErrorCode::NotEnough)),
82            -7 => Some(Err(ErrorCode::InvalidTarget)),
83            -8 => Some(Err(ErrorCode::Full)),
84            -9 => Some(Err(ErrorCode::NotInRange)),
85            -10 => Some(Err(ErrorCode::InvalidArgs)),
86            -11 => Some(Err(ErrorCode::Tired)),
87            -12 => Some(Err(ErrorCode::NoBodypart)),
88            -14 => Some(Err(ErrorCode::RclNotEnough)),
89            -15 => Some(Err(ErrorCode::GclNotEnough)),
90            -16 => Some(Err(ErrorCode::AccessDenied)),
91            _ => None,
92        }
93    }
94}
95
96/// Translates direction constants.
97#[wasm_bindgen]
98#[derive(
99    Debug,
100    PartialEq,
101    Eq,
102    Clone,
103    Copy,
104    Hash,
105    FromPrimitive,
106    Serialize_repr,
107    Deserialize_repr,
108    Sequence,
109)]
110#[repr(u8)]
111pub enum Direction {
112    Top = 1,
113    TopRight = 2,
114    Right = 3,
115    BottomRight = 4,
116    Bottom = 5,
117    BottomLeft = 6,
118    Left = 7,
119    TopLeft = 8,
120}
121
122impl Direction {
123    /// Whether the direction is orthogonal.
124    ///
125    /// Example usage:
126    ///
127    /// ```
128    /// use screeps::Direction::*;
129    ///
130    /// assert_eq!(Top.is_orthogonal(), true);
131    /// assert_eq!(TopRight.is_orthogonal(), false);
132    /// ```
133    pub fn is_orthogonal(self) -> bool {
134        use Direction::*;
135
136        matches!(self, Top | Right | Bottom | Left)
137    }
138
139    /// Whether the direction is diagonal.
140    ///
141    /// Example usage:
142    ///
143    /// ```
144    /// use screeps::Direction::*;
145    ///
146    /// assert_eq!(Top.is_diagonal(), false);
147    /// assert_eq!(TopRight.is_diagonal(), true);
148    /// ```
149    pub fn is_diagonal(self) -> bool {
150        !self.is_orthogonal()
151    }
152
153    /// Rotate the direction by a specified number of steps clockwise if
154    /// positive or counter-clockwise if negative.
155    ///
156    /// Example usage:
157    ///
158    /// ```
159    /// use screeps::Direction::*;
160    ///
161    /// assert_eq!(Top.multi_rot(1), TopRight);
162    /// assert_eq!(Top.multi_rot(2), Right);
163    /// assert_eq!(Top.multi_rot(-1), TopLeft);
164    /// assert_eq!(Top.multi_rot(-2), Left);
165    /// assert_eq!(Top.multi_rot(64), Top);
166    /// ```
167    pub fn multi_rot(self, times: i8) -> Self {
168        let raw_dir = ((self as u8) - 1).wrapping_add_signed(times) % 8 + 1;
169        // unwrap should be optimized away, as the integer we ended up with
170        // is always a valid value
171        Self::from_u8(raw_dir).unwrap()
172    }
173
174    /// Rotate the direction clockwise by one step.
175    ///
176    /// Example usage:
177    ///
178    /// ```
179    /// use screeps::Direction::*;
180    ///
181    /// assert_eq!(Top.rot_cw(), TopRight);
182    /// ```
183    pub fn rot_cw(self) -> Self {
184        self.multi_rot(1)
185    }
186
187    /// Rotate the direction counter-clockwise by one step.
188    ///
189    /// Example usage:
190    ///
191    /// ```
192    /// use screeps::Direction::*;
193    ///
194    /// assert_eq!(Top.rot_ccw(), TopLeft);
195    /// ```
196    pub fn rot_ccw(self) -> Self {
197        self.multi_rot(-1)
198    }
199
200    /// Returns an iterator over all 8 direction constants, in clockwise order.
201    ///
202    /// Example usage:
203    ///
204    /// ```
205    /// use screeps::Direction;
206    ///
207    /// for dir in Direction::iter() {
208    ///     println!("{:?}", dir);
209    /// }
210    /// ```
211    ///
212    /// Alternatively:
213    /// ```
214    /// use screeps::Direction;
215    /// let mut dirs = Direction::iter();
216    ///
217    /// assert_eq!(dirs.next(), Some(&Direction::Top));
218    /// assert_eq!(dirs.next(), Some(&Direction::TopRight));
219    /// assert_eq!(dirs.next(), Some(&Direction::Right));
220    /// assert_eq!(dirs.next(), Some(&Direction::BottomRight));
221    /// assert_eq!(dirs.next(), Some(&Direction::Bottom));
222    /// assert_eq!(dirs.next(), Some(&Direction::BottomLeft));
223    /// assert_eq!(dirs.next(), Some(&Direction::Left));
224    /// assert_eq!(dirs.next(), Some(&Direction::TopLeft));
225    /// assert_eq!(dirs.next(), None);
226    /// ```
227    pub fn iter() -> Iter<'static, Direction> {
228        use crate::Direction::*;
229        static DIRECTIONS: [Direction; 8] = [
230            Top,
231            TopRight,
232            Right,
233            BottomRight,
234            Bottom,
235            BottomLeft,
236            Left,
237            TopLeft,
238        ];
239        DIRECTIONS.iter()
240    }
241}
242
243impl JsCollectionIntoValue for Direction {
244    fn into_value(self) -> JsValue {
245        (self as u8).into()
246    }
247}
248
249impl JsCollectionFromValue for Direction {
250    fn from_value(val: JsValue) -> Direction {
251        let n = if let Some(val) = val.as_string() {
252            val.parse::<u8>().expect("expected parseable u8 string")
253        } else {
254            val.as_f64().expect("expected number value") as u8
255        };
256
257        Self::from_u8(n).expect("unknown direction")
258    }
259}
260
261impl From<Direction> for (i32, i32) {
262    /// Returns the change in (x, y) when moving in each direction.
263    #[inline]
264    fn from(direction: Direction) -> (i32, i32) {
265        match direction {
266            Direction::Top => (0, -1),
267            Direction::TopRight => (1, -1),
268            Direction::Right => (1, 0),
269            Direction::BottomRight => (1, 1),
270            Direction::Bottom => (0, 1),
271            Direction::BottomLeft => (-1, 1),
272            Direction::Left => (-1, 0),
273            Direction::TopLeft => (-1, -1),
274        }
275    }
276}
277
278impl ::std::ops::Neg for Direction {
279    type Output = Direction;
280
281    /// Negates this direction. Top goes to Bottom, TopRight goes to BottomLeft,
282    /// etc.
283    ///
284    /// Example usage:
285    ///
286    /// ```
287    /// use screeps::Direction::*;
288    ///
289    /// assert_eq!(-Top, Bottom);
290    /// assert_eq!(-BottomRight, TopLeft);
291    /// assert_eq!(-Left, Right);
292    /// ```
293    #[inline]
294    fn neg(self) -> Direction {
295        use Direction::*;
296
297        match self {
298            Top => Bottom,
299            TopRight => BottomLeft,
300            Right => Left,
301            BottomRight => TopLeft,
302            Bottom => Top,
303            BottomLeft => TopRight,
304            Left => Right,
305            TopLeft => BottomRight,
306        }
307    }
308}
309
310impl fmt::Display for Direction {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        let ch = match *self {
313            Direction::Top => "↑",
314            Direction::TopRight => "↗",
315            Direction::Right => "→",
316            Direction::BottomRight => "↘",
317            Direction::Bottom => "↓",
318            Direction::BottomLeft => "↙",
319            Direction::Left => "←",
320            Direction::TopLeft => "↖",
321        };
322        f.write_str(ch)
323    }
324}
325
326/// Type used for when the game returns a direction to an exit.
327///
328/// Restricted more than `Direction` in that it can't be diagonal. Used as the
329/// result of [`Room::find_exit_to`].
330///
331/// Can be converted to [`Find`] for immediate use of [`Room::find`]
332/// and [`Direction`].
333///
334/// [`Room::find`]: crate::objects::Room::find
335/// [`Room::find_exit_to`]: crate::objects::Room::find_exit_to
336#[wasm_bindgen]
337#[derive(
338    Debug,
339    PartialEq,
340    Eq,
341    Clone,
342    Copy,
343    Hash,
344    FromPrimitive,
345    Serialize_repr,
346    Deserialize_repr,
347    Sequence,
348)]
349#[repr(u8)]
350pub enum ExitDirection {
351    Top = 1,
352    Right = 3,
353    Bottom = 5,
354    Left = 7,
355}
356
357impl From<ExitDirection> for Find {
358    #[inline]
359    fn from(dir: ExitDirection) -> Self {
360        match dir {
361            ExitDirection::Top => Find::ExitTop,
362            ExitDirection::Right => Find::ExitRight,
363            ExitDirection::Bottom => Find::ExitBottom,
364            ExitDirection::Left => Find::ExitLeft,
365        }
366    }
367}
368
369impl From<ExitDirection> for Direction {
370    #[inline]
371    fn from(dir: ExitDirection) -> Self {
372        match dir {
373            ExitDirection::Top => Direction::Top,
374            ExitDirection::Right => Direction::Right,
375            ExitDirection::Bottom => Direction::Bottom,
376            ExitDirection::Left => Direction::Left,
377        }
378    }
379}
380
381impl From<ExitDirection> for Exit {
382    fn from(value: ExitDirection) -> Self {
383        match value {
384            ExitDirection::Top => Exit::Top,
385            ExitDirection::Right => Exit::Right,
386            ExitDirection::Bottom => Exit::Bottom,
387            ExitDirection::Left => Exit::Left,
388        }
389    }
390}
391
392/// Translates `COLOR_*` and `COLORS_ALL` constants.
393#[wasm_bindgen]
394#[derive(
395    Debug,
396    PartialEq,
397    Eq,
398    Clone,
399    Copy,
400    FromPrimitive,
401    Hash,
402    Deserialize_repr,
403    Serialize_repr,
404    Sequence,
405)]
406#[repr(u8)]
407pub enum Color {
408    Red = 1,
409    Purple = 2,
410    Blue = 3,
411    Cyan = 4,
412    Green = 5,
413    Yellow = 6,
414    Orange = 7,
415    Brown = 8,
416    Grey = 9,
417    White = 10,
418}
419
420/// Translates `TERRAIN_*` constants.
421#[wasm_bindgen]
422#[derive(
423    Debug,
424    PartialEq,
425    Eq,
426    Clone,
427    Copy,
428    Hash,
429    FromPrimitive,
430    Serialize_repr,
431    Deserialize_repr,
432    Sequence,
433)]
434#[repr(u8)]
435pub enum Terrain {
436    // There's no constant for plains, but the absense of a terrain value indicates a plain
437    Plain = 0,
438    // TERRAIN_MASK_WALL
439    Wall = 1,
440    // TERRAIN_MASK_SWAMP
441    Swamp = 2,
442    /* TERRAIN_MASK_LAVA, unimplemented in game
443     * Lava = 4, */
444}
445
446impl Terrain {
447    // the strings here do not match the terrain mask constants, appearing nowhere
448    // but look results. assuming it's a plain if it's anything invalid is probably
449    // not the best approach but for now it's something
450    pub fn from_look_constant_str(terrain_look_str: &str) -> Self {
451        match terrain_look_str {
452            "wall" => Terrain::Wall,
453            "swamp" => Terrain::Swamp,
454            "plain" => Terrain::Plain,
455            _ => Terrain::Plain,
456        }
457    }
458
459    pub fn from_look_constant_jsvalue(terrain_look_jsvalue: JsValue) -> Self {
460        let terrain_look_string: String = JsString::from(terrain_look_jsvalue).into();
461        Self::from_look_constant_str(&terrain_look_string)
462    }
463}
464
465/// Translates body part type and `BODYPARTS_ALL` constants
466#[wasm_bindgen]
467#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Sequence)]
468pub enum Part {
469    Move = "move",
470    Work = "work",
471    Carry = "carry",
472    Attack = "attack",
473    RangedAttack = "ranged_attack",
474    Tough = "tough",
475    Heal = "heal",
476    Claim = "claim",
477}
478
479named_enum_serialize_deserialize!(Part);
480
481impl Part {
482    /// Translates the `BODYPART_COST` constant.
483    #[inline]
484    pub const fn cost(self) -> u32 {
485        match self {
486            Part::Move => 50,
487            Part::Work => 100,
488            Part::Carry => 50,
489            Part::Attack => 80,
490            Part::RangedAttack => 150,
491            Part::Tough => 10,
492            Part::Heal => 250,
493            Part::Claim => 600,
494            // I guess bindgen is adding a `#[non_exhaustive]` onto the enum and forcing us to do
495            // this:
496            _ => 0,
497        }
498    }
499}
500
501/// Translates the `DENSITY_*` constants.
502#[wasm_bindgen]
503#[derive(
504    Debug,
505    PartialEq,
506    Eq,
507    Clone,
508    Copy,
509    FromPrimitive,
510    Hash,
511    Serialize_repr,
512    Deserialize_repr,
513    Sequence,
514)]
515#[repr(u8)]
516pub enum Density {
517    Low = 1,
518    Moderate = 2,
519    High = 3,
520    Ultra = 4,
521}
522
523impl Density {
524    /// Translates the `MINERAL_DENSITY` constant, the amount of mineral
525    /// generated for each density level
526    #[inline]
527    pub const fn amount(self) -> u32 {
528        match self {
529            Density::Low => 15_000,
530            Density::Moderate => 35_000,
531            Density::High => 70_000,
532            Density::Ultra => 100_000,
533        }
534    }
535
536    /// Translates the `MINERAL_DENSITY_PROBABILITY` constant.
537    ///
538    /// These are values intended for subsequent percentage checks
539    /// in the order `Low` -> `Medium` -> `High` -> `Ultra`. Use the
540    /// [`enum_iterator::all`] iterator to iterate in this order.
541    ///
542    /// If low or ultra on previous regeneration, or random number rolled at
543    /// probability [`MINERAL_DENSITY_CHANGE`], the mineral will determine a
544    /// random new value ([source]):
545    ///
546    ///  - Low: 10% chance
547    ///  - Moderate: 40% chance
548    ///  - High: 40% chance
549    ///  - Ultra: 10% chance
550    ///
551    /// [source]: https://github.com/screeps/engine/blob/c0cfac8f746f26c660501686f16a1fcdb0396d8d/src/processor/intents/minerals/tick.js#L19
552    /// [`MINERAL_DENSITY_CHANGE`]: crate::constants::MINERAL_DENSITY_CHANGE
553    #[inline]
554    pub const fn probability(self) -> f32 {
555        match self {
556            Density::Low => 0.1,
557            Density::Moderate => 0.5,
558            Density::High => 0.9,
559            Density::Ultra => 1.0,
560        }
561    }
562}
563
564/// Translates `ORDER_*` constants.
565#[wasm_bindgen]
566#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
567pub enum OrderType {
568    Sell = "sell",
569    Buy = "buy",
570}