use std::{ collections::{hash_map::Iter, HashMap}, path::Path, }; use crate::game::content::prelude::*; use crate::game::game::GameHandle; use anyhow::Result; use assetpath::AssetPath; use cgmath::{vec3, Deg, InnerSpace, Vector3, Zero}; use engine::prelude::*; use entity_manager::*; use rpg_components::{ components::{ attributes::Attributes, character_status::CharacterStatus, level::Level, npc_type::{NPCBoss, NPCElite, NPCNormal, NPCType}, statistics::Statistics, }, items::Rarities, }; const NPC_BASE_STATS: u32 = 2; create_settings_section!( NPCMetaInfo, "Meta", { entity_file: String, name: String, movement_speed: f32, aggro_radius: f32, deaggro_radius: f32, scale: f32, mob_type: NPCType, strength_per_level: f32, intelligence_per_level: f32, agility_per_level: f32, } ); create_settings_section!( AbilitySlot0, "AbilitySlot_0", { used: bool, name: String, rarity: Rarities, level: f32, [addons: String], } ); create_settings_section!( AbilitySlot1, "AbilitySlot_1", { used: bool, name: String, rarity: Rarities, level: f32, [addons: String], } ); create_settings_section!( AbilitySlot2, "AbilitySlot_2", { used: bool, name: String, rarity: Rarities, level: f32, [addons: String], } ); create_settings_section!( AbilitySlot3, "AbilitySlot_3", { used: bool, name: String, rarity: Rarities, level: f32, [addons: String], } ); pub trait NPCAbilitySlot { fn used(&mut self) -> &mut bool; fn name(&mut self) -> &mut String; fn rarity(&mut self) -> &mut Rarities; fn level(&mut self) -> &mut f32; fn addons(&mut self) -> &mut [String]; } impl NPCAbilitySlot for AbilitySlot0 { fn used(&mut self) -> &mut bool { &mut self.used } fn name(&mut self) -> &mut String { &mut self.name } fn rarity(&mut self) -> &mut Rarities { &mut self.rarity } fn level(&mut self) -> &mut f32 { &mut self.level } fn addons(&mut self) -> &mut [String] { &mut self.addons } } impl NPCAbilitySlot for AbilitySlot1 { fn used(&mut self) -> &mut bool { &mut self.used } fn name(&mut self) -> &mut String { &mut self.name } fn rarity(&mut self) -> &mut Rarities { &mut self.rarity } fn level(&mut self) -> &mut f32 { &mut self.level } fn addons(&mut self) -> &mut [String] { &mut self.addons } } impl NPCAbilitySlot for AbilitySlot2 { fn used(&mut self) -> &mut bool { &mut self.used } fn name(&mut self) -> &mut String { &mut self.name } fn rarity(&mut self) -> &mut Rarities { &mut self.rarity } fn level(&mut self) -> &mut f32 { &mut self.level } fn addons(&mut self) -> &mut [String] { &mut self.addons } } impl NPCAbilitySlot for AbilitySlot3 { fn used(&mut self) -> &mut bool { &mut self.used } fn name(&mut self) -> &mut String { &mut self.name } fn rarity(&mut self) -> &mut Rarities { &mut self.rarity } fn level(&mut self) -> &mut f32 { &mut self.level } fn addons(&mut self) -> &mut [String] { &mut self.addons } } create_settings_container!( NPCSettings, { meta: NPCMetaInfo, ability_0: AbilitySlot0, ability_1: AbilitySlot1, ability_2: AbilitySlot2, ability_3: AbilitySlot3, } ); pub struct NPCFactory { elite_npcs: HashMap, normal_npcs: HashMap, boss_npcs: HashMap, } impl NPCFactory { pub fn new(npc_directory: AssetPath) -> Result { let files = search_dir_recursively(&npc_directory.full_path(), ".npc")?; let mut elite_npcs = HashMap::new(); let mut normal_npcs = HashMap::new(); let mut boss_npcs = HashMap::new(); for npc_file in files { let npc_name = Path::new(&npc_file.full_path()) .file_stem() .unwrap() .to_str() .unwrap() .to_string(); let npc_setting = NPCSettings::load(npc_file)?; match npc_setting.meta.mob_type { NPCType::Normal => normal_npcs.insert(npc_name, npc_setting), NPCType::Elite => elite_npcs.insert(npc_name, npc_setting), NPCType::Boss => boss_npcs.insert(npc_name, npc_setting), }; } Ok(Self { elite_npcs, normal_npcs, boss_npcs, }) } pub fn npc(&self, name: &String, npc_type: impl Into) -> Option<&NPCSettings> { match npc_type.into() { NPCType::Normal => self.normal_npcs.get(name), NPCType::Elite => self.elite_npcs.get(name), NPCType::Boss => self.boss_npcs.get(name), } } pub fn npcs(&self, npc_type: impl Into) -> Iter<'_, String, NPCSettings> { match npc_type.into() { NPCType::Normal => self.normal_npcs.iter(), NPCType::Elite => self.elite_npcs.iter(), NPCType::Boss => self.boss_npcs.iter(), } } fn find_setting(&self, npc: &String) -> Result<&NPCSettings> { self.normal_npcs .get(npc) .or(self.elite_npcs.get(npc)) .or(self.boss_npcs.get(npc)) .ok_or(anyhow::Error::msg(format!("NPC file not found {}", npc))) } pub fn create_npc( &self, entity_manager: &mut EntityManager, game_handle: &GameHandle, npc: &String, level: u32, position: Vector3, ) -> Result { let game = game_handle.upgrade(); let npc_settings = self.find_setting(npc)?; let mut entity_object = entity_manager.load_entity(game.engine().assets(), &npc_settings.meta.entity_file)?; // ========== create components ========== // location let location = Location::new_and_setup(&mut entity_object)?; location.update_raw(position, Deg(Random::range_f32(0.0, 360.0))); // scale let scale = npc_settings.meta.scale; location.set_scale(vec3(scale, scale, scale)); let hitbox = entity_object.get_component_mut::()?; hitbox.set_radius(hitbox.radius() * scale); hitbox.set_height(hitbox.height() * scale); // movement let movement = Movement::new( npc_settings.meta.movement_speed, entity_object.get_component_mut::