diff --git a/assets/server/manifests/kits.ron b/assets/server/manifests/kits.ron index bf3ed02c9e..d17c2997a8 100644 --- a/assets/server/manifests/kits.ron +++ b/assets/server/manifests/kits.ron @@ -45,41 +45,30 @@ ("common.items.food.tomatosalad", 100), ], "jewellery": [ - // TODO: remove duplicates and handle quantity of non-stackable items - // Necklace - ("common.items.armor.misc.neck.plain_1",1), - ("common.items.armor.misc.neck.shell",1), + ("common.items.armor.misc.neck.shell", 1), + ("common.items.armor.misc.neck.plain_1", 1), // Rings // With effects - ("common.items.armor.misc.ring.diamond",1), - ("common.items.armor.misc.ring.diamond",1), + ("common.items.armor.misc.ring.diamond", 2), - ("common.items.armor.misc.ring.ruby",1), - ("common.items.armor.misc.ring.ruby",1), + ("common.items.armor.misc.ring.ruby", 2), - ("common.items.armor.misc.ring.emerald",1), - ("common.items.armor.misc.ring.emerald",1), + ("common.items.armor.misc.ring.emerald", 2), - ("common.items.armor.misc.ring.sapphire",1), - ("common.items.armor.misc.ring.sapphire",1), + ("common.items.armor.misc.ring.sapphire", 2), - ("common.items.armor.misc.ring.topaz",1), - ("common.items.armor.misc.ring.topaz",1), + ("common.items.armor.misc.ring.topaz", 2), - ("common.items.armor.misc.ring.amethyst",1), - ("common.items.armor.misc.ring.amethyst",1), + ("common.items.armor.misc.ring.amethyst", 2), // Without effects - ("common.items.armor.misc.ring.scratched",1), - ("common.items.armor.misc.ring.scratched",1), + ("common.items.armor.misc.ring.scratched", 2), - ("common.items.armor.misc.ring.gold",1), - ("common.items.armor.misc.ring.gold",1), + ("common.items.armor.misc.ring.gold", 2), - ("common.items.armor.misc.ring.skull",1), - ("common.items.armor.misc.ring.skull",1), + ("common.items.armor.misc.ring.skull", 2), ], "endgame": [ // Cultist weapons @@ -326,8 +315,7 @@ // weapons ("common.items.weapons.sword.stone-0", 1), - ("common.items.weapons.axe_1h.stone-0", 1), - ("common.items.weapons.axe_1h.stone-1", 1), + ("common.items.weapons.axe_1h.stone-0", 2), ("common.items.weapons.hammer.stone_hammer-0", 1), ("common.items.weapons.bow.wood-0", 1), ("common.items.weapons.staff.bent_fuse", 1), diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 4859170201..5d6eadf393 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -121,6 +121,9 @@ impl assets::Asset for SkillPresetManifest { const EXTENSION: &'static str = "ron"; } +pub const KIT_MANIFEST_PATH: &str = "server.manifests.kits"; +pub const PRESET_MANIFEST_PATH: &str = "server.manifests.presets"; + lazy_static! { static ref ALIGNMENTS: Vec = vec!["wild", "enemy", "npc", "pet"] .iter() @@ -240,7 +243,7 @@ lazy_static! { }; static ref KITS: Vec = { - if let Ok(kits) = KitManifest::load("server.manifests.kits") { + if let Ok(kits) = KitManifest::load(KIT_MANIFEST_PATH) { kits.read().0.keys().cloned().collect() } else { Vec::new() @@ -248,7 +251,7 @@ lazy_static! { }; static ref PRESETS: HashMap> = { - if let Ok(presets) = SkillPresetManifest::load("server.manifests.presets") { + if let Ok(presets) = SkillPresetManifest::load(PRESET_MANIFEST_PATH) { presets.read().0.clone() } else { warn!("Error while loading presets"); @@ -847,9 +850,18 @@ impl ArgumentSpec { #[cfg(test)] mod tests { use super::*; + use crate::comp::Item; #[test] - fn test_loading_skill_presets() { - SkillPresetManifest::load_expect("server.manifests.presets"); + fn test_loading_skill_presets() { SkillPresetManifest::load_expect(PRESET_MANIFEST_PATH); } + + #[test] + fn test_load_kits() { + let kits = KitManifest::load_expect(KIT_MANIFEST_PATH).read(); + for kit in kits.0.values() { + for (item_id, _) in kit.iter() { + std::mem::drop(Item::new_from_asset_expect(item_id)); + } + } } } diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 84a5e2b7d7..76ea19e12b 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -320,7 +320,7 @@ impl Inventory { pub fn populated_slots(&self) -> usize { self.slots().filter_map(|slot| slot.as_ref()).count() } - fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() } + pub fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() } /// Check if an item is in this inventory. pub fn contains(&self, item: &Item) -> bool { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index e319d789b9..643b6df4d1 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -14,7 +14,9 @@ use authc::Uuid; use chrono::{NaiveTime, Timelike, Utc}; use common::{ assets, - cmd::{ChatCommand, BUFF_PACK, BUFF_PARSER}, + cmd::{ + ChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS, KIT_MANIFEST_PATH, PRESET_MANIFEST_PATH, + }, comp::{ self, aura::{Aura, AuraKind, AuraTarget}, @@ -1568,49 +1570,112 @@ fn handle_kill_npcs( fn handle_kit( server: &mut Server, - _client: EcsEntity, + client: EcsEntity, target: EcsEntity, args: Vec, action: &ChatCommand, ) -> CmdResult<()> { - if let Some(name) = parse_args!(args, String) { - if let Ok(kits) = common::cmd::KitManifest::load("server.manifests.kits") { - let kits = kits.read(); - if let Some(kit) = kits.0.get(&name) { - if let (Some(mut target_inventory), mut target_inv_update) = ( - server - .state() - .ecs() - .write_storage::() - .get_mut(target), - server.state.ecs().write_storage::(), - ) { - for (item_id, quantity) in kit.iter() { - if let Ok(mut item) = comp::Item::new_from_asset(item_id) { - let _ = item.set_amount(*quantity); - let (_, _) = ( - target_inventory.push(item), - target_inv_update.insert( - target, - comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug), - ), - ); - } else { - warn!("Unknown item: {}", &item_id); - } - } - Ok(()) - } else { - Err("Could not get inventory".to_string()) - } - } else { - Err(format!("Kit '{}' not found", name)) + use common::cmd::KitManifest; + + let notify = |server: &mut Server, kit_name: &str| { + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, format!("Gave kit: {}", kit_name)), + ); + }; + let name = parse_args!(args, String).ok_or_else(|| action.help_string())?; + + match name.as_str() { + "all" => { + // TODO: we will probably want to handle modular items here too + let items = &ITEM_SPECS; + let res = push_kit( + items.iter().map(|item_id| (item_id.as_str(), 1)), + items.len(), + server, + target, + ); + if res.is_ok() { + notify(server, "all"); } - } else { - Err("Could not load manifest file 'server.manifests.kits'".to_string()) + res + }, + kit_name => { + let kits = KitManifest::load(KIT_MANIFEST_PATH) + .map(|kits| kits.read()) + .map_err(|_| format!("Could not load manifest file {}", KIT_MANIFEST_PATH))?; + + let kit = kits + .0 + .get(kit_name) + .ok_or(format!("Kit '{}' not found", kit_name))?; + + let res = push_kit( + kit.iter() + .map(|&(ref item_id, quantity)| (item_id.as_str(), quantity)), + kit.len(), + server, + target, + ); + if res.is_ok() { + notify(server, kit_name); + } + res + }, + } +} + +fn push_kit<'a, I>(kit: I, count: usize, server: &mut Server, target: EcsEntity) -> CmdResult<()> +where + I: Iterator, +{ + if let (Some(mut target_inventory), mut target_inv_update) = ( + server + .state() + .ecs() + .write_storage::() + .get_mut(target), + server.state.ecs().write_storage::(), + ) { + // TODO: implement atomic `insert_all_or_nothing` on Inventory + if target_inventory.free_slots() < count { + return Err("Inventory doesn't have enough slots".to_owned()); } + for (item_id, quantity) in kit { + let mut item = comp::Item::new_from_asset(item_id) + .map_err(|_| format!("Unknown item: {}", item_id))?; + let mut res = Ok(()); + + // Either push stack or push one by one. + if item.is_stackable() { + // FIXME: in theory, this can fail, + // but we don't have stack sizes yet. + let _ = item.set_amount(quantity); + res = target_inventory.push(item); + let _ = target_inv_update.insert( + target, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug), + ); + } else { + let ability_map = server.state.ecs().read_resource::(); + let msm = server.state.ecs().read_resource::(); + for _ in 0..quantity { + res = target_inventory.push(item.duplicate(&ability_map, &msm)); + let _ = target_inv_update.insert( + target, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug), + ); + } + } + // I think it's possible to pick-up item during this loop + // and fail into case where you had space but now you don't? + if res.is_err() { + return Err("Can't fit item to inventory".to_owned()); + } + } + Ok(()) } else { - Err(action.help_string()) + Err("Could not get inventory".to_string()) } } @@ -3058,7 +3123,7 @@ fn handle_skill_preset( fn clear_skillset(skill_set: &mut comp::SkillSet) { *skill_set = comp::SkillSet::default(); } fn set_skills(skill_set: &mut comp::SkillSet, preset: &str) -> CmdResult<()> { - let presets = match common::cmd::SkillPresetManifest::load("server.manifests.presets") { + let presets = match common::cmd::SkillPresetManifest::load(PRESET_MANIFEST_PATH) { Ok(presets) => presets.read().0.clone(), Err(err) => { warn!("Error in preset: {}", err);