1211 lines
34 KiB
Rust
1211 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::{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<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(
|
|
engine: &Engine,
|
|
entity_manager: &mut impl AssetLoader,
|
|
flag_name: &str,
|
|
area_name: &str,
|
|
) -> Result<Self> {
|
|
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<EntityObject> {
|
|
let mut flag = entity.clone_without_components(engine);
|
|
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, engine: &Engine) -> Result<Self> {
|
|
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<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, scene: &mut impl SceneEntities) -> Result<SpawnMarker> {
|
|
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::<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, 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<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,
|
|
engine: &Engine,
|
|
position: Vector3<f32>,
|
|
) -> Result<EntityObject> {
|
|
let mut flag = SpawnMarkerEntities::clone_entity(&marker.flag, engine)?;
|
|
|
|
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, 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::<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,
|
|
scene: &mut impl SceneEntities,
|
|
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(),
|
|
scene,
|
|
)?;
|
|
|
|
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,
|
|
scene_contents: &mut impl SceneEntities,
|
|
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(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<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,
|
|
scene: &mut impl SceneEntities,
|
|
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(),
|
|
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<Entity> = 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<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(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<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(scene)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn set_boss_spawn(
|
|
&mut self,
|
|
engine: &Engine,
|
|
scene: &mut impl SceneEntities,
|
|
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,
|
|
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<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 {
|
|
scene.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,
|
|
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::<Location>()
|
|
.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<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(),
|
|
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<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(())
|
|
}
|
|
}
|