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