diff --git a/korangar/src/input/mode.rs b/korangar/src/input/mode.rs index 0d56c036..7ca52c26 100644 --- a/korangar/src/input/mode.rs +++ b/korangar/src/input/mode.rs @@ -9,8 +9,8 @@ use crate::graphics::Texture; use crate::interface::application::InterfaceSettings; use crate::interface::resource::{ItemSource, SkillSource}; use crate::inventory::Skill; -use crate::loaders::{ResourceMetadata, Sprite}; -use crate::world::{Actions, SpriteAnimationState}; +use crate::loaders::Sprite; +use crate::world::{Actions, ResourceMetadata, SpriteAnimationState}; #[derive(Default)] pub enum MouseInputMode { @@ -42,7 +42,10 @@ impl MouseInputMode { pub fn grabbed(&self) -> Option { match self { - MouseInputMode::MoveItem(_, item) => Some(Grabbed::Texture(item.metadata.texture.clone())), + MouseInputMode::MoveItem(_, item) => match item.metadata.texture.as_ref() { + None => None, + Some(texture) => Some(Grabbed::Texture(texture.clone())), + }, MouseInputMode::MoveSkill(_, skill) => Some(Grabbed::Action( skill.sprite.clone(), skill.actions.clone(), diff --git a/korangar/src/interface/elements/containers/equipment.rs b/korangar/src/interface/elements/containers/equipment.rs index 670150f6..bb35d94a 100644 --- a/korangar/src/interface/elements/containers/equipment.rs +++ b/korangar/src/interface/elements/containers/equipment.rs @@ -15,8 +15,8 @@ use crate::interface::elements::ItemBox; use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::resource::ItemSource; use crate::interface::theme::InterfaceTheme; -use crate::loaders::ResourceMetadata; use crate::renderer::InterfaceRenderer; +use crate::world::ResourceMetadata; pub struct EquipmentContainer { items: PlainRemote>>, diff --git a/korangar/src/interface/elements/containers/inventory.rs b/korangar/src/interface/elements/containers/inventory.rs index c50f23c3..70b209a6 100644 --- a/korangar/src/interface/elements/containers/inventory.rs +++ b/korangar/src/interface/elements/containers/inventory.rs @@ -12,8 +12,8 @@ use crate::interface::elements::ItemBox; use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::resource::{ItemSource, Move, PartialMove}; use crate::interface::theme::InterfaceTheme; -use crate::loaders::ResourceMetadata; use crate::renderer::InterfaceRenderer; +use crate::world::ResourceMetadata; pub struct InventoryContainer { items: PlainRemote>>, diff --git a/korangar/src/interface/elements/miscellanious/item.rs b/korangar/src/interface/elements/miscellanious/item.rs index 8d6c1ef9..0dfd6391 100644 --- a/korangar/src/interface/elements/miscellanious/item.rs +++ b/korangar/src/interface/elements/miscellanious/item.rs @@ -12,8 +12,9 @@ use crate::interface::application::InterfaceSettings; use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::resource::{ItemSource, Move, PartialMove}; use crate::interface::theme::InterfaceTheme; -use crate::loaders::{FontSize, ResourceMetadata, Scaling}; +use crate::loaders::{FontSize, Scaling}; use crate::renderer::{InterfaceRenderer, SpriteRenderer}; +use crate::world::ResourceMetadata; #[derive(new)] pub struct ItemBox { @@ -100,9 +101,11 @@ impl Element for ItemBox { renderer.render_background(CornerRadius::uniform(5.0), background_color); - if let Some(item) = &self.item { + if let Some(item) = &self.item + && let Some(texture) = item.metadata.texture.as_ref() + { renderer.renderer.render_sprite( - item.metadata.texture.clone(), + texture.clone(), renderer.position, ScreenSize::uniform(30.0).scaled(Scaling::new(application.get_scaling_factor())), renderer.clip, diff --git a/korangar/src/interface/elements/shop/buy.rs b/korangar/src/interface/elements/shop/buy.rs index 3c61560f..49460726 100644 --- a/korangar/src/interface/elements/shop/buy.rs +++ b/korangar/src/interface/elements/shop/buy.rs @@ -12,8 +12,8 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::{ShopEntry, ShopEntryOperation}; use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::theme::InterfaceTheme; -use crate::loaders::ResourceMetadata; use crate::renderer::InterfaceRenderer; +use crate::world::ResourceMetadata; pub struct BuyContainer { items: PlainRemote>>, diff --git a/korangar/src/interface/elements/shop/buy_cart.rs b/korangar/src/interface/elements/shop/buy_cart.rs index e705b342..5e743127 100644 --- a/korangar/src/interface/elements/shop/buy_cart.rs +++ b/korangar/src/interface/elements/shop/buy_cart.rs @@ -15,8 +15,8 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::{ShopEntry, ShopEntryOperation}; use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::theme::InterfaceTheme; -use crate::loaders::ResourceMetadata; use crate::renderer::InterfaceRenderer; +use crate::world::ResourceMetadata; pub struct BuyCartContainer { cart: PlainTrackedState>>, diff --git a/korangar/src/interface/elements/shop/display.rs b/korangar/src/interface/elements/shop/display.rs index 1cf7d78a..e8117219 100644 --- a/korangar/src/interface/elements/shop/display.rs +++ b/korangar/src/interface/elements/shop/display.rs @@ -9,8 +9,9 @@ use crate::input::MouseInputMode; use crate::interface::application::InterfaceSettings; use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::theme::InterfaceTheme; -use crate::loaders::{FontSize, ResourceMetadata, Scaling}; +use crate::loaders::{FontSize, Scaling}; use crate::renderer::{InterfaceRenderer, SpriteRenderer}; +use crate::world::ResourceMetadata; pub trait ItemResourceProvider { fn get_resource_metadata(&self) -> &ResourceMetadata; @@ -97,14 +98,16 @@ where renderer.render_background(CornerRadius::uniform(5.0), background_color); - renderer.renderer.render_sprite( - self.item.get_resource_metadata().texture.clone(), - renderer.position, - ScreenSize::uniform(30.0).scaled(Scaling::new(application.get_scaling_factor())), - renderer.clip, - Color::WHITE, - false, - ); + if let Some(texture) = self.item.get_resource_metadata().texture.as_ref() { + renderer.renderer.render_sprite( + texture.clone(), + renderer.position, + ScreenSize::uniform(30.0).scaled(Scaling::new(application.get_scaling_factor())), + renderer.clip, + Color::WHITE, + false, + ); + } if let Some(quantity) = (self.get_quantity)(&self.item) { renderer.render_text( diff --git a/korangar/src/interface/elements/shop/sell.rs b/korangar/src/interface/elements/shop/sell.rs index f0227a16..edfb5835 100644 --- a/korangar/src/interface/elements/shop/sell.rs +++ b/korangar/src/interface/elements/shop/sell.rs @@ -12,8 +12,8 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::{ShopEntry, ShopEntryOperation}; use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::theme::InterfaceTheme; -use crate::loaders::ResourceMetadata; use crate::renderer::InterfaceRenderer; +use crate::world::ResourceMetadata; pub struct SellContainer { items: PlainRemote>>, diff --git a/korangar/src/interface/elements/shop/sell_cart.rs b/korangar/src/interface/elements/shop/sell_cart.rs index 83000ba5..d2e8a16b 100644 --- a/korangar/src/interface/elements/shop/sell_cart.rs +++ b/korangar/src/interface/elements/shop/sell_cart.rs @@ -16,8 +16,8 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::{ShopEntry, ShopEntryOperation}; use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::interface::theme::InterfaceTheme; -use crate::loaders::ResourceMetadata; use crate::renderer::InterfaceRenderer; +use crate::world::ResourceMetadata; pub struct SellCartContainer { cart: PlainTrackedState>>, diff --git a/korangar/src/interface/resource.rs b/korangar/src/interface/resource.rs index 2d5939fa..eb869d75 100644 --- a/korangar/src/interface/resource.rs +++ b/korangar/src/interface/resource.rs @@ -2,7 +2,7 @@ use korangar_networking::InventoryItem; use ragnarok_packets::{EquipPosition, HotbarSlot}; use crate::inventory::Skill; -use crate::loaders::ResourceMetadata; +use crate::world::ResourceMetadata; #[derive(Clone, Copy, Debug, PartialEq)] pub enum ItemSource { diff --git a/korangar/src/interface/windows/character/equipment.rs b/korangar/src/interface/windows/character/equipment.rs index b7be168a..53b801a7 100644 --- a/korangar/src/interface/windows/character/equipment.rs +++ b/korangar/src/interface/windows/character/equipment.rs @@ -9,7 +9,7 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::EquipmentContainer; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; -use crate::loaders::ResourceMetadata; +use crate::world::ResourceMetadata; #[derive(new)] pub struct EquipmentWindow { diff --git a/korangar/src/interface/windows/character/inventory.rs b/korangar/src/interface/windows/character/inventory.rs index 41fd718b..4d16489a 100644 --- a/korangar/src/interface/windows/character/inventory.rs +++ b/korangar/src/interface/windows/character/inventory.rs @@ -9,7 +9,7 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::InventoryContainer; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; -use crate::loaders::ResourceMetadata; +use crate::world::ResourceMetadata; #[derive(new)] pub struct InventoryWindow { diff --git a/korangar/src/interface/windows/shop/buy.rs b/korangar/src/interface/windows/shop/buy.rs index a8afc749..b4c3a9bc 100644 --- a/korangar/src/interface/windows/shop/buy.rs +++ b/korangar/src/interface/windows/shop/buy.rs @@ -9,7 +9,7 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::BuyContainer; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; -use crate::loaders::ResourceMetadata; +use crate::world::ResourceMetadata; #[derive(new)] pub struct BuyWindow { diff --git a/korangar/src/interface/windows/shop/buy_cart.rs b/korangar/src/interface/windows/shop/buy_cart.rs index d81ec4d1..f2d517c0 100644 --- a/korangar/src/interface/windows/shop/buy_cart.rs +++ b/korangar/src/interface/windows/shop/buy_cart.rs @@ -9,7 +9,7 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::BuyCartContainer; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; -use crate::loaders::ResourceMetadata; +use crate::world::ResourceMetadata; #[derive(new)] pub struct BuyCartWindow { diff --git a/korangar/src/interface/windows/shop/sell.rs b/korangar/src/interface/windows/shop/sell.rs index 5e408b08..13b2707e 100644 --- a/korangar/src/interface/windows/shop/sell.rs +++ b/korangar/src/interface/windows/shop/sell.rs @@ -9,7 +9,7 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::SellContainer; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; -use crate::loaders::ResourceMetadata; +use crate::world::ResourceMetadata; #[derive(new)] pub struct SellWindow { diff --git a/korangar/src/interface/windows/shop/sell_cart.rs b/korangar/src/interface/windows/shop/sell_cart.rs index 0e6b0c3f..1114bbf9 100644 --- a/korangar/src/interface/windows/shop/sell_cart.rs +++ b/korangar/src/interface/windows/shop/sell_cart.rs @@ -9,7 +9,7 @@ use crate::interface::application::InterfaceSettings; use crate::interface::elements::SellCartContainer; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; -use crate::loaders::ResourceMetadata; +use crate::world::ResourceMetadata; #[derive(new)] pub struct SellCartWindow { diff --git a/korangar/src/inventory/mod.rs b/korangar/src/inventory/mod.rs index 5190a4da..76201517 100644 --- a/korangar/src/inventory/mod.rs +++ b/korangar/src/inventory/mod.rs @@ -2,14 +2,17 @@ mod hotbar; mod skills; use std::cell::Ref; +use std::sync::Arc; use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState, TrackedStateExt, ValueState}; use korangar_networking::{InventoryItem, InventoryItemDetails, NoMetadata}; -use ragnarok_packets::{EquipPosition, InventoryIndex}; +use ragnarok_packets::{EquipPosition, InventoryIndex, ItemId}; pub use self::hotbar::Hotbar; pub use self::skills::{Skill, SkillTree}; -use crate::loaders::{ResourceMetadata, ScriptLoader, TextureLoader}; +use crate::graphics::Texture; +use crate::loaders::AsyncLoader; +use crate::world::{Library, ResourceMetadata}; #[derive(Default)] pub struct Inventory { @@ -17,16 +20,16 @@ pub struct Inventory { } impl Inventory { - pub fn fill(&mut self, texture_loader: &TextureLoader, script_loader: &ScriptLoader, items: Vec>) { + pub fn fill(&mut self, async_loader: &AsyncLoader, library: &Library, items: Vec>) { let items = items .into_iter() - .map(|item| script_loader.load_inventory_item_metadata(texture_loader, item)) + .map(|item| library.load_inventory_item_metadata(async_loader, item)) .collect(); self.items.set(items); } - pub fn add_item(&mut self, texture_loader: &TextureLoader, script_loader: &ScriptLoader, item: InventoryItem) { + pub fn add_item(&mut self, async_loader: &AsyncLoader, library: &Library, item: InventoryItem) { self.items.with_mut(|items| { if let Some(found_item) = items.iter_mut().find(|inventory_item| inventory_item.index == item.index) { let InventoryItemDetails::Regular { amount, .. } = &mut found_item.details else { @@ -39,7 +42,7 @@ impl Inventory { *amount += added_amount; } else { - let item = script_loader.load_inventory_item_metadata(texture_loader, item); + let item = library.load_inventory_item_metadata(async_loader, item); items.push(item); } @@ -48,6 +51,16 @@ impl Inventory { }); } + pub fn update_item_sprite(&mut self, item_id: ItemId, texture: Arc) { + self.items.with_mut(|items| { + items.iter_mut().filter(|item| item.item_id == item_id).for_each(|item| { + item.metadata.texture = Some(texture.clone()); + }); + + ValueState::Mutated(()) + }) + } + pub fn remove_item(&mut self, index: InventoryIndex, remove_amount: u16) { self.items.with_mut(|items| { let position = items.iter().position(|item| item.index == index).expect("item not in inventory"); diff --git a/korangar/src/loaders/async/mod.rs b/korangar/src/loaders/async/mod.rs index 150c3541..aac30cf3 100644 --- a/korangar/src/loaders/async/mod.rs +++ b/korangar/src/loaders/async/mod.rs @@ -6,23 +6,32 @@ use hashbrown::HashMap; use korangar_debug::logging::print_debug; #[cfg(feature = "debug")] use korangar_util::texture_atlas::AtlasAllocation; -use ragnarok_packets::{EntityId, TilePosition}; +use ragnarok_packets::{EntityId, ItemId, TilePosition}; use rayon::{ThreadPool, ThreadPoolBuilder}; +use crate::graphics::Texture; use crate::loaders::error::LoadError; -use crate::loaders::{ActionLoader, AnimationLoader, MapLoader, ModelLoader, SpriteLoader, TextureLoader}; +use crate::loaders::{ActionLoader, AnimationLoader, ImageType, MapLoader, ModelLoader, SpriteLoader, TextureLoader}; #[cfg(feature = "debug")] use crate::threads; use crate::world::{AnimationData, EntityType, Map}; +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +pub enum ItemLocation { + Inventory, + Shop, +} + #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub enum LoaderId { AnimationData(EntityId), + ItemSprite(ItemId), Map(String), } pub enum LoadableResource { AnimationData(Arc), + ItemSprite { texture: Arc, location: ItemLocation }, Map { map: Box, player_position: TilePosition }, } @@ -76,19 +85,61 @@ impl AsyncLoader { } } - pub fn request_animation_data_load(&self, entity_id: EntityId, entity_type: EntityType, entity_part_files: Vec) { - let sprite_loader = self.sprite_loader.clone(); - let action_loader = self.action_loader.clone(); - let animation_loader = self.animation_loader.clone(); - - self.request_load(LoaderId::AnimationData(entity_id), move || { - let animation_data = match animation_loader.get(&entity_part_files) { - None => animation_loader.load(&sprite_loader, &action_loader, entity_type, &entity_part_files)?, - Some(animation_data) => animation_data, - }; + #[must_use] + pub fn request_animation_data_load( + &self, + entity_id: EntityId, + entity_type: EntityType, + entity_part_files: Vec, + ) -> Option> { + match self.animation_loader.get(&entity_part_files) { + Some(animation_data) => Some(animation_data), + None => { + let sprite_loader = self.sprite_loader.clone(); + let action_loader = self.action_loader.clone(); + let animation_loader = self.animation_loader.clone(); + + self.request_load(LoaderId::AnimationData(entity_id), move || { + let animation_data = match animation_loader.get(&entity_part_files) { + Some(animation_data) => animation_data, + None => animation_loader.load(&sprite_loader, &action_loader, entity_type, &entity_part_files)?, + }; + Ok(LoadableResource::AnimationData(animation_data)) + }); + + None + } + } + } - Ok(LoadableResource::AnimationData(animation_data)) - }); + #[must_use] + pub fn request_item_sprite_load( + &self, + item_location: ItemLocation, + item_id: ItemId, + path: &str, + image_type: ImageType, + ) -> Option> { + match self.texture_loader.get(path, image_type) { + Some(texture) => Some(texture), + None => { + let texture_loader = self.texture_loader.clone(); + let path = path.to_string(); + + self.request_load(LoaderId::ItemSprite(item_id), move || { + let texture = match texture_loader.get(&path, image_type) { + None => texture_loader.load(&path, image_type)?, + Some(texture) => texture, + }; + Ok(LoadableResource::ItemSprite { + texture, + location: item_location, + }) + }); + + None + } + } } pub fn request_map_load( diff --git a/korangar/src/loaders/mod.rs b/korangar/src/loaders/mod.rs index dc237d4e..7c8c25c6 100644 --- a/korangar/src/loaders/mod.rs +++ b/korangar/src/loaders/mod.rs @@ -9,7 +9,6 @@ mod font; mod gamefile; mod map; mod model; -mod script; mod server; mod sprite; mod texture; @@ -22,7 +21,6 @@ pub use self::gamefile::*; pub use self::map::{MapLoader, MAP_TILE_SIZE}; pub use self::model::*; pub use self::r#async::*; -pub use self::script::{ResourceMetadata, ScriptLoader}; pub use self::server::{load_client_info, ClientInfo, ServiceId}; pub use self::sprite::*; pub use self::texture::{ImageType, TextureAtlasFactory, TextureLoader}; diff --git a/korangar/src/loaders/script/mod.rs b/korangar/src/loaders/script/mod.rs deleted file mode 100644 index 3cca4a62..00000000 --- a/korangar/src/loaders/script/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::sync::Arc; - -use korangar_networking::{InventoryItem, NoMetadata, ShopItem}; -use korangar_util::FileLoader; -use mlua::Lua; -use ragnarok_packets::ItemId; - -use super::{ImageType, TextureLoader}; -use crate::graphics::Texture; -use crate::loaders::GameFileLoader; - -#[derive(Debug, Clone)] -pub struct ResourceMetadata { - pub texture: Arc, - pub name: String, -} - -pub struct ScriptLoader { - state: Lua, -} - -impl ScriptLoader { - pub fn new(game_file_loader: &GameFileLoader) -> mlua::Result { - let state = Lua::new(); - - let data = game_file_loader - .get("data\\luafiles514\\lua files\\datainfo\\jobidentity.lub") - .unwrap(); - state.load(&data).exec()?; - - let data = game_file_loader - .get("data\\luafiles514\\lua files\\datainfo\\iteminfo.lub") - .unwrap(); - state.load(&data).exec()?; - - let data = game_file_loader - .get("data\\luafiles514\\lua files\\datainfo\\npcidentity.lub") - .unwrap(); - state.load(&data).exec()?; - - let job_id_function = r#" -function get_job_name_from_id(id) - for k,v in pairs(JTtbl) do - if v==id then - - if string.sub(k, 1, 5) == "JT_G_" then - return string.sub(k, 6) - end - - if string.sub(k, 1, 6) == "JT_C1_" then - return string.sub(k, 7) - end - - if string.sub(k, 1, 6) == "JT_C2_" then - return string.sub(k, 7) - end - - if string.sub(k, 1, 6) == "JT_C3_" then - return string.sub(k, 7) - end - - if string.sub(k, 1, 6) == "JT_C4_" then - return string.sub(k, 7) - end - - if string.sub(k, 1, 6) == "JT_C5_" then - return string.sub(k, 7) - end - - return string.sub(k, 4) - end - end - - for k,v in pairs(jobtbl) do - if v==id then - if string.sub(k, 1, 5) == "JT_G_" then - return string.sub(k, 6) - end - return string.sub(k, 4) - end - end - - return "1_f_maria" - --return nil -end -"#; - - state.load(job_id_function).exec()?; - - Ok(Self { state }) - } - - // TODO: move this to a different class that utilizes the script loader - pub fn get_job_name_from_id(&self, job_id: usize) -> String { - use mlua::prelude::*; - use mlua::Function; - - let globals = self.state.globals(); - - let print: Function = globals.get("get_job_name_from_id").unwrap(); - print - .call::(job_id) - .unwrap() - .to_str() - .unwrap() - .replace("CHONCHON", "chocho") // TODO: find a way to do this - // properly - } - - // TODO: move this to a different class that utilizes the script loader - fn get_item_name_from_id(&self, item_id: ItemId, is_identified: bool) -> String { - use mlua::prelude::*; - - let globals = self.state.globals(); - let lookup_name = match is_identified { - true => "identifiedDisplayName", - false => "unidentifiedDisplayName", - }; - - globals - .get::("tbl") - .unwrap() - .get::(item_id.0) - .map(|table| table.get::(lookup_name).unwrap().to_str().unwrap().to_owned()) - .unwrap_or_else(|_| "NOTFOUND".to_owned()) - } - - // TODO: move this to a different class that utilizes the script loader - fn get_item_resource_from_id(&self, item_id: ItemId, is_identified: bool) -> String { - use mlua::prelude::*; - - let globals = self.state.globals(); - let lookup_name = match is_identified { - true => "identifiedResourceName", - false => "unidentifiedResourceName", - }; - - globals - .get::("tbl") - .unwrap() - .get::(item_id.0) - .map(|table| table.get::(lookup_name).unwrap().to_str().unwrap().to_owned()) - .unwrap_or_else(|_| "»ç°ú".to_owned()) - } - - pub fn load_inventory_item_metadata( - &self, - texture_loader: &TextureLoader, - item: InventoryItem, - ) -> InventoryItem { - let is_identified = item.is_identifed(); - - let resource_name = self.get_item_resource_from_id(item.item_id, is_identified); - let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp"); - let texture = texture_loader.get_or_load(&full_path, ImageType::Color).unwrap(); - let name = self.get_item_name_from_id(item.item_id, is_identified); - - let metadata = ResourceMetadata { texture, name }; - - InventoryItem { metadata, ..item } - } - - pub fn load_market_item_metadata(&self, texture_loader: &TextureLoader, item: ShopItem) -> ShopItem { - let resource_name = self.get_item_resource_from_id(item.item_id, true); - let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp"); - let texture = texture_loader.get_or_load(&full_path, ImageType::Color).unwrap(); - let name = self.get_item_name_from_id(item.item_id, true); - - let metadata = ResourceMetadata { texture, name }; - - ShopItem { metadata, ..item } - } -} diff --git a/korangar/src/loaders/texture/mod.rs b/korangar/src/loaders/texture/mod.rs index ff36f6f5..54b1680c 100644 --- a/korangar/src/loaders/texture/mod.rs +++ b/korangar/src/loaders/texture/mod.rs @@ -182,7 +182,7 @@ impl TextureLoader { Arc::new(texture) } - fn load(&self, path: &str, image_type: ImageType) -> Result, LoadError> { + pub fn load(&self, path: &str, image_type: ImageType) -> Result, LoadError> { let texture = match image_type { ImageType::Color => { let (texture_data, transparent) = self.load_texture_data(path, false)?; @@ -345,6 +345,11 @@ impl TextureLoader { Ok(image_buffer) } + pub fn get(&self, path: &str, image_type: ImageType) -> Option> { + let mut lock = self.cache.lock().unwrap(); + lock.get(&(path.into(), image_type)).cloned() + } + pub fn get_or_load(&self, path: &str, image_type: ImageType) -> Result, LoadError> { let mut lock = self.cache.lock().unwrap(); match lock.get(&(path.into(), image_type)) { diff --git a/korangar/src/main.rs b/korangar/src/main.rs index 780da454..b87ce7c2 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -55,7 +55,7 @@ use korangar_debug::profile_block; use korangar_debug::profiling::Profiler; use korangar_interface::application::{Application, FocusState, FontSizeTrait, PositionTraitExt}; use korangar_interface::state::{ - MappedRemote, PlainTrackedState, Remote, TrackedState, TrackedStateExt, TrackedStateTake, TrackedStateVec, + MappedRemote, PlainTrackedState, Remote, TrackedState, TrackedStateExt, TrackedStateTake, TrackedStateVec, ValueState, }; use korangar_interface::Interface; use korangar_networking::{ @@ -161,15 +161,14 @@ struct Client { networking_system: NetworkingSystem, action_loader: Arc, - animation_loader: Arc, async_loader: Arc, effect_loader: Arc, font_loader: Rc>, map_loader: Arc, model_loader: Arc, - script_loader: Rc, sprite_loader: Arc, texture_loader: Arc, + library: Library, interface_renderer: InterfaceRenderer, bottom_interface_renderer: GameInterfaceRenderer, @@ -388,8 +387,8 @@ impl Client { let effect_loader = Arc::new(EffectLoader::new(game_file_loader.clone())); let animation_loader = Arc::new(AnimationLoader::new()); - let script_loader = Rc::new(ScriptLoader::new(&game_file_loader).unwrap_or_else(|_| { - // The scrip loader not being created correctly means that the lua files were + let library = Library::new(&game_file_loader).unwrap_or_else(|_| { + // The library not being created correctly means that the lua files were // not valid. It's possible that the archive was copied from a // different machine with a different architecture, so the one thing // we can try is generating it again. @@ -403,8 +402,8 @@ impl Client { game_file_loader.remove_patched_lua_files(); game_file_loader.load_patched_lua_files(); - ScriptLoader::new(&game_file_loader).unwrap() - })); + Library::new(&game_file_loader).unwrap() + }); let async_loader = Arc::new(AsyncLoader::new( action_loader.clone(), @@ -594,15 +593,14 @@ impl Client { packet_history_callback, networking_system, action_loader, - animation_loader, async_loader, effect_loader, font_loader, map_loader, model_loader, - script_loader, sprite_loader, texture_loader, + library, interface_renderer, bottom_interface_renderer, middle_interface_renderer, @@ -964,16 +962,13 @@ impl Client { let entity_id = player.get_entity_id(); let entity_type = player.get_entity_type(); - let entity_part_files = player.get_entity_part_files(&self.script_loader); + let entity_part_files = player.get_entity_part_files(&self.library); - match self.animation_loader.get(&entity_part_files) { - Some(animation_data) => { - player.set_animation_data(animation_data); - } - None => { - self.async_loader - .request_animation_data_load(entity_id, entity_type, entity_part_files); - } + if let Some(animation_data) = self + .async_loader + .request_animation_data_load(entity_id, entity_type, entity_part_files) + { + player.set_animation_data(animation_data); } self.entities.push(player); @@ -1023,21 +1018,18 @@ impl Client { let entity_id = npc.get_entity_id(); let entity_type = npc.get_entity_type(); - let entity_part_files = npc.get_entity_part_files(&self.script_loader); + let entity_part_files = npc.get_entity_part_files(&self.library); // Sometimes (like after a job change) the server will tell the client // that a new entity appeared, even though it was already on screen. So // to prevent the entity existing twice, we remove the old one. self.entities.retain(|entity| entity.get_entity_id() != entity_id); - match self.animation_loader.get(&entity_part_files) { - Some(animation_data) => { - npc.set_animation_data(animation_data); - } - None => { - self.async_loader - .request_animation_data_load(entity_id, entity_type, entity_part_files); - } + if let Some(animation_data) = + self.async_loader + .request_animation_data_load(entity_id, entity_type, entity_part_files) + { + npc.set_animation_data(animation_data); } self.entities.push(npc); @@ -1165,10 +1157,10 @@ impl Client { } NetworkEvent::RemoveQuestEffect(entity_id) => self.particle_holder.remove_quest_icon(entity_id), NetworkEvent::SetInventory { items } => { - self.player_inventory.fill(&self.texture_loader, &self.script_loader, items); + self.player_inventory.fill(&self.async_loader, &self.library, items); } NetworkEvent::IventoryItemAdded { item } => { - self.player_inventory.add_item(&self.texture_loader, &self.script_loader, item); + self.player_inventory.add_item(&self.async_loader, &self.library, item); // TODO: Update the selling items. If you pick up an item // that you already have the sell window @@ -1202,11 +1194,13 @@ impl Client { entity.set_job(job_id as usize); - self.async_loader.request_animation_data_load( + if let Some(animation_data) = self.async_loader.request_animation_data_load( entity.get_entity_id(), entity.get_entity_type(), - entity.get_entity_part_files(&self.script_loader), - ); + entity.get_entity_part_files(&self.library), + ) { + entity.set_animation_data(animation_data); + } } NetworkEvent::ChangeHair { account_id, hair_id } => { let entity = self @@ -1217,11 +1211,13 @@ impl Client { entity.set_hair(hair_id as usize); - self.async_loader.request_animation_data_load( + if let Some(animation_data) = self.async_loader.request_animation_data_load( entity.get_entity_id(), entity.get_entity_type(), - entity.get_entity_part_files(&self.script_loader), - ); + entity.get_entity_part_files(&self.library), + ) { + entity.set_animation_data(animation_data); + } } NetworkEvent::LoggedOut => { self.networking_system.disconnect_from_map_server(); @@ -1341,7 +1337,7 @@ impl Client { self.shop_items.mutate(|shop_items| { *shop_items = items .into_iter() - .map(|item| self.script_loader.load_market_item_metadata(&self.texture_loader, item)) + .map(|item| self.library.load_shop_item_metadata(&self.async_loader, item)) .collect() }); @@ -1846,6 +1842,21 @@ impl Client { entity.set_animation_data(animation_data); } } + (LoaderId::ItemSprite(item_id), LoadableResource::ItemSprite { texture, location }) => match location { + ItemLocation::Inventory => { + self.player_inventory.update_item_sprite(item_id, texture); + } + ItemLocation::Shop => { + self.shop_items.mutate(|items| { + items + .iter_mut() + .filter(|item| item.item_id == item_id) + .for_each(|item| item.metadata.texture = Some(texture.clone())); + + ValueState::Mutated(()) + }); + } + }, (LoaderId::Map(..), LoadableResource::Map { map, player_position }) => { let map = self.map.insert(map); diff --git a/korangar/src/world/entity/mod.rs b/korangar/src/world/entity/mod.rs index 97a4ae5d..dd0128d7 100644 --- a/korangar/src/world/entity/mod.rs +++ b/korangar/src/world/entity/mod.rs @@ -22,13 +22,13 @@ use crate::interface::application::InterfaceSettings; use crate::interface::layout::{ScreenPosition, ScreenSize}; use crate::interface::theme::GameTheme; use crate::interface::windows::WindowCache; -use crate::loaders::{GameFileLoader, ScriptLoader}; +use crate::loaders::GameFileLoader; use crate::renderer::GameInterfaceRenderer; #[cfg(feature = "debug")] use crate::renderer::MarkerRenderer; #[cfg(feature = "debug")] use crate::world::MarkerIdentifier; -use crate::world::{ActionEvent, AnimationActionType, AnimationData, AnimationState, Camera, Map}; +use crate::world::{ActionEvent, AnimationActionType, AnimationData, AnimationState, Camera, Library, Map}; #[cfg(feature = "debug")] use crate::{Buffer, Color, ModelVertex}; @@ -297,13 +297,7 @@ fn get_sprite_path_for_player_job(job_id: usize) -> &'static str { } } -fn get_entity_part_files( - script_loader: &ScriptLoader, - entity_type: EntityType, - job_id: usize, - sex: Sex, - head: Option, -) -> Vec { +fn get_entity_part_files(library: &Library, entity_type: EntityType, job_id: usize, sex: Sex, head: Option) -> Vec { let sex_sprite_path = match sex == Sex::Female { true => "¿©", false => "³²", @@ -335,9 +329,9 @@ fn get_entity_part_files( player_body_path(sex_sprite_path, job_id), player_head_path(sex_sprite_path, head_id), ], - EntityType::Npc => vec![format!("npc\\{}", script_loader.get_job_name_from_id(job_id))], - EntityType::Monster => vec![format!("¸ó½ºÅÍ\\{}", script_loader.get_job_name_from_id(job_id))], - EntityType::Warp | EntityType::Hidden => vec![format!("npc\\{}", script_loader.get_job_name_from_id(job_id))], // TODO: change + EntityType::Npc => vec![format!("npc\\{}", library.get_job_identity_from_id(job_id))], + EntityType::Monster => vec![format!("¸ó½ºÅÍ\\{}", library.get_job_identity_from_id(job_id))], + EntityType::Warp | EntityType::Hidden => vec![format!("npc\\{}", library.get_job_identity_from_id(job_id))], // TODO: change } } @@ -379,8 +373,8 @@ impl Common { } } - pub fn get_entity_part_files(&self, script_loader: &ScriptLoader) -> Vec { - get_entity_part_files(script_loader, self.entity_type, self.job_id, self.sex, None) + pub fn get_entity_part_files(&self, library: &Library) -> Vec { + get_entity_part_files(library, self.entity_type, self.job_id, self.sex, None) } pub fn update(&mut self, audio_engine: &AudioEngine, map: &Map, camera: &dyn Camera, client_tick: ClientTick) { @@ -881,9 +875,9 @@ impl Player { ); } - pub fn get_entity_part_files(&self, script_loader: &ScriptLoader) -> Vec { + pub fn get_entity_part_files(&self, library: &Library) -> Vec { let common = self.get_common(); - get_entity_part_files(script_loader, common.entity_type, common.job_id, common.sex, Some(self.hair_id)) + get_entity_part_files(library, common.entity_type, common.job_id, common.sex, Some(self.hair_id)) } } @@ -1004,10 +998,10 @@ impl Entity { self.get_common_mut().animation_data = Some(animation_data) } - pub fn get_entity_part_files(&self, script_loader: &ScriptLoader) -> Vec { + pub fn get_entity_part_files(&self, library: &Library) -> Vec { match self { - Self::Player(player) => player.get_entity_part_files(script_loader), - Self::Npc(npc) => npc.get_common().get_entity_part_files(script_loader), + Self::Player(player) => player.get_entity_part_files(library), + Self::Npc(npc) => npc.get_common().get_entity_part_files(library), } } diff --git a/korangar/src/world/library/mod.rs b/korangar/src/world/library/mod.rs new file mode 100644 index 00000000..d73c986f --- /dev/null +++ b/korangar/src/world/library/mod.rs @@ -0,0 +1,175 @@ +use std::sync::Arc; + +use hashbrown::HashMap; +use korangar_networking::{InventoryItem, NoMetadata, ShopItem}; +use korangar_util::FileLoader; +use mlua::Lua; +use ragnarok_packets::ItemId; + +use crate::graphics::Texture; +use crate::loaders::{AsyncLoader, GameFileLoader, ImageType, ItemLocation}; + +#[derive(Debug, Clone)] +pub struct ResourceMetadata { + pub texture: Option>, + pub name: String, +} + +#[derive(Debug, Clone)] +struct ItemInfo { + identified_name: Option, + unidentified_name: Option, + identified_resource: Option, + unidentified_resource: Option, +} + +pub struct Library { + job_identity_table: HashMap, + item_table: HashMap, +} + +impl Library { + pub fn new(game_file_loader: &GameFileLoader) -> mlua::Result { + let state = Lua::new(); + + let data = game_file_loader + .get("data\\luafiles514\\lua files\\datainfo\\jobidentity.lub") + .unwrap(); + state.load(&data).exec()?; + + let data = game_file_loader + .get("data\\luafiles514\\lua files\\datainfo\\npcidentity.lub") + .unwrap(); + state.load(&data).exec()?; + + let job_identity_table = Self::load_job_identity_table(&state)?; + + let state = Lua::new(); + + let data = game_file_loader + .get("data\\luafiles514\\lua files\\datainfo\\iteminfo.lub") + .unwrap(); + state.load(&data).exec()?; + + let item_table = Self::load_item_table(&state)?; + + Ok(Self { + job_identity_table, + item_table, + }) + } + + pub fn load_job_identity_table(state: &Lua) -> mlua::Result> { + let globals = state.globals(); + let mut result = HashMap::new(); + + if let Ok(jobtbl) = globals.get::("jobtbl") { + for (key, value) in jobtbl.pairs::().flatten() { + let cleaned_key = if let Some(end) = key.strip_prefix("JT_G_") { + end.to_string() + } else { + key[3..].to_string() + }; + + result.insert(value, cleaned_key); + } + } + + if let Ok(jttbl) = globals.get::("JTtbl") { + for (key, value) in jttbl.pairs::().flatten() { + let cleaned_key = if let Some(end) = key.strip_prefix("JT_G_") { + end.to_string() + } else if key.starts_with("JT_C1_") + || key.starts_with("JT_C2_") + || key.starts_with("JT_C3_") + || key.starts_with("JT_C4_") + || key.starts_with("JT_C5_") + { + key[6..].to_string() + } else { + key[3..].to_string() + }; + + let cleaned_key = cleaned_key.replace("CHONCHON", "chocho"); + + result.insert(value, cleaned_key); + } + } + + let compacted = HashMap::from_iter(result); + + Ok(compacted) + } + + fn load_item_table(state: &Lua) -> mlua::Result> { + let globals = state.globals(); + let mut result = HashMap::new(); + + if let Ok(table) = globals.get::("tbl") { + for (item_id, item_table) in table.pairs::().flatten() { + let info = ItemInfo { + identified_name: item_table.get("identifiedDisplayName").ok(), + unidentified_name: item_table.get("unidentifiedDisplayName").ok(), + identified_resource: item_table.get("identifiedResourceName").ok(), + unidentified_resource: item_table.get("unidentifiedResourceName").ok(), + }; + result.insert(ItemId(item_id), info); + } + } + + let compacted = HashMap::from_iter(result); + + Ok(compacted) + } + + pub fn get_job_identity_from_id(&self, job_id: usize) -> &str { + self.job_identity_table + .get(&job_id) + .map(|name| name.as_str()) + .unwrap_or("1_f_maria") + } + + fn get_item_name_from_id(&self, item_id: ItemId, is_identified: bool) -> &str { + match is_identified { + true => self.item_table.get(&item_id).and_then(|info| info.identified_name.as_deref()), + false => self.item_table.get(&item_id).and_then(|info| info.unidentified_name.as_deref()), + } + .unwrap_or("NOTFOUND") + } + + fn get_item_resource_from_id(&self, item_id: ItemId, is_identified: bool) -> &str { + match is_identified { + true => self.item_table.get(&item_id).and_then(|info| info.identified_resource.as_deref()), + false => self.item_table.get(&item_id).and_then(|info| info.unidentified_resource.as_deref()), + } + .unwrap_or("»ç°ú") + } + + pub fn load_inventory_item_metadata( + &self, + async_loader: &AsyncLoader, + item: InventoryItem, + ) -> InventoryItem { + let is_identified = item.is_identified(); + + let resource_name = self.get_item_resource_from_id(item.item_id, is_identified); + let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp"); + let texture = async_loader.request_item_sprite_load(ItemLocation::Inventory, item.item_id, &full_path, ImageType::Color); + let name = self.get_item_name_from_id(item.item_id, is_identified).to_string(); + + let metadata = ResourceMetadata { texture, name }; + + InventoryItem { metadata, ..item } + } + + pub fn load_shop_item_metadata(&self, async_loader: &AsyncLoader, item: ShopItem) -> ShopItem { + let resource_name = self.get_item_resource_from_id(item.item_id, true); + let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp"); + let texture = async_loader.request_item_sprite_load(ItemLocation::Shop, item.item_id, &full_path, ImageType::Color); + let name = self.get_item_name_from_id(item.item_id, true).to_string(); + + let metadata = ResourceMetadata { texture, name }; + + ShopItem { metadata, ..item } + } +} diff --git a/korangar/src/world/mod.rs b/korangar/src/world/mod.rs index 30000f80..fafc6fb6 100644 --- a/korangar/src/world/mod.rs +++ b/korangar/src/world/mod.rs @@ -3,6 +3,7 @@ mod animation; mod cameras; mod effect; mod entity; +mod library; mod light; mod map; mod model; @@ -15,6 +16,7 @@ pub use self::animation::*; pub use self::cameras::*; pub use self::effect::*; pub use self::entity::*; +pub use self::library::*; pub use self::light::*; pub use self::map::*; pub use self::model::*; diff --git a/korangar_networking/src/items.rs b/korangar_networking/src/items.rs index 3d0b1509..f2b8731e 100644 --- a/korangar_networking/src/items.rs +++ b/korangar_networking/src/items.rs @@ -35,7 +35,7 @@ pub struct InventoryItem { } impl InventoryItem { - pub fn is_identifed(&self) -> bool { + pub fn is_identified(&self) -> bool { match &self.details { InventoryItemDetails::Regular { flags, .. } => flags.contains(RegularItemFlags::IDENTIFIED), InventoryItemDetails::Equippable { flags, .. } => flags.contains(EquippableItemFlags::IDENTIFIED),