engine/map/src/mapdata.rs
2025-02-28 08:19:35 +01:00

1202 lines
34 KiB
Rust

// engine
use engine::prelude::*;
use super::{async_db::AsyncDBAccess, map::MapObjectType, map_db::EntityDBType, surface::Surface};
use anyhow::Result;
use cgmath::{Rad, vec3};
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<T> {
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<Vector2<f32>> for Coordinate {
fn from(v: Vector2<f32>) -> 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(
world: &mut World,
entity_manager: &mut impl AssetLoader,
flag_name: &str,
area_name: &str,
) -> Result<Self> {
let mut assets = AssetHandler::create(world);
let mut flag = entity_manager.load_entity(&mut 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(&mut 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, world: &mut World) -> Result<EntityObject> {
let mut flag = world.clone_without_components(entity);
flag.clone_component_from::<Draw>(entity)?;
flag.clone_component_from::<Location>(entity)?;
flag.clone_component_from::<BoundingBox>(entity)?;
Location::setup(flag.multi_mut())?;
Ok(flag)
}
fn clone(&self, world: &mut World) -> Result<Self> {
let flag = Self::clone_entity(&self.flag, world)?;
let area = Self::clone_entity(&self.area, world)?;
Ok(Self { flag, area })
}
fn set_position(&mut self, position: Vector3<f32>) {
self.flag
.get_component_mut::<Location>()
.unwrap()
.set_position(position);
self.area
.get_component_mut::<Location>()
.unwrap()
.set_position(position);
}
fn set_radius(&mut self, radius: f32) {
self.area
.get_component_mut::<Location>()
.unwrap()
.set_scale(vec3(radius, radius, 1.0));
}
pub fn add(self, world: &mut World) -> Result<SpawnMarker> {
let flag = world.add_entity(self.flag)?;
let area = world.add_entity(self.area)?;
Ok(SpawnMarker { flag, area })
}
}
pub struct SpawnMarker {
flag: Entity,
area: Entity,
}
impl SpawnMarker {
fn set_radius(&self, world: &mut World, radius: f32) -> Result<()> {
let flag_object = world.entity_mut(self.area)?;
flag_object
.get_component_mut::<Location>()?
.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<f32>,
) -> Option<Entity>;
fn remove(&mut self, coordinate: &Coordinate) -> Option<Entity>;
}
impl AlterEntities for HashMap<Coordinate, Entity> {
fn get(&self, coordinate: &Coordinate) -> Option<&Entity> {
self.get(coordinate)
}
fn insert(
&mut self,
coordinate: Coordinate,
entity: Entity,
_: Vector3<f32>,
) -> Option<Entity> {
self.insert(coordinate, entity)
}
fn remove(&mut self, coordinate: &Coordinate) -> Option<Entity> {
self.remove(coordinate)
}
}
impl AlterEntities for HashMap<Coordinate, (Entity, Vector3<f32>)> {
fn get(&self, coordinate: &Coordinate) -> Option<&Entity> {
self.get(coordinate).map(|(e, _)| e)
}
fn insert(
&mut self,
coordinate: Coordinate,
entity: Entity,
position: Vector3<f32>,
) -> Option<Entity> {
self.insert(coordinate, (entity, position)).map(|(e, _)| e)
}
fn remove(&mut self, coordinate: &Coordinate) -> Option<Entity> {
self.remove(coordinate).map(|(e, _)| e)
}
}
impl SpawnMarker {
pub fn remove(&self, world: &mut World) -> Result<()> {
world.remove_entity(self.flag)?;
world.remove_entity(self.area)?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct MapNPCSpawnInfo {
pub coordinate: Coordinate,
pub position: Vector2<f32>,
pub radius: f32,
pub min_count: u32,
pub max_count: u32,
pub normal_npc: Option<String>,
pub elite_npc: Option<String>,
}
#[derive(Clone, Debug)]
pub struct MapBossSpawnInfo {
pub coordinate: Coordinate,
pub position: Vector2<f32>,
pub name: Option<String>,
}
impl MapBossSpawnInfo {
fn setup_flag(
marker: &SpawnMarkerEntities,
world: &mut World,
position: Vector3<f32>,
) -> Result<EntityObject> {
let mut flag = SpawnMarkerEntities::clone_entity(&marker.flag, world)?;
let location = flag.get_component_mut::<Location>()?;
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<Coordinate, Entity>,
// textures
pub textures_index: HashMap<String, u32>,
pub textures_info: HashMap<u32, OrderedElement<Arc<Image>>>,
// <(Coordinate), TextureIndex>
pub chunk_handles: HashMap<(u32, u32), Entity>,
pub spawn_locations: HashMap<Coordinate, (Entity, Vector3<f32>)>,
pub show_spawn_locations: bool,
pub leave_locations: HashMap<Coordinate, Entity>,
pub npc_spawn_areas: Vec<(MapNPCSpawnInfo, Option<SpawnMarker>)>,
pub boss_spawns: Vec<(MapBossSpawnInfo, Option<Entity>)>,
pub show_npc_spawns: bool,
pub npc_spawn_marker: Option<SpawnMarkerEntities>,
}
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<Entity> {
self.entities.get(&Coordinate { x, y }).copied()
}
pub fn find_spawn_marker(&self, x: u32, y: u32) -> Option<Entity> {
self.spawn_locations
.get(&Coordinate { x, y })
.map(|(e, _)| *e)
}
pub fn find_leave_marker(&self, x: u32, y: u32) -> Option<Entity> {
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<Entity> {
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<F>(&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, world: &mut World) -> Result<()> {
self.execute_on_entity(x, y, |entity| {
let entity_object = world.entity_mut(entity)?;
let location = entity_object.get_component_mut::<Location>()?;
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,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: cgmath::Vector2<f32>,
rotation: impl Into<Rad<f32>>,
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(),
world,
)?;
Ok(())
}
pub fn change_boss(
&mut self,
async_db: &AsyncDBAccess,
position: Vector2<f32>,
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,
world: &mut World,
position: Vector2<f32>,
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(world, radius)?;
async_db.add(move |sql| sql.update_npc_spawn_radius(&coordinate, radius))?;
}
}
}
Ok(())
}
pub fn disable_spawns(&mut self, world: &mut World) -> Result<()> {
if self.show_spawn_locations {
self.show_spawn_locations = false;
for (spawn_entity, _) in self.spawn_locations.values() {
world.remove_entity(*spawn_entity)?;
}
}
Ok(())
}
pub fn spawn_locations(&self) -> Vec<Vector3<f32>> {
let locations = self
.spawn_locations
.values()
.map(|(_, location)| *location)
.collect();
locations
}
pub fn leave_markers(&self) -> Vec<Entity> {
self.leave_locations.values().copied().collect()
}
pub fn set_leave_location(
&mut self,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: cgmath::Vector2<f32>,
rotation: impl Into<Rad<f32>>,
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(),
world,
)
}
pub fn toggle_npc_spawns(&mut self, world: &mut World) -> 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(world)?;
// set position
new_marker.set_position(spawn_info.position.extend(height));
new_marker.set_radius(spawn_info.radius);
*marker = Some(new_marker.add(world)?);
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(world)?;
}
}
}
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<Entity> = match flag_marker {
Some(marker) => Some(*marker),
None => match &self.npc_spawn_marker {
Some(global_marker) => {
let flag = MapBossSpawnInfo::setup_flag(
global_marker,
world,
spawn_info.position.extend(height),
)?;
*flag_marker = Some(world.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 {
world.remove_entity(marker)?;
}
}
}
Ok(())
}
pub fn set_npc_spawn(
&mut self,
world: &mut World,
position: cgmath::Vector2<f32>,
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(world)?;
// set position
marker.set_position(position.extend(self.get_height(position.x, position.y)));
marker.set_radius(radius);
Some(marker.add(world)?)
}
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,
world: &mut World,
position: cgmath::Vector2<f32>,
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(world)?;
}
}
Ok(())
}
pub fn set_boss_spawn(
&mut self,
world: &mut World,
position: cgmath::Vector2<f32>,
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,
world,
position.extend(self.get_height(position.x, position.y)),
)?;
Some(world.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,
world: &mut World,
position: cgmath::Vector2<f32>,
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 {
world.remove_entity(marker)?;
}
}
Ok(())
}
fn set_entity_alike<T: AlterEntities>(
entity_opt: Option<(String, EntityObject)>,
position: cgmath::Vector2<f32>,
rotation: impl Into<Rad<f32>>,
async_db: &AsyncDBAccess,
entities: &mut T,
(width, height): (u32, u32),
surface: &Surface,
table_name: EntityDBType,
world: &mut World,
) -> 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::<Location>()
.unwrap()
.update_raw(position_3d, rad_rotation);
let handle = world.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))
})?;
world.remove_entity(entity)?;
return Ok(());
}
}
}
Ok(())
}
pub fn set_entity(
&mut self,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: cgmath::Vector2<f32>,
rotation: impl Into<Rad<f32>>,
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(),
world,
)
}
/// 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<Image>`
pub fn set_texture(
&mut self,
x: u32,
y: u32,
texture: &Arc<Image>,
async_db: &AsyncDBAccess,
) -> Result<bool> {
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(())
}
}