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
20pub type OutputImage = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;
22
23pub const DEFAULT_ROOM_MAX_COLUMNS: u32 = ROOM_SIZE as u32;
25
26pub const DEFAULT_ROOM_MAX_ROWS: u32 = ROOM_SIZE as u32;
28
29pub const DEFAULT_SCALE_FACTOR: u32 = 50;
31
32#[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#[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
197pub fn create_image() -> OutputImage {
199 create_image_with_size_params(DEFAULT_ROOM_MAX_COLUMNS, DEFAULT_ROOM_MAX_ROWS, DEFAULT_SCALE_FACTOR)
200}
201
202pub 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
216pub fn draw_grid(imgbuf: &mut OutputImage) {
218 draw_grid_with_scale_factor(imgbuf, DEFAULT_SCALE_FACTOR)
219}
220
221pub 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
233pub 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
238pub 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
245fn draw_text_number_raw(imgbuf: &mut OutputImage, x: i32, y: i32, text: &str, text_scale_factor: f32) {
247 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 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
348pub 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
353pub 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
364pub 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
369pub 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
386pub 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
391pub 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
417fn 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}