screeps_local_visuals/
render.rs

1
2use std::io::Cursor;
3use image::io::Reader;
4
5use crate::assets_data;
6
7pub use screeps::constants::Terrain;
8
9use screeps::constants::{
10  ResourceType,
11  structure::StructureType
12};
13
14use screeps::local::LocalCostMatrix;
15use screeps::objects::Source;
16use screeps::constants::ROOM_SIZE;
17
18use screeps_utils::offline_map::OfflineObject;
19
20/// A helpful type alias for the type of images this library operates on
21pub type OutputImage = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;
22
23/// The default number of columns in a room (x-coordinate)
24pub const DEFAULT_ROOM_MAX_COLUMNS: u32 = ROOM_SIZE as u32;
25
26/// The default number of rows in a room (y-coordinate)
27pub const DEFAULT_ROOM_MAX_ROWS: u32 = ROOM_SIZE as u32;
28
29/// The default scaling factor for the final image, meaning the number of pixels allocated for each room cell
30pub const DEFAULT_SCALE_FACTOR: u32 = 50;
31
32/// Represents the various different types of resources that can exist in a room
33#[derive(Debug)]
34pub enum Resource {
35  Source,
36  Hydrogen,
37  Oxygen,
38  Keanium,
39  Lemergium,
40  Utrium,
41  Zynthium,
42  Catalyst,
43  Unknown,
44}
45
46/// Represents the various types of player-buildable structures
47#[derive(Debug)]
48pub enum BuildableStructure {
49  ConstructedWall,
50  Container,
51  Controller,
52  Extension,
53  Extractor,
54  Factory,
55  Lab,
56  Link,
57  Nuker,
58  Observer,
59  PowerSpawn,
60  Rampart,
61  Road,
62  Spawn,
63  Storage,
64  Terminal,
65  Tower,
66  Unknown,
67}
68
69impl From<Source> for Resource {
70    #[inline]
71    fn from(_source: Source) -> Resource {
72      Resource::Source
73    }
74}
75
76impl TryFrom<&ResourceType> for Resource {
77    type Error = ();
78
79    #[inline]
80    fn try_from(resource_type: &ResourceType) -> Result<Resource, Self::Error> {
81        Ok(match resource_type {
82            ResourceType::Hydrogen   => Resource::Hydrogen,
83            ResourceType::Oxygen     => Resource::Oxygen,
84            ResourceType::Keanium    => Resource::Keanium,
85            ResourceType::Lemergium  => Resource::Lemergium,
86            ResourceType::Utrium     => Resource::Utrium,
87            ResourceType::Zynthium   => Resource::Zynthium,
88            ResourceType::Catalyst   => Resource::Catalyst,
89            _                        => Resource::Unknown,
90        })
91    }
92}
93
94impl TryFrom<ResourceType> for Resource {
95    type Error = ();
96
97    #[inline]
98    fn try_from(resource_type: ResourceType) -> Result<Resource, Self::Error> {
99        (&resource_type).try_into()
100    }
101}
102
103impl TryFrom<&OfflineObject> for Resource {
104    type Error = ();
105
106    #[inline]
107    fn try_from(obj: &OfflineObject) -> Result<Resource, Self::Error> {
108      match obj {
109        OfflineObject::Source { .. } => Ok(Resource::Source),
110        OfflineObject::Mineral { mineral_type, .. } => {
111          Ok(match mineral_type {
112              ResourceType::Hydrogen   => Resource::Hydrogen,
113              ResourceType::Oxygen     => Resource::Oxygen,
114              ResourceType::Keanium    => Resource::Keanium,
115              ResourceType::Lemergium  => Resource::Lemergium,
116              ResourceType::Utrium     => Resource::Utrium,
117              ResourceType::Zynthium   => Resource::Zynthium,
118              ResourceType::Catalyst   => Resource::Catalyst,
119              _                        => Resource::Unknown,
120          })
121        },
122        _ => Ok(Resource::Unknown)
123      }
124    }
125}
126
127impl TryFrom<OfflineObject> for Resource {
128    type Error = ();
129
130    #[inline]
131    fn try_from(obj: OfflineObject) -> Result<Resource, Self::Error> {
132        (&obj).try_into()
133    }
134}
135
136impl TryFrom<StructureType> for BuildableStructure {
137    type Error = ();
138
139    #[inline]
140    fn try_from(structure_type: StructureType) -> Result<BuildableStructure, Self::Error> {
141        Ok(match structure_type {
142            StructureType::Wall        => BuildableStructure::ConstructedWall,
143            StructureType::Container   => BuildableStructure::Container,
144            StructureType::Controller  => BuildableStructure::Controller,
145            StructureType::Extension   => BuildableStructure::Extension,
146            StructureType::Extractor   => BuildableStructure::Extractor,
147            StructureType::Factory     => BuildableStructure::Factory,
148            StructureType::Lab         => BuildableStructure::Lab,
149            StructureType::Link        => BuildableStructure::Link,
150            StructureType::Nuker       => BuildableStructure::Nuker,
151            StructureType::Observer    => BuildableStructure::Observer,
152            StructureType::PowerSpawn  => BuildableStructure::PowerSpawn,
153            StructureType::Rampart     => BuildableStructure::Rampart,
154            StructureType::Road        => BuildableStructure::Road,
155            StructureType::Spawn       => BuildableStructure::Spawn,
156            StructureType::Storage     => BuildableStructure::Storage,
157            StructureType::Terminal    => BuildableStructure::Terminal,
158            StructureType::Tower       => BuildableStructure::Tower,
159            _                          => BuildableStructure::Unknown,
160        })
161    }
162}
163
164impl TryFrom<&StructureType> for BuildableStructure {
165    type Error = ();
166
167    #[inline]
168    fn try_from(structure_type: &StructureType) -> Result<BuildableStructure, Self::Error> {
169        (*structure_type).try_into()
170    }
171}
172
173impl TryFrom<&OfflineObject> for BuildableStructure {
174  type Error = ();
175
176  #[inline]
177    fn try_from(obj: &OfflineObject) -> Result<BuildableStructure, Self::Error> {
178        Ok(match obj {
179            OfflineObject::ConstructedWall { .. } => BuildableStructure::ConstructedWall,
180            OfflineObject::Controller { .. } => BuildableStructure::Controller,
181            OfflineObject::Extractor { .. } => BuildableStructure::Extractor,
182            OfflineObject::Terminal { .. } => BuildableStructure::Terminal,
183            _                          => BuildableStructure::Unknown,
184        })
185    }
186}
187
188impl TryFrom<OfflineObject> for BuildableStructure {
189  type Error = ();
190
191  #[inline]
192    fn try_from(obj: OfflineObject) -> Result<BuildableStructure, Self::Error> {
193        (&obj).try_into()
194    }
195}
196
197/// Creates an image with default size parameters
198pub fn create_image() -> OutputImage {
199  create_image_with_size_params(DEFAULT_ROOM_MAX_COLUMNS, DEFAULT_ROOM_MAX_ROWS, DEFAULT_SCALE_FACTOR)
200}
201
202/// Creates an image with user-supplied size parameters
203pub fn create_image_with_size_params(room_max_cols: u32, room_max_rows: u32, scale_factor: u32) -> OutputImage {
204  let mut imgbuf = image::ImageBuffer::new((room_max_cols * scale_factor) + 1 as u32, (room_max_rows * scale_factor) + 1 as u32);
205
206  for (_x, _y, pixel) in imgbuf.enumerate_pixels_mut() {
207    let r: u8 = 0;
208    let g: u8 = 0;
209    let b: u8 = 0;
210    *pixel = image::Rgba([r, g, b, 255]);
211  }
212
213  imgbuf
214}
215
216/// Draws a grid on a default-sized image
217pub fn draw_grid(imgbuf: &mut OutputImage)  {
218  draw_grid_with_scale_factor(imgbuf, DEFAULT_SCALE_FACTOR)
219}
220
221/// Draws a grid on an image with user-supplied scaling
222pub fn draw_grid_with_scale_factor(imgbuf: &mut OutputImage, scale_factor: u32) {
223  for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
224    if (x % scale_factor == 0) | (y % scale_factor == 0)  {
225      let r: u8 = 255;
226      let g: u8 = 255;
227      let b: u8 = 255;
228      *pixel = image::Rgba([r, g, b, 128]);
229    }
230  }
231}
232
233/// Draws a text number on a default-sized image at a specific cell location
234pub fn draw_text_number_xy(imgbuf: &mut OutputImage, col: u32, row: u32, text: &str) {
235  draw_text_number_xy_with_scale_factor(imgbuf, col, row, text, DEFAULT_SCALE_FACTOR, DEFAULT_SCALE_FACTOR)
236}
237
238/// Draws a text number on a user-sized image at a specific cell location
239pub fn draw_text_number_xy_with_scale_factor(imgbuf: &mut OutputImage, col: u32, row: u32, text: &str, scale_factor: u32, text_scale_factor: u32) {
240  let x = (col * scale_factor + 2).try_into().unwrap();
241  let y = (row * scale_factor + 2).try_into().unwrap();
242  draw_text_number_raw(imgbuf, x, y, text, text_scale_factor as f32);
243}
244
245/// Underlying function for drawing numbers on an image at a specific cell location
246fn draw_text_number_raw(imgbuf: &mut OutputImage, x: i32, y: i32, text: &str, text_scale_factor: f32) {
247  // let scale = rusttype::Scale::uniform(15.0);
248  let scale = rusttype::Scale::uniform(text_scale_factor);
249  let font = rusttype::Font::try_from_bytes(assets_data::FREE_MONO_FONT_DATA).expect("Could not load FreeMono font");
250  imageproc::drawing::draw_text_mut(imgbuf, image::Rgba([255,255,255,255]), x, y, scale, &font, text);
251}
252
253pub fn draw_cost_matrix(imgbuf: &mut OutputImage, cm: LocalCostMatrix, v_min: u8, v_max: u8, b_max: u8, a: u8, skip_out_of_bounds_values: bool) {
254  draw_cost_matrix_with_scale_factor(imgbuf, cm, v_min, v_max, b_max, a, DEFAULT_SCALE_FACTOR, skip_out_of_bounds_values)
255}
256
257fn draw_cost_matrix_with_scale_factor(imgbuf: &mut OutputImage, cm: LocalCostMatrix, v_min: u8, v_max: u8, b_max: u8, a: u8, scale_factor: u32, skip_out_of_bounds_values: bool) {
258  let alpha_overlay = get_cost_matrix_alpha_overlay(&cm, imgbuf.width(), imgbuf.height(), scale_factor, v_min, v_max, b_max, a, skip_out_of_bounds_values);
259  image::imageops::overlay(imgbuf, &alpha_overlay, 0, 0);
260
261  for (position, value) in cm.iter() {
262    if value == 0 {
263      continue;
264    }
265
266    if skip_out_of_bounds_values {
267      if value > v_max {
268        continue;
269      }
270
271      if value < v_min {
272        continue;
273      }
274    }
275
276    let col = position.x.u8();
277    let row = position.y.u8();
278
279    let text = value.to_string();
280
281    let text_scale_factor = if value > 9 {
282      ((scale_factor as f32) * 0.75) as u32
283    }
284    else {
285      scale_factor
286    };
287
288    draw_text_number_xy_with_scale_factor(imgbuf, col.into(), row.into(), &text, scale_factor, text_scale_factor);
289  }
290}
291
292fn get_cost_matrix_alpha_overlay(cm: &LocalCostMatrix, overlay_width: u32, overlay_height: u32, scale_factor: u32, v_min: u8, v_max: u8, b_max: u8, a: u8, skip_out_of_bounds_values: bool) -> OutputImage {
293  let mut alpha_overlay = image::ImageBuffer::new(overlay_width, overlay_height);
294
295  for (position, value) in cm.iter() {
296    let clamped_value = if value > v_max {
297      if skip_out_of_bounds_values {
298        continue;
299      }
300      v_max
301    }
302    else {
303      if value < v_min {
304        if skip_out_of_bounds_values {
305          continue;
306        }
307        v_min
308      }
309      else {
310        value
311      }
312    };
313
314    let range = (v_max - v_min) as f32;
315
316    let b = b_max - lerp(0.0, b_max as f32, ((clamped_value - v_min) as f32)/range) as u8;
317
318    let others = lerp(b_max as f32, 0.0, (b as f32)/(b_max as f32)) as u8;
319
320    let alpha = if value == 0 {
321      0
322    }
323    else {
324      a
325    };
326
327    let rgba = image::Rgba([others, others, b, alpha]);
328    // let rgba = image::Rgba([0, 0, 255, a]);
329
330    let x = position.x.u8();
331    let y = position.y.u8();
332
333    let x_start = (x as u32) * scale_factor + 1;
334    let y_start = (y as u32) * scale_factor + 1;
335    let x_end = x_start + scale_factor;
336    let y_end = y_start + scale_factor;
337
338    for draw_x in x_start..x_end {
339      for draw_y in y_start..y_end {
340        alpha_overlay.put_pixel(draw_x, draw_y, rgba);
341      }
342    }
343  }
344
345  alpha_overlay
346}
347
348/// Draws a [Terrain] tile at a specific cell location
349pub fn draw_terrain_tile_xy(imgbuf: &mut OutputImage, col: u32, row: u32, tile: &Terrain) {
350  draw_terrain_tile_xy_with_scale_factor(imgbuf, col, row, tile, DEFAULT_SCALE_FACTOR)
351}
352
353/// Draws a [Terrain] tile at a specific cell location with a user-supplied scaling factor
354pub fn draw_terrain_tile_xy_with_scale_factor(imgbuf: &mut OutputImage, col: u32, row: u32, tile: &Terrain, scale_factor: u32) {
355  let tile_img_reader_result = match tile {
356    Terrain::Plain => Reader::new(Cursor::new(assets_data::TERRAIN_PLAIN_IMG_DATA)).with_guessed_format(),
357    Terrain::Swamp => Reader::new(Cursor::new(assets_data::TERRAIN_SWAMP_IMG_DATA)).with_guessed_format(),
358    Terrain::Wall  => Reader::new(Cursor::new(assets_data::TERRAIN_WALL_IMG_DATA)).with_guessed_format(),
359  };
360
361  draw_tile_img_xy(imgbuf, col, row, tile_img_reader_result, scale_factor);
362}
363
364/// Draws a [Resource] tile at a specific cell location
365pub fn draw_resource_tile_xy(imgbuf: &mut OutputImage, col: u32, row: u32, tile: &Resource) {
366  draw_resource_tile_xy_with_scale_factor(imgbuf, col, row, tile, DEFAULT_SCALE_FACTOR)
367}
368
369/// Draws a [Resource] tile at a specific cell location with a user-supplied scaling factor
370pub fn draw_resource_tile_xy_with_scale_factor(imgbuf: &mut OutputImage, col: u32, row: u32, tile: &Resource, scale_factor: u32) {
371  let tile_img_reader_result = match tile {
372    Resource::Source    => Reader::new(Cursor::new(assets_data::RESOURCE_SOURCE_IMG_DATA)).with_guessed_format(),
373    Resource::Hydrogen  => Reader::new(Cursor::new(assets_data::RESOURCE_HYDROGEN_IMG_DATA)).with_guessed_format(),
374    Resource::Oxygen    => Reader::new(Cursor::new(assets_data::RESOURCE_OXYGEN_IMG_DATA)).with_guessed_format(),
375    Resource::Keanium   => Reader::new(Cursor::new(assets_data::RESOURCE_KEANIUM_IMG_DATA)).with_guessed_format(),
376    Resource::Lemergium => Reader::new(Cursor::new(assets_data::RESOURCE_LEMERGIUM_IMG_DATA)).with_guessed_format(),
377    Resource::Utrium    => Reader::new(Cursor::new(assets_data::RESOURCE_UTRIUM_IMG_DATA)).with_guessed_format(),
378    Resource::Zynthium  => Reader::new(Cursor::new(assets_data::RESOURCE_ZYNTHIUM_IMG_DATA)).with_guessed_format(),
379    Resource::Catalyst  => Reader::new(Cursor::new(assets_data::RESOURCE_CATALYST_IMG_DATA)).with_guessed_format(),
380    Resource::Unknown   => Reader::new(Cursor::new(assets_data::RESOURCE_UNKNOWN_IMG_DATA)).with_guessed_format(),
381  };
382
383  draw_tile_img_xy(imgbuf, col, row, tile_img_reader_result, scale_factor);
384}
385
386/// Draws a [BuildableStructure] tile at a specific cell location
387pub fn draw_buildablestructure_tile_xy(imgbuf: &mut OutputImage, col: u32, row: u32, tile: &BuildableStructure) {
388  draw_buildablestructure_tile_xy_with_scale_factor(imgbuf, col, row, tile, DEFAULT_SCALE_FACTOR)
389}
390
391/// Draws a [BuildableStructure] tile at a specific cell location with a user-supplied scaling factor
392pub fn draw_buildablestructure_tile_xy_with_scale_factor(imgbuf: &mut OutputImage, col: u32, row: u32, tile: &BuildableStructure, scale_factor: u32) {
393  let tile_img_reader_result = match tile {
394    BuildableStructure::ConstructedWall => Reader::new(Cursor::new(assets_data::STRUCTURE_CONSTRUCTEDWALL_IMG_DATA)).with_guessed_format(),
395    BuildableStructure::Container       => Reader::new(Cursor::new(assets_data::STRUCTURE_CONTAINER_IMG_DATA)).with_guessed_format(),
396    BuildableStructure::Controller      => Reader::new(Cursor::new(assets_data::STRUCTURE_CONTROLLER_IMG_DATA)).with_guessed_format(),
397    BuildableStructure::Extension       => Reader::new(Cursor::new(assets_data::STRUCTURE_EXTENSION_IMG_DATA)).with_guessed_format(),
398    BuildableStructure::Extractor       => Reader::new(Cursor::new(assets_data::STRUCTURE_EXTRACTOR_IMG_DATA)).with_guessed_format(),
399    BuildableStructure::Factory         => Reader::new(Cursor::new(assets_data::STRUCTURE_FACTORY_IMG_DATA)).with_guessed_format(),
400    BuildableStructure::Lab             => Reader::new(Cursor::new(assets_data::STRUCTURE_LAB_IMG_DATA)).with_guessed_format(),
401    BuildableStructure::Link            => Reader::new(Cursor::new(assets_data::STRUCTURE_LINK_IMG_DATA)).with_guessed_format(),
402    BuildableStructure::Nuker           => Reader::new(Cursor::new(assets_data::STRUCTURE_NUKER_IMG_DATA)).with_guessed_format(),
403    BuildableStructure::Observer        => Reader::new(Cursor::new(assets_data::STRUCTURE_OBSERVER_IMG_DATA)).with_guessed_format(),
404    BuildableStructure::PowerSpawn      => Reader::new(Cursor::new(assets_data::STRUCTURE_POWERSPAWN_IMG_DATA)).with_guessed_format(),
405    BuildableStructure::Rampart         => Reader::new(Cursor::new(assets_data::STRUCTURE_RAMPART_IMG_DATA)).with_guessed_format(),
406    BuildableStructure::Road            => Reader::new(Cursor::new(assets_data::STRUCTURE_ROAD_IMG_DATA)).with_guessed_format(),
407    BuildableStructure::Spawn           => Reader::new(Cursor::new(assets_data::STRUCTURE_SPAWN_IMG_DATA)).with_guessed_format(),
408    BuildableStructure::Storage         => Reader::new(Cursor::new(assets_data::STRUCTURE_STORAGE_IMG_DATA)).with_guessed_format(),
409    BuildableStructure::Terminal        => Reader::new(Cursor::new(assets_data::STRUCTURE_TERMINAL_IMG_DATA)).with_guessed_format(),
410    BuildableStructure::Tower           => Reader::new(Cursor::new(assets_data::STRUCTURE_TOWER_IMG_DATA)).with_guessed_format(),
411    BuildableStructure::Unknown         => Reader::new(Cursor::new(assets_data::STRUCTURE_UNKNOWN_IMG_DATA)).with_guessed_format(),
412  };
413
414  draw_tile_img_xy(imgbuf, col, row, tile_img_reader_result, scale_factor);
415}
416
417/// Underlying helper function to draw a tile image at a specific cell location
418fn draw_tile_img_xy(imgbuf: &mut OutputImage, col: u32, row: u32, tile_img_reader_result: Result<Reader<Cursor<&[u8]>>, std::io::Error>, scale_factor: u32) {
419
420  if let Ok(tile_img_reader) = tile_img_reader_result {
421    let tile_img_result = tile_img_reader.decode();
422    if let Ok(tile_img_dynamic) = tile_img_result {
423      let mut tile_img = tile_img_dynamic.to_rgba8();
424
425      let new_width = scale_factor;
426      let new_height = scale_factor;
427      if (new_width != tile_img.width()) | (new_height != tile_img.height())  {
428        tile_img = image::imageops::resize(&tile_img, new_width, new_height, image::imageops::FilterType::Nearest);
429      }
430
431      let x = (col * scale_factor + 1).try_into().unwrap();
432      let y = (row * scale_factor + 1).try_into().unwrap();
433
434      image::imageops::overlay(imgbuf, &tile_img, x, y);
435    }
436  };
437}
438
439pub fn get_tile_alpha_overlay(overlay_width: u32, overlay_height: u32, scale_factor: u32, r: u8, g: u8, b: u8, a: u8, x: u8, y: u8) -> OutputImage {
440  let mut alpha_overlay = image::ImageBuffer::new(overlay_width, overlay_height);
441
442  let rgba = image::Rgba([r, g, b, a]);
443
444  let x_start = (x as u32) * scale_factor + 1;
445  let y_start = (y as u32) * scale_factor + 1;
446  let x_end = x_start + scale_factor;
447  let y_end = y_start + scale_factor;
448
449  for draw_x in x_start..x_end {
450    for draw_y in y_start..y_end {
451      alpha_overlay.put_pixel(draw_x, draw_y, rgba);
452    }
453  }
454
455  alpha_overlay
456}
457
458pub fn get_tile_alpha_overlay_multi_tile(overlay_width: u32, overlay_height: u32, scale_factor: u32, r: u8, g: u8, b: u8, a: u8, tiles: &[(u8, u8)]) -> OutputImage {
459  let mut alpha_overlay = image::ImageBuffer::new(overlay_width, overlay_height);
460
461  let rgba = image::Rgba([r, g, b, a]);
462
463  for (x, y) in tiles {
464    let x_start = (*x as u32) * scale_factor + 1;
465    let y_start = (*y as u32) * scale_factor + 1;
466    let x_end = x_start + scale_factor;
467    let y_end = y_start + scale_factor;
468
469    for draw_x in x_start..x_end {
470      for draw_y in y_start..y_end {
471        alpha_overlay.put_pixel(draw_x, draw_y, rgba);
472      }
473    }
474  }
475
476  alpha_overlay
477}
478
479fn lerp(v0: f32, v1: f32, t: f32) -> f32 {
480  return (1.0 - t) * v0 + t * v1;
481}