screeps/objects/impls/
map_visual.rs

1use js_sys::JsString;
2use serde::Serialize;
3
4use crate::{
5    local::{Position, RoomCoordinate, RoomName},
6    objects::{CircleStyle, LineStyle, PolyStyle, RectStyle},
7    TextAlign,
8};
9
10#[derive(Clone, Serialize)]
11pub struct MapCircleData {
12    x: RoomCoordinate,
13    y: RoomCoordinate,
14    n: RoomName,
15    #[serde(rename = "s")]
16    style: CircleStyle,
17}
18
19#[derive(Clone, Serialize)]
20pub struct MapLineData {
21    x1: RoomCoordinate,
22    y1: RoomCoordinate,
23    n1: RoomName,
24    x2: RoomCoordinate,
25    y2: RoomCoordinate,
26    n2: RoomName,
27    #[serde(rename = "s")]
28    style: LineStyle,
29}
30
31#[derive(Clone, Serialize)]
32pub struct MapRectData {
33    x: RoomCoordinate,
34    y: RoomCoordinate,
35    n: RoomName,
36    #[serde(rename = "w")]
37    width: u32,
38    #[serde(rename = "h")]
39    height: u32,
40    #[serde(rename = "s")]
41    style: RectStyle,
42}
43
44#[derive(Clone, Serialize)]
45pub struct MapPolyPoint {
46    x: RoomCoordinate,
47    y: RoomCoordinate,
48    n: RoomName,
49}
50
51impl From<&Position> for MapPolyPoint {
52    fn from(pos: &Position) -> MapPolyPoint {
53        MapPolyPoint {
54            x: pos.x(),
55            y: pos.y(),
56            n: pos.room_name(),
57        }
58    }
59}
60
61#[derive(Clone, Serialize)]
62pub struct MapPolyData {
63    points: Vec<MapPolyPoint>,
64    #[serde(rename = "s")]
65    style: PolyStyle,
66}
67
68/// The font style for map text visuals.
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
70#[serde(rename_all = "lowercase")]
71pub enum MapFontStyle {
72    /// Use the normal font face.
73    #[default]
74    Normal,
75    /// Use the italic font face.
76    Italic,
77    /// Use the oblique font face.
78    Oblique,
79}
80
81impl MapFontStyle {
82    pub fn is_normal(&self) -> bool {
83        matches!(self, MapFontStyle::Normal)
84    }
85}
86
87/// The font variant for map text visuals.
88#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
89#[serde(rename_all = "kebab-case")]
90pub enum MapFontVariant {
91    /// No variant for the font, text rendered normally.
92    #[default]
93    Normal,
94    /// Render all lowercase characters in small caps.
95    SmallCaps,
96}
97
98impl MapFontVariant {
99    pub fn is_normal(&self) -> bool {
100        matches!(self, MapFontVariant::Normal)
101    }
102}
103
104/// Settings for text visuals on the map, used with [`MapVisual::text`].
105///
106/// This is different than [`TextStyle`] which is used with [`RoomVisual::text`]
107/// because the two methods take data in a slightly different format. Notably,
108/// room visuals accept colors in any web format and take use a shorthond for
109/// font data, where map visuals require stricter color formats and have
110/// different font options.
111///
112/// <div class="warning">
113/// <b>Warning</b>
114///
115/// The `backgroundPadding` setting in the Screeps docs does not function in
116/// game so it is not present in this API.
117/// </div><br/>
118///
119/// See also: [Screeps docs](https://docs.screeps.com/api/#Game.map-visual.text).
120///
121/// [`TextStyle`]: crate::objects::visual::TextStyle
122/// [`RoomVisual::text`]: crate::objects::visual::RoomVisual::text
123#[derive(Clone, Default, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub struct MapTextStyle {
126    #[serde(skip_serializing_if = "Option::is_none")]
127    color: Option<String>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    font_family: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    font_size: Option<f32>,
132    #[serde(skip_serializing_if = "MapFontStyle::is_normal")]
133    font_style: MapFontStyle,
134    #[serde(skip_serializing_if = "MapFontVariant::is_normal")]
135    font_variant: MapFontVariant,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    stroke: Option<String>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    stroke_width: Option<f32>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    background_color: Option<String>,
142    // This setting does not do anything, even though it's documented.
143    // #[serde(skip_serializing_if = "Option::is_none")]
144    // background_padding: Option<f32>,
145    #[serde(skip_serializing_if = "TextAlign::is_center")]
146    align: TextAlign,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    opacity: Option<f32>,
149}
150
151impl MapTextStyle {
152    /// Sets the color of the text style.
153    ///
154    /// **Unlike room visuals, only hex triplets with a leading `#` are valid.**
155    ///
156    /// The default value is `#FFFFFF`.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use screeps::MapTextStyle;
162    ///
163    /// // Bright pink! We really need to see this text!
164    /// let style = MapTextStyle::default().color("#FF00FF");
165    /// ```
166    pub fn color(mut self, val: impl Into<String>) -> Self {
167        self.color = Some(val.into());
168        self
169    }
170
171    /// Sets the font family of the text style. Font families with spaces in
172    /// their name must be quoted. Normally this involves escaping quotes in a
173    /// string literal.
174    ///
175    /// The default value is `sans-serif`.
176    ///
177    /// # Examples
178    ///
179    /// ```rust
180    /// use screeps::MapTextStyle;
181    /// let monospace_style = MapTextStyle::default().font_family("monospace");
182    ///
183    /// let font_family_style = MapTextStyle::default().font_family("\"Comic Sans MS\"");
184    ///
185    /// let fallback_style = MapTextStyle::default().font_family("\"Comic Sans MS\", Times, serif");
186    /// ```
187    ///
188    /// For more information about font families, see the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family).
189    pub fn font_family(mut self, val: impl Into<String>) -> Self {
190        self.font_family = Some(val.into());
191        self
192    }
193
194    /// Sets the size of the font, in *game coordinates*. This means that a
195    /// `font_size` of 1 corresponds to the same size as one coordinate on the
196    /// map. This does **not** support any other units for size.
197    ///
198    /// The default value is `10`.
199    ///
200    /// # Examples
201    /// ```rust
202    /// use screeps::MapTextStyle;
203    ///
204    /// let tiny_text = MapTextStyle::default().font_size(2_f32);
205    ///
206    /// let room_height = MapTextStyle::default().font_size(50_f32);
207    /// ```
208    pub fn font_size(mut self, val: f32) -> Self {
209        self.font_size = Some(val);
210        self
211    }
212
213    /// Sets the style of the font. This controls whether a font should be
214    /// styled with a normal, italic, or oblique face.
215    ///
216    /// The default value is [`MapFontStyle::Normal`].
217    ///
218    /// `oblique <angle>` is not supported.
219    ///
220    /// # Examples
221    /// ```rust
222    /// use screeps::{MapFontStyle, MapTextStyle};
223    ///
224    /// let normal = MapTextStyle::default().font_style(MapFontStyle::Normal);
225    /// let italic = MapTextStyle::default().font_style(MapFontStyle::Italic);
226    /// ```
227    ///
228    /// For more information about font styles, see the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
229    ///
230    /// [`MapFontStyle::Normal`]: self::MapFontStyle#variant.Normal
231    pub fn font_style(mut self, val: MapFontStyle) -> Self {
232        self.font_style = val;
233        self
234    }
235
236    /// Sets the variant of the font. This controls whether or not text is
237    /// rendered as small caps.
238    ///
239    /// The default value is [`MapFontVariant::Normal`].
240    ///
241    /// # Examples
242    /// ```rust
243    /// use screeps::{MapFontVariant, MapTextStyle};
244    ///
245    /// let normal = MapTextStyle::default().font_variant(MapFontVariant::Normal);
246    /// let small_caps = MapTextStyle::default().font_variant(MapFontVariant::SmallCaps);
247    /// ```
248    ///
249    /// [`MapFontVariant::Normal`]: self::MapFontVariant#variant.Normal
250    pub fn font_variant(mut self, val: MapFontVariant) -> Self {
251        self.font_variant = val;
252        self
253    }
254
255    /// **Unlike room visuals, only hex triplets with a leading `#` are valid.**
256    pub fn stroke_color(mut self, val: Option<impl Into<String>>) -> Self {
257        self.stroke = val.map(Into::into);
258        self
259    }
260
261    /// Sets the width of the stroke for the text, if the stroke is enabled.
262    ///
263    /// A stroke width of `0` results in no stroke. Negative values are invalid.
264    ///
265    /// The default value is `0.15`.
266    ///
267    /// # Examples
268    /// ```rust
269    /// use screeps::MapTextStyle;
270    ///
271    /// let thin = MapTextStyle::default().stroke_width(0.05_f32);
272    /// let wide = MapTextStyle::default().stroke_width(2.0_f32);
273    /// ```
274    pub fn stroke_width(mut self, val: f32) -> Self {
275        self.stroke_width = Some(val);
276        self
277    }
278
279    /// Sets or removes the background color for the text visual. When a
280    /// background is enabled, the text's vertical align is set to `middle`
281    /// instead of `baseline`.
282    ///
283    /// **Unlike room visuals, only hex triplets with a leading `#` are valid.**
284    ///
285    /// The default value is `None`.
286    ///
287    /// # Examples
288    /// ```rust
289    /// use screeps::MapTextStyle;
290    ///
291    /// let lavender = MapTextStyle::default().background_color(Some("#b373de"));
292    /// // Even though this is the default, it's possible to unset the background color.
293    /// let unset = MapTextStyle::default().background_color(None);
294    /// ```
295    // This only takes `&str` to avoid issues where passing `None` fails to infer a
296    // type.
297    pub fn background_color(mut self, val: Option<&str>) -> Self {
298        self.background_color = val.map(String::from);
299        self
300    }
301
302    /// Sets the horizontal alignment of the text.
303    ///
304    /// The default value is [`TextAlign::Center`].
305    ///
306    /// # Examples
307    /// ```rust
308    /// use screeps::{MapTextStyle, TextAlign};
309    ///
310    /// let left = MapTextStyle::default().align(TextAlign::Left);
311    /// ```
312    ///
313    /// [`TextAlign::Center`]: crate::objects::visual::TextAlign#variant.Center
314    pub fn align(mut self, val: TextAlign) -> Self {
315        self.align = val;
316        self
317    }
318
319    /// Sets the opacity of the text visual. Valid values are in the range
320    /// `0.0..=1.0`.
321    ///
322    /// The default value is `0.5`.
323    ///
324    /// # Examples
325    ///
326    /// ```rust
327    /// use screeps::MapTextStyle;
328    ///
329    /// let opaque = MapTextStyle::default().opacity(1.0_f32);
330    /// let ghost = MapTextStyle::default().opacity(0.05_f32);
331    /// ```
332    pub fn opacity(mut self, val: f32) -> Self {
333        self.opacity = Some(val);
334        self
335    }
336}
337
338#[derive(Clone, Serialize)]
339pub struct MapTextData {
340    text: String,
341    x: RoomCoordinate,
342    y: RoomCoordinate,
343    n: RoomName,
344    #[serde(rename = "s")]
345    style: MapTextStyle,
346}
347
348#[derive(Clone, Serialize)]
349#[serde(tag = "t")]
350pub enum MapVisualShape {
351    #[serde(rename = "c")]
352    Circle(MapCircleData),
353    #[serde(rename = "l")]
354    Line(MapLineData),
355    #[serde(rename = "r")]
356    Rect(MapRectData),
357    #[serde(rename = "p")]
358    Poly(MapPolyData),
359    #[serde(rename = "t")]
360    Text(MapTextData),
361}
362
363impl MapVisualShape {
364    pub fn circle(center: Position, style: CircleStyle) -> MapVisualShape {
365        MapVisualShape::Circle(MapCircleData {
366            x: center.x(),
367            y: center.y(),
368            n: center.room_name(),
369            style,
370        })
371    }
372
373    pub fn line(from: Position, to: Position, style: LineStyle) -> MapVisualShape {
374        MapVisualShape::Line(MapLineData {
375            x1: from.x(),
376            y1: from.y(),
377            n1: from.room_name(),
378            x2: to.x(),
379            y2: to.y(),
380            n2: to.room_name(),
381            style,
382        })
383    }
384
385    pub fn rect(top_left: Position, width: u32, height: u32, style: RectStyle) -> MapVisualShape {
386        MapVisualShape::Rect(MapRectData {
387            x: top_left.x(),
388            y: top_left.y(),
389            n: top_left.room_name(),
390            width,
391            height,
392            style,
393        })
394    }
395
396    pub fn poly(points: Vec<MapPolyPoint>, style: PolyStyle) -> MapVisualShape {
397        MapVisualShape::Poly(MapPolyData { points, style })
398    }
399
400    pub fn text(pos: Position, text: String, style: MapTextStyle) -> MapVisualShape {
401        MapVisualShape::Text(MapTextData {
402            x: pos.x(),
403            y: pos.y(),
404            n: pos.room_name(),
405            text,
406            style,
407        })
408    }
409}
410
411pub struct MapVisual {}
412
413impl MapVisual {
414    pub fn draw(visual: &MapVisualShape) {
415        let val = serde_wasm_bindgen::to_value(visual).expect("expect convert visual to value");
416
417        crate::console::add_visual(Some(&JsString::from("map")), &val);
418    }
419
420    pub fn draw_multi(visuals: &[MapVisualShape]) {
421        for visual in visuals {
422            let val = serde_wasm_bindgen::to_value(visual).expect("expect convert visual to value");
423
424            crate::console::add_visual(Some(&JsString::from("map")), &val);
425        }
426    }
427
428    pub fn circle(pos: Position, style: CircleStyle) {
429        Self::draw(&MapVisualShape::circle(pos, style));
430    }
431
432    pub fn line(from: Position, to: Position, style: LineStyle) {
433        Self::draw(&MapVisualShape::line(from, to, style));
434    }
435
436    pub fn rect(top_left: Position, width: u32, height: u32, style: RectStyle) {
437        Self::draw(&MapVisualShape::rect(top_left, width, height, style));
438    }
439
440    pub fn poly(points: Vec<Position>, style: PolyStyle) {
441        let points = points.iter().map(Into::into).collect();
442        Self::draw(&MapVisualShape::poly(points, style));
443    }
444
445    pub fn text(pos: Position, text: String, style: MapTextStyle) {
446        Self::draw(&MapVisualShape::text(pos, text, style));
447    }
448}