// engine use engine::prelude::*; use super::{async_db::AsyncDBAccess, map::MapObjectType, map_db::EntityDBType, surface::Surface}; use anyhow::Result; use cgmath::{vec3, Rad}; use cgmath::{Vector2, Vector3}; // std use std::cmp::Ordering; use std::collections::HashMap; use std::path::Path; use std::sync::Arc; const BOSS_MARKER_SCALE: f32 = 3.0; #[derive(Debug, Clone)] pub struct OrderedElement { pub index: u32, pub references: u32, pub name: String, pub element: T, } #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub struct Coordinate { pub x: u32, pub y: u32, } impl From> for Coordinate { fn from(v: Vector2) -> Self { Coordinate { x: v.x as u32, y: v.y as u32, } } } pub enum NPCSpawnParameter { MinCount(u32), MaxCount(u32), EliteNPC(String), NormalNPC(String), Radius(f32), } pub struct SpawnMarkerEntities { flag: EntityObject, area: EntityObject, } impl SpawnMarkerEntities { pub fn new( engine: &Engine, entity_manager: &mut impl AssetLoader, flag_name: &str, area_name: &str, ) -> Result { let assets = engine.assets(); let mut flag = entity_manager.load_entity(assets, flag_name)?; #[cfg(debug_assertions)] { flag.debug_name = Some("NPC Spawn Flag".to_string()); } Location::new_and_setup(&mut flag)?; let mut area = entity_manager.load_entity(assets, area_name)?; #[cfg(debug_assertions)] { area.debug_name = Some("NPC Spawn Area".to_string()); } Location::new_and_setup(&mut area)?; Ok(Self { flag, area }) } fn clone_entity(entity: &EntityObject, engine: &Engine) -> Result { let mut flag = entity.clone_without_components(engine); flag.clone_component_from::(entity)?; flag.clone_component_from::(entity)?; flag.clone_component_from::(entity)?; Location::setup(flag.multi_mut())?; Ok(flag) } fn clone(&self, engine: &Engine) -> Result { let flag = Self::clone_entity(&self.flag, engine)?; let area = Self::clone_entity(&self.area, engine)?; Ok(Self { flag, area }) } fn set_position(&mut self, position: Vector3) { self.flag .get_component_mut::() .unwrap() .set_position(position); self.area .get_component_mut::() .unwrap() .set_position(position); } fn set_radius(&mut self, radius: f32) { self.area .get_component_mut::() .unwrap() .set_scale(vec3(radius, radius, 1.0)); } pub fn add(self, scene: &mut impl SceneEntities) -> Result { let flag = scene.add_entity(self.flag)?; let area = scene.add_entity(self.area)?; Ok(SpawnMarker { flag, area }) } } pub struct SpawnMarker { flag: Entity, area: Entity, } impl SpawnMarker { fn set_radius(&self, scene_contents: &mut impl SceneEntities, radius: f32) -> Result<()> { let flag_object = scene_contents.entity_mut(self.area)?; flag_object .get_component_mut::()? .set_scale(vec3(radius, radius, 1.0)); Ok(()) } } trait AlterEntities { fn get(&self, coordinate: &Coordinate) -> Option<&Entity>; fn insert( &mut self, coordinate: Coordinate, entity: Entity, position: Vector3, ) -> Option; fn remove(&mut self, coordinate: &Coordinate) -> Option; } impl AlterEntities for HashMap { fn get(&self, coordinate: &Coordinate) -> Option<&Entity> { self.get(coordinate) } fn insert( &mut self, coordinate: Coordinate, entity: Entity, _: Vector3, ) -> Option { self.insert(coordinate, entity) } fn remove(&mut self, coordinate: &Coordinate) -> Option { self.remove(coordinate) } } impl AlterEntities for HashMap)> { fn get(&self, coordinate: &Coordinate) -> Option<&Entity> { self.get(coordinate).map(|(e, _)| e) } fn insert( &mut self, coordinate: Coordinate, entity: Entity, position: Vector3, ) -> Option { self.insert(coordinate, (entity, position)).map(|(e, _)| e) } fn remove(&mut self, coordinate: &Coordinate) -> Option { self.remove(coordinate).map(|(e, _)| e) } } impl SpawnMarker { pub fn remove(&self, scene: &mut impl SceneEntities) -> Result<()> { scene.remove_entity(self.flag)?; scene.remove_entity(self.area)?; Ok(()) } } #[derive(Clone, Debug)] pub struct MapNPCSpawnInfo { pub coordinate: Coordinate, pub position: Vector2, pub radius: f32, pub min_count: u32, pub max_count: u32, pub normal_npc: Option, pub elite_npc: Option, } #[derive(Clone, Debug)] pub struct MapBossSpawnInfo { pub coordinate: Coordinate, pub position: Vector2, pub name: Option, } impl MapBossSpawnInfo { fn setup_flag( marker: &SpawnMarkerEntities, engine: &Engine, position: Vector3, ) -> Result { let mut flag = SpawnMarkerEntities::clone_entity(&marker.flag, engine)?; let location = flag.get_component_mut::()?; let scale = location.scale(); location.set_scale(scale * BOSS_MARKER_SCALE); location.set_position(position); Ok(flag) } } pub struct MapData { pub width: u32, pub height: u32, // surface pub surface: Surface, // entities pub entities: HashMap, // textures pub textures_index: HashMap, pub textures_info: HashMap>>, // <(Coordinate), TextureIndex> pub chunk_handles: HashMap<(u32, u32), Entity>, pub spawn_locations: HashMap)>, pub show_spawn_locations: bool, pub leave_locations: HashMap, pub npc_spawn_areas: Vec<(MapNPCSpawnInfo, Option)>, pub boss_spawns: Vec<(MapBossSpawnInfo, Option)>, pub show_npc_spawns: bool, pub npc_spawn_marker: Option, } impl MapData { pub fn interpolate_height(x: f32, y: f32, surface: &Surface) -> f32 { let x_fract = x.fract(); let y_fract = y.fract(); let ux = x as u32; let uy = y as u32; let left_bottom = surface.point(ux, uy); let right_bottom = surface.point(ux + 1, uy); let left_top = surface.point(ux, uy + 1); let right_top = surface.point(ux + 1, uy + 1); let bottom_right_diff = right_bottom - left_bottom; let top_right_diff = right_top - left_top; let bottom_inter = left_bottom + x_fract * bottom_right_diff; let top_inter = left_top + x_fract * top_right_diff; let inter_diff = top_inter - bottom_inter; let middle_inter = bottom_inter + y_fract * inter_diff; middle_inter.z } pub fn add_height( &mut self, x: u32, y: u32, height: f32, async_db: &AsyncDBAccess, ) -> Result<()> { // (height, x, y, z) let mut vec = vec![ (height, x, y, self.surface.point(x, y).z), (height, x, y + 1, self.surface.point(x, y + 1).z), (height, x + 1, y, self.surface.point(x + 1, y).z), (height, x + 1, y + 1, self.surface.point(x + 1, y + 1).z), ]; vec.sort_by(|(_h1, _x1, _y1, z1), (_h2, _x2, _y2, z2)| { z1.partial_cmp(z2).unwrap_or(Ordering::Equal) }); if height < 0.0 { // take highest point let (_, _, _, zv) = vec[3]; let target_height = zv + height; for (h, _, _, z) in &mut vec { let point_height = target_height - *z; if point_height >= 0.0 { *h = 0.0; } else { *h = point_height; } } } else { // take lowest point let (_, _, _, zv) = vec[0]; let target_height = zv + height; for (h, _, _, z) in &mut vec { let point_height = target_height - *z; if point_height <= 0.0 { *h = 0.0; } else { *h = point_height; } } } for (height, x, y, _z) in vec { self.add_height_to_point(x, y, height, async_db)?; } Ok(()) // TODO: check if tiles at the corners still are plains } #[inline] fn _get_height(x: f32, y: f32, width: u32, height: u32, surface: &Surface) -> f32 { if x >= 0.0 && y >= 0.0 && x <= width as f32 && y <= height as f32 { Self::interpolate_height(x, y, surface) } else { 0.0 } } pub fn get_height(&self, x: f32, y: f32) -> f32 { Self::_get_height(x, y, self.width, self.height, &self.surface) } pub fn find_entity(&self, x: u32, y: u32) -> Option { self.entities.get(&Coordinate { x, y }).copied() } pub fn find_spawn_marker(&self, x: u32, y: u32) -> Option { self.spawn_locations .get(&Coordinate { x, y }) .map(|(e, _)| *e) } pub fn find_leave_marker(&self, x: u32, y: u32) -> Option { self.leave_locations.get(&Coordinate { x, y }).copied() } pub fn find_npc_spawn_marker(&self, x: u32, y: u32) -> Option<&SpawnMarker> { self.npc_spawn_areas .iter() .find(|(spawn_info, _)| Coordinate { x, y } == spawn_info.coordinate) .and_then(|(_, marker)| marker.as_ref()) } pub fn find_boss_spawn_marker(&self, x: u32, y: u32) -> Option { self.boss_spawns .iter() .find(|(spawn_info, _)| Coordinate { x, y } == spawn_info.coordinate) .and_then(|(_, flag)| *flag) } pub fn object_type_of(&self, object: Entity) -> MapObjectType { if self .spawn_locations .values() .any(|(entity, _)| *entity == object) { return MapObjectType::PlayerSpawn; } if self .leave_locations .values() .find(|&entity| *entity == object) .is_some() { return MapObjectType::PlayerDespawn; } if self .npc_spawn_areas .iter() .find(|(_, marker_opt)| match marker_opt { Some(marker) => object == marker.flag, None => false, }) .is_some() { return MapObjectType::NPCSpawn; } if self .boss_spawns .iter() .find(|(_, flag_opt)| match flag_opt { Some(flag) => object == *flag, None => false, }) .is_some() { return MapObjectType::BossSpawn; } if self.entities.values().any(|entity| *entity == object) { return MapObjectType::Object; } MapObjectType::Unknown } #[inline] fn execute_on_entity(&self, x: u32, y: u32, mut f: F) -> Result<()> where F: FnMut(Entity) -> Result<()>, { if let Some(managed_entity) = self.find_entity(x, y) { f(managed_entity)?; } if self.show_spawn_locations { if let Some(managed_entity) = self.find_spawn_marker(x, y) { f(managed_entity)?; } } if let Some(managed_entity) = self.find_leave_marker(x, y) { f(managed_entity)?; } if self.show_npc_spawns { if let Some(managed_entity) = self.find_npc_spawn_marker(x, y) { f(managed_entity.flag)?; f(managed_entity.area)?; } if let Some(flag) = self.find_boss_spawn_marker(x, y) { f(flag)?; } } Ok(()) } pub fn check_height_at(&mut self, x: u32, y: u32, scene: &mut Scene) -> Result<()> { self.execute_on_entity(x, y, |entity| { let entity_object = scene.entity_mut(entity)?; let location = entity_object.get_component_mut::()?; let pos = location.position(); location.set_position(cgmath::Vector3::new( pos.x, pos.y, self.get_height(pos.x, pos.y), )); Ok(()) })?; Ok(()) } pub fn set_spawn_location( &mut self, scene: &mut impl SceneEntities, entity_opt: Option<(String, EntityObject)>, position: cgmath::Vector2, rotation: impl Into>, async_db: &AsyncDBAccess, ) -> Result<()> { Self::set_entity_alike( entity_opt, position, rotation, async_db, &mut self.spawn_locations, (self.width, self.height), &self.surface, EntityDBType::spawn_location(), scene, )?; Ok(()) } pub fn change_boss( &mut self, async_db: &AsyncDBAccess, position: Vector2, name: impl ToString, ) -> Result<()> { if let Some((spawn_info, _)) = self .boss_spawns .iter_mut() .find(|(spawn_info, _)| spawn_info.coordinate == Coordinate::from(position)) { let coordinate = spawn_info.coordinate.clone(); let name = name.to_string(); spawn_info.name = Some(name.clone()); async_db.add(move |sql| sql.update_boss_name(&coordinate, &name))?; } Ok(()) } pub fn change_npc_spawn( &mut self, async_db: &AsyncDBAccess, scene_contents: &mut impl SceneEntities, position: Vector2, npc_spawn_parameter: NPCSpawnParameter, ) -> Result<()> { if let Some((spawn_info, marker)) = self .npc_spawn_areas .iter_mut() .find(|(spawn_info, _)| spawn_info.coordinate == Coordinate::from(position)) { let coordinate = spawn_info.coordinate.clone(); match npc_spawn_parameter { NPCSpawnParameter::MinCount(min_count) => { spawn_info.min_count = min_count; async_db.add(move |sql| { sql.update_npc_spawn_min_npc_count(&coordinate, min_count) })?; } NPCSpawnParameter::MaxCount(max_count) => { spawn_info.max_count = max_count; async_db.add(move |sql| { sql.update_npc_spawn_max_npc_count(&coordinate, max_count) })?; } NPCSpawnParameter::EliteNPC(elite_npc) => { spawn_info.elite_npc = Some(elite_npc.clone()); async_db .add(move |sql| sql.update_npc_spawn_elite_npc(&coordinate, elite_npc))?; } NPCSpawnParameter::NormalNPC(normal_npc) => { spawn_info.normal_npc = Some(normal_npc.clone()); async_db .add(move |sql| sql.update_npc_spawn_normal_npc(&coordinate, normal_npc))?; } NPCSpawnParameter::Radius(radius) => { spawn_info.radius = radius; marker .as_ref() .unwrap() .set_radius(scene_contents, radius)?; async_db.add(move |sql| sql.update_npc_spawn_radius(&coordinate, radius))?; } } } Ok(()) } pub fn disable_spawns(&mut self, scene: &mut impl SceneEntities) -> Result<()> { if self.show_spawn_locations { self.show_spawn_locations = false; for (spawn_entity, _) in self.spawn_locations.values() { scene.remove_entity(*spawn_entity)?; } } Ok(()) } pub fn spawn_locations(&self) -> Vec> { let locations = self .spawn_locations .values() .map(|(_, location)| *location) .collect(); locations } pub fn leave_markers(&self) -> Vec { self.leave_locations.values().copied().collect() } pub fn set_leave_location( &mut self, scene: &mut impl SceneEntities, entity_opt: Option<(String, EntityObject)>, position: cgmath::Vector2, rotation: impl Into>, async_db: &AsyncDBAccess, ) -> Result<()> { Self::set_entity_alike( entity_opt, position, rotation, async_db, &mut self.leave_locations, (self.width, self.height), &self.surface, EntityDBType::leave_location(), scene, ) } pub fn toggle_npc_spawns( &mut self, engine: &Engine, scene: &mut impl SceneEntities, ) -> Result<()> { self.show_npc_spawns = !self.show_npc_spawns; for (spawn_info, marker) in self.npc_spawn_areas.iter_mut() { let height = Self::_get_height( spawn_info.position.x, spawn_info.position.y, self.width, self.height, &self.surface, ); let marker: Option<&mut SpawnMarker> = match marker { Some(marker) => Some(marker), None => match &self.npc_spawn_marker { Some(global_marker) => { let mut new_marker = global_marker.clone(engine)?; // set position new_marker.set_position(spawn_info.position.extend(height)); new_marker.set_radius(spawn_info.radius); *marker = Some(new_marker.add(scene)?); marker.as_mut() } None => None, }, }; if let Some(marker) = marker { // let coordinate = Coordinate::from(*position); if self.show_npc_spawns { // marker.add(scene)?; } else { marker.remove(scene)?; } } } for (spawn_info, flag_marker) in self.boss_spawns.iter_mut() { let height = Self::_get_height( spawn_info.position.x, spawn_info.position.y, self.width, self.height, &self.surface, ); let marker: Option = match flag_marker { Some(marker) => Some(*marker), None => match &self.npc_spawn_marker { Some(global_marker) => { let flag = MapBossSpawnInfo::setup_flag( global_marker, engine, spawn_info.position.extend(height), )?; *flag_marker = Some(scene.add_entity(flag)?); *flag_marker } None => None, }, }; if let Some(marker) = marker { // let coordinate = Coordinate::from(*position); if self.show_npc_spawns { // marker.add(scene)?; } else { scene.remove_entity(marker)?; } } } Ok(()) } pub fn set_npc_spawn( &mut self, engine: &Engine, scene: &mut impl SceneEntities, position: cgmath::Vector2, async_db: &AsyncDBAccess, radius: f32, min: u32, max: u32, ) -> Result<()> { let coordinate = Coordinate::from(position); if self .npc_spawn_areas .iter() .any(|(spawn_info, _)| spawn_info.coordinate == coordinate) { // if there is already a spawn at position, ignore request return Ok(()); } let marker = match self.npc_spawn_marker.as_ref() { Some(managed_entity) => { let mut marker = managed_entity.clone(engine)?; // set position marker.set_position(position.extend(self.get_height(position.x, position.y))); marker.set_radius(radius); Some(marker.add(scene)?) } None => None, }; self.npc_spawn_areas.push(( MapNPCSpawnInfo { coordinate: coordinate.clone(), position, radius, min_count: min, max_count: max, normal_npc: None, elite_npc: None, }, marker, )); async_db .add(move |sql| sql.insert_npc_spawn((coordinate.x, coordinate.y), radius, min, max))?; Ok(()) } pub fn unset_npc_spawn( &mut self, scene: &mut impl SceneEntities, position: cgmath::Vector2, async_db: &AsyncDBAccess, ) -> Result<()> { let coordinate = Coordinate::from(position); if let Some(index) = self .npc_spawn_areas .iter() .position(|(spawn_info, _)| spawn_info.coordinate == coordinate) { let (_, marker) = self.npc_spawn_areas.remove(index); async_db.add(move |sql| sql.remove_npc_spawn((coordinate.x, coordinate.y)))?; if let Some(marker) = marker { marker.remove(scene)?; } } Ok(()) } pub fn set_boss_spawn( &mut self, engine: &Engine, scene: &mut impl SceneEntities, position: cgmath::Vector2, async_db: &AsyncDBAccess, ) -> Result<()> { let coordinate = Coordinate::from(position); if self .boss_spawns .iter() .any(|(spawn_info, _)| spawn_info.coordinate == coordinate) { // if there is already a spawn at position, ignore request return Ok(()); } let marker = match self.npc_spawn_marker.as_ref() { Some(managed_entity) => { let flag = MapBossSpawnInfo::setup_flag( managed_entity, engine, position.extend(self.get_height(position.x, position.y)), )?; Some(scene.add_entity(flag)?) } None => None, }; self.boss_spawns.push(( MapBossSpawnInfo { coordinate: coordinate.clone(), position, name: None, }, marker, )); async_db.add(move |sql| sql.insert_boss_spawn((coordinate.x, coordinate.y)))?; Ok(()) } pub fn unset_boss_spawn( &mut self, scene: &mut impl SceneEntities, position: cgmath::Vector2, async_db: &AsyncDBAccess, ) -> Result<()> { let coordinate = Coordinate::from(position); if let Some(index) = self .boss_spawns .iter() .position(|(spawn_info, _)| spawn_info.coordinate == coordinate) { let (_, marker) = self.boss_spawns.remove(index); async_db.add(move |sql| sql.remove_boss_spawn((coordinate.x, coordinate.y)))?; if let Some(marker) = marker { scene.remove_entity(marker)?; } } Ok(()) } fn set_entity_alike( entity_opt: Option<(String, EntityObject)>, position: cgmath::Vector2, rotation: impl Into>, async_db: &AsyncDBAccess, entities: &mut T, (width, height): (u32, u32), surface: &Surface, table_name: EntityDBType, scene: &mut impl SceneEntities, ) -> Result<()> { let coordinate = Coordinate { x: position.x as u32, y: position.y as u32, }; let position_3d = position.extend(Self::_get_height( position.x, position.y, width, height, surface, )); let rad_rotation = rotation.into(); match entity_opt { // set entity to current location or dismiss Some((entity_name, mut entity)) => { // check if tile is used by an entity if entities.get(&coordinate).is_some() { println!( "tile ({}, {}) has already on entity, consider erasing first", coordinate.x, coordinate.y ); return Ok(()); } let x = coordinate.x; let y = coordinate.y; async_db.add(move |sql| { sql.insert_entity( table_name, (x, y), entity_name, position_3d.truncate(), rad_rotation, ) })?; // set position entity .get_component_mut::() .unwrap() .update_raw(position_3d, rad_rotation); let handle = scene.add_entity(entity)?; entities.insert(coordinate, handle, position_3d); } // erase entity object on current location None => { if let Some(entity) = entities.remove(&coordinate) { async_db.add(move |sql| { sql.remove_entity(table_name, (coordinate.x, coordinate.y)) })?; scene.remove_entity(entity)?; return Ok(()); } } } Ok(()) } pub fn set_entity( &mut self, scene: &mut impl SceneEntities, entity_opt: Option<(String, EntityObject)>, position: cgmath::Vector2, rotation: impl Into>, async_db: &AsyncDBAccess, ) -> Result<()> { Self::set_entity_alike( entity_opt, position, rotation, async_db, &mut self.entities, (self.width, self.height), &self.surface, EntityDBType::entity(), scene, ) } /// Updates the texture used by the specified tile /// Returns a bool which indicates if the used textures or their /// indices have changed /// /// # Arguments /// /// * `x` x coordinate of the tile /// * `y` y coordinate of the tile /// * `texture_name` name of the texture which should be used /// * `texture` texture as `Arc` pub fn set_texture( &mut self, x: u32, y: u32, texture: &Arc, async_db: &AsyncDBAccess, ) -> Result { let mut map_operations_needed = false; let mut first_return = false; let mut remove_index = 0; let texture_path = (*texture.file_name().as_ref().unwrap()).clone(); let texture_name = Path::new(&texture_path.full_path()) .file_stem() .unwrap() .to_str() .unwrap() .to_string(); // texture is already used in the map if let Some(texture_index) = self.textures_index.get(&texture_name) { let texture_id = self.surface.texture_id(x, y); if texture_id == *texture_index { // the texture is already used in this tile -> nothing has to be changed return Ok(false); } else { if let Some(old_texture_info) = self.textures_info.get_mut(&texture_id) { old_texture_info.references -= 1; if old_texture_info.references == 0 { map_operations_needed = true; remove_index = old_texture_info.index; } } if let Some(new_texture_info) = self.textures_info.get_mut(texture_index) { new_texture_info.references += 1; } self.surface.set_texture_id(x, y, *texture_index); let tile_index = Self::tile_index(x, y, self.width); let sql_texture_index = texture_index + 1; async_db.add(move |sql| sql.set_texture(tile_index, sql_texture_index))?; first_return = true; } } if map_operations_needed { self.check_texture_clean_up(remove_index, async_db)?; } if first_return { // returns if the tile got a new texture, // but this texture was already present in the map return Ok(true); } // else - texture is new on this map // commented because of borrowing let texture_index = self.textures_index.len() as u32; self.textures_index .insert(texture_name.clone(), texture_index); self.textures_info.insert( texture_index, OrderedElement { name: texture_name.clone(), index: texture_index, references: 1, element: texture.clone(), }, ); if let Some(info) = self.textures_info.get_mut(&self.surface.texture_id(x, y)) { info.references -= 1; if info.references == 0 { map_operations_needed = true; remove_index = info.index; } } self.surface.set_texture_id(x, y, texture_index); if map_operations_needed { self.check_texture_clean_up(remove_index, async_db)?; } // sql persistency let i = Self::tile_index(x, y, self.width); let sql_texture_index = texture_index + 1; let texture_name_clone = texture_name.clone(); async_db.add(move |sql| { sql.set_texture(i, sql_texture_index)?; sql.insert_new_texture(sql_texture_index, texture_name_clone)?; Ok(()) })?; Ok(true) } } // private impl MapData { fn add_height_to_point( &mut self, x: u32, y: u32, height: f32, async_db: &AsyncDBAccess, ) -> Result<()> { let new_height = self.surface.change_height(x, y, height); let i = Self::tile_index(x, y, self.width + 1); async_db.add(move |sql| sql.update_height(i, new_height))?; Ok(()) } fn tile_index(x: u32, y: u32, width: u32) -> u32 { (x + 1) + (y * width) } fn check_texture_clean_up( &mut self, remove_index: u32, async_db: &AsyncDBAccess, ) -> Result<()> { let info = match self.textures_info.get(&remove_index) { Some(info) => info.clone(), None => { return Err(anyhow::Error::msg(format!( "Index ({}) in textures_info (MapData) not found", remove_index ))) } }; let index = match self.textures_index.remove(&info.name) { Some(index) => index, None => { return Err(anyhow::Error::msg(format!( "Could not remove key ({}) from textures_index", info.name ))) } }; if self.textures_info.remove(&index).is_none() { return Err(anyhow::Error::msg(format!( "Could not remove key ({}) from textures_info", index ))); } let sql_index = remove_index + 1; async_db.add(move |sql| sql.remove_texture(sql_index))?; self.tighten_indices(async_db)?; Ok(()) } fn tighten_indices(&mut self, async_db: &AsyncDBAccess) -> Result<()> { // check if indices are continuous let mut operations_needed = false; let mut broken_index = 0; for i in 0..self.textures_info.len() { if self.textures_info.get(&(i as u32)).is_none() { broken_index = i as u32; operations_needed = true; break; } } if !operations_needed { return Ok(()); } let index_of_last_one = self.textures_info.len() as u32; // update the indices for every tile: last_index -> borken_index for x in 0..self.width { for y in 0..self.height { if self.surface.texture_id(x, y) == index_of_last_one { self.surface.set_texture_id(x, y, broken_index); } } } // (1) get the info of the last index and update its index let corrected_info = match self.textures_info.get_mut(&index_of_last_one) { Some(info) => { info.index = broken_index; info.clone() } None => return Ok(()), }; // use name of info to update the other map if let Some(i) = self.textures_index.get_mut(&corrected_info.name) { *i = broken_index; } // (2) remove the last index self.textures_info.remove(&index_of_last_one); // (3) add the info at the broken index self.textures_info.insert(broken_index, corrected_info); // sql normalization, since its indexing at 1 let sql_broken_index = broken_index + 1; let sql_last_index = index_of_last_one + 1; async_db.add(move |sql| sql.update_texture_index(sql_broken_index, sql_last_index))?; Ok(()) } }