From 14eb0f9aa258561e06e22fa23c7ddbc4a7aed9ea Mon Sep 17 00:00:00 2001 From: Monty Marz Date: Thu, 29 Jul 2021 22:38:35 +0000 Subject: [PATCH] Remove sceptre, add swords --- CHANGELOG.md | 6 + .../common/items/weapons/sword_1h/starter.ron | 21 ++++ assets/common/recipe_book.ron | 11 +- .../ui/char_select/portraits/danari_f.png | 4 +- .../ui/char_select/portraits/danari_m.png | 4 +- assets/voxygen/element/weapons/swords.png | 3 + assets/voxygen/i18n/en/common.ron | 6 +- assets/voxygen/item_image_manifest.ron | 4 + .../voxygen/voxel/biped_weapon_manifest.ron | 4 + .../voxygen/voxel/weapon/sword_1h/starter.vox | 3 + client/src/bin/bot/main.rs | 1 + client/src/lib.rs | 15 ++- common/net/src/msg/client.rs | 3 +- server/src/character_creator.rs | 68 +++++++---- server/src/sys/msg/character_screen.rs | 23 +++- voxygen/src/menu/char_selection/mod.rs | 9 +- voxygen/src/menu/char_selection/ui/mod.rs | 111 +++++++++++------- 17 files changed, 215 insertions(+), 81 deletions(-) create mode 100644 assets/common/items/weapons/sword_1h/starter.ron create mode 100644 assets/voxygen/element/weapons/swords.png create mode 100644 assets/voxygen/voxel/weapon/sword_1h/starter.vox diff --git a/CHANGELOG.md b/CHANGELOG.md index 4845b4b3ab..a748274fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Quotes and escape codes can be used in command arguments - Toggle chat with a shortcut (default is F5) - Pets are now saved on logout 🐕 🦎 🐼 +- Dualwielded, one-handed swords as starting weapons (Will be replaced by daggers in the future!) +- Healing sceptre crafting recipe ### Changed @@ -41,10 +43,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Trades now consider if items can stack in full inventories. - The types of animals that can be tamed as pets are now limited to certain species, pending further balancing of pets + ### Removed - Enemies no longer spawn in dungeon boss room - Melee critical hit no longer applies after reduction by armour +- Enemies no more spawn in dungeon boss room +- Melee critical hit no more applies after reduction by armour +- Removed Healing Sceptre as a starting weapon as it is considered an advanced weapon ### Fixed diff --git a/assets/common/items/weapons/sword_1h/starter.ron b/assets/common/items/weapons/sword_1h/starter.ron new file mode 100644 index 0000000000..665c22a547 --- /dev/null +++ b/assets/common/items/weapons/sword_1h/starter.ron @@ -0,0 +1,21 @@ +ItemDef( + name: "Damaged Gladius", + description: "This blade has seen better days.", + kind: Tool(( + kind: Sword, + hands: One, + stats: Direct(( + equip_time_secs: 0.3, + power: 0.5, + effect_power: 1.0, + speed: 1.0, + crit_chance: 0.16, + range: 1.0, + energy_efficiency: 1.0, + buff_strength: 1.0, + )), + )), + quality: Low, + tags: [], + ability_spec: None, +) diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index 5a1f2a8515..b46eece6a9 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -506,6 +506,15 @@ craft_sprite: Some(CraftingBench), is_recycling: false, ), + "Healing Sceptre": ( + output: ("common.items.weapons.sceptre.starter_sceptre", 1), + inputs: [ + (Item("common.items.crafting_ing.twigs"), 10), + (Item("common.items.crafting_ing.stones"), 0), + ], + craft_sprite: None, + is_recycling: false, + ), "Soothing Loop": ( output: ("common.items.weapons.sceptre.loops0", 1), inputs: [ @@ -514,7 +523,7 @@ (Item("common.items.mineral.gem.ruby"), 4), (Item("common.items.tool.craftsman_hammer"), 0), ], - craft_sprite: None, + craft_sprite: Some(CraftingBench), is_recycling: false, ), "Hunting Bow": ( diff --git a/assets/voxygen/element/ui/char_select/portraits/danari_f.png b/assets/voxygen/element/ui/char_select/portraits/danari_f.png index 503e55cb56..a7dd41216b 100644 --- a/assets/voxygen/element/ui/char_select/portraits/danari_f.png +++ b/assets/voxygen/element/ui/char_select/portraits/danari_f.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8877b0f6b59742996a64e9e35f07028e15c2e2df17eb7f0b80a1714cc7b30c96 -size 6323 +oid sha256:b3fe6ed254e8d1fc4dbdec0014070bd8a421e924f581f59642837b999f47fef3 +size 685 diff --git a/assets/voxygen/element/ui/char_select/portraits/danari_m.png b/assets/voxygen/element/ui/char_select/portraits/danari_m.png index 739e877c1d..c1845faeb4 100644 --- a/assets/voxygen/element/ui/char_select/portraits/danari_m.png +++ b/assets/voxygen/element/ui/char_select/portraits/danari_m.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e5ab138c073d970481e9213ba9f7c4c681011eba92114faef37c7c7edfde387 -size 12016 +oid sha256:d0a3c0ecf9309e439361d80b93f5292941fc677603e43531f628a024fe5917b3 +size 676 diff --git a/assets/voxygen/element/weapons/swords.png b/assets/voxygen/element/weapons/swords.png new file mode 100644 index 0000000000..fa2a4f1ade --- /dev/null +++ b/assets/voxygen/element/weapons/swords.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48c06c9dab55e392528037e8efbbae35c8586ece413976cdc72c0d84f4ad117 +size 316 diff --git a/assets/voxygen/i18n/en/common.ron b/assets/voxygen/i18n/en/common.ron index 1dd6bf2c10..efbffbcda5 100644 --- a/assets/voxygen/i18n/en/common.ron +++ b/assets/voxygen/i18n/en/common.ron @@ -61,12 +61,14 @@ Is the client up to date?"#, "common.species.danari": "Danari", "common.weapons.axe": "Axe", + "common.weapons.greatsword": "Greatsword", + "common.weapons.shortswords": "Shortswords", "common.weapons.sword": "Sword", - "common.weapons.staff": "Staff", + "common.weapons.staff": "Firestaff", "common.weapons.bow": "Bow", "common.weapons.hammer": "Hammer", "common.weapons.general": "General Combat", - "common.weapons.sceptre": "Sceptre", + "common.weapons.sceptre": "Healing Sceptre", "common.weapons.shield": "Shield", "common.weapons.spear": "Spear", "common.weapons.hammer_simple": "Simple Hammer", diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 303028d56e..bf44608ff0 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -437,6 +437,10 @@ "voxel.weapon.sword_1h.cobalt-3", (1.0, -1.0, 0.0), (-135.0, 90.0, 0.0), 1.2, ), + Tool("common.items.weapons.sword_1h.starter"): VoxTrans( + "voxel.weapon.sword_1h.starter", + (-1.0, 1.0, 0.0), (-135.0, 90.0, 0.0), 1.2, + ), Tool("common.items.weapons.sword_1h.iron-0"): VoxTrans( "voxel.weapon.sword_1h.iron-0", (1.0, -1.0, 0.0), (-135.0, 90.0, 0.0), 1.2, diff --git a/assets/voxygen/voxel/biped_weapon_manifest.ron b/assets/voxygen/voxel/biped_weapon_manifest.ron index 74cca7017f..ffc7128cf7 100644 --- a/assets/voxygen/voxel/biped_weapon_manifest.ron +++ b/assets/voxygen/voxel/biped_weapon_manifest.ron @@ -224,6 +224,10 @@ vox_spec: ("weapon.sword_1h.iron-4", (-2.0, -4.5, -3.0)), color: None ), + "common.items.weapons.sword_1h.starter": ( + vox_spec: ("weapon.sword_1h.starter", (-2.0, -4.5, -3.0)), + color: None + ), "common.items.weapons.sword_1h.obsidian-0": ( vox_spec: ("weapon.sword_1h.obsidian-0", (-2.0, -4.5, -3.0)), color: None diff --git a/assets/voxygen/voxel/weapon/sword_1h/starter.vox b/assets/voxygen/voxel/weapon/sword_1h/starter.vox new file mode 100644 index 0000000000..82917f17e9 --- /dev/null +++ b/assets/voxygen/voxel/weapon/sword_1h/starter.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcde6c25e14c7970ebb926528e0e0a2f715f200a6f88c65a2a29da90f62aa513 +size 1304 diff --git a/client/src/bin/bot/main.rs b/client/src/bin/bot/main.rs index c6a4f5b63a..d4362b9b12 100644 --- a/client/src/bin/bot/main.rs +++ b/client/src/bin/bot/main.rs @@ -174,6 +174,7 @@ impl BotClient { client.create_character( cred.username.clone(), Some("common.items.weapons.sword.starter".to_string()), + None, body.into(), ); client.load_character_list(); diff --git a/client/src/lib.rs b/client/src/lib.rs index 4a6a823c1e..ae69cbf55a 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -826,9 +826,20 @@ impl Client { } /// New character creation - pub fn create_character(&mut self, alias: String, tool: Option, body: comp::Body) { + pub fn create_character( + &mut self, + alias: String, + mainhand: Option, + offhand: Option, + body: comp::Body, + ) { self.character_list.loading = true; - self.send_msg(ClientGeneral::CreateCharacter { alias, tool, body }); + self.send_msg(ClientGeneral::CreateCharacter { + alias, + mainhand, + offhand, + body, + }); } /// Character deletion diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index 0cc811ceb9..4f7b18ffa0 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -51,7 +51,8 @@ pub enum ClientGeneral { RequestCharacterList, CreateCharacter { alias: String, - tool: Option, + mainhand: Option, + offhand: Option, body: comp::Body, }, DeleteCharacter(CharacterId), diff --git a/server/src/character_creator.rs b/server/src/character_creator.rs index 688b0829fd..9c193262df 100644 --- a/server/src/character_creator.rs +++ b/server/src/character_creator.rs @@ -4,45 +4,54 @@ use common::comp::{ }; use specs::{Entity, WriteExpect}; -const VALID_STARTER_ITEMS: [&str; 6] = [ - "common.items.weapons.hammer.starter_hammer", - "common.items.weapons.bow.starter", - "common.items.weapons.axe.starter_axe", - "common.items.weapons.staff.starter_staff", - "common.items.weapons.sword.starter", - "common.items.weapons.sceptre.starter_sceptre", +const VALID_STARTER_ITEMS: &[[Option<&str>; 2]] = &[ + [None, None], // Not used with an unmodified client but should still be allowed (zesterer) + [Some("common.items.weapons.hammer.starter_hammer"), None], + [Some("common.items.weapons.bow.starter"), None], + [Some("common.items.weapons.axe.starter_axe"), None], + [Some("common.items.weapons.staff.starter_staff"), None], + [Some("common.items.weapons.sword.starter"), None], + [ + Some("common.items.weapons.sword_1h.starter"), + Some("common.items.weapons.sword_1h.starter"), + ], ]; +#[derive(Debug)] +pub enum CreationError { + InvalidWeapon, + InvalidBody, +} + pub fn create_character( entity: Entity, player_uuid: String, character_alias: String, - character_tool: Option, + character_mainhand: Option, + character_offhand: Option, body: Body, character_updater: &mut WriteExpect<'_, CharacterUpdater>, -) { +) -> Result<(), CreationError> { // quick fix whitelist validation for now; eventually replace the // `Option` with an index into a server-provided list of starter // items, and replace `comp::body::Body` with `comp::body::humanoid::Body` // throughout the messages involved - let tool_id = match character_tool { - Some(tool_id) if VALID_STARTER_ITEMS.contains(&&*tool_id) => tool_id, - _ => return, - }; if !matches!(body, Body::Humanoid(_)) { - return; + return Err(CreationError::InvalidBody); } - - let stats = Stats::new(character_alias.to_string()); - let skill_set = SkillSet::default(); - + if !VALID_STARTER_ITEMS.contains(&[character_mainhand.as_deref(), character_offhand.as_deref()]) + { + return Err(CreationError::InvalidWeapon); + }; + // The client sends None if a weapon hand is empty let loadout = LoadoutBuilder::empty() .defaults() - .active_mainhand(Some(Item::new_from_asset_expect(&tool_id))) + .active_mainhand(character_mainhand.map(|x| Item::new_from_asset_expect(&x))) + .active_offhand(character_offhand.map(|x| Item::new_from_asset_expect(&x))) .build(); - let mut inventory = Inventory::new_with_loadout(loadout); - + let stats = Stats::new(character_alias.to_string()); + let skill_set = SkillSet::default(); // Default items for new characters inventory .push(Item::new_from_asset_expect( @@ -61,4 +70,21 @@ pub fn create_character( character_alias, (body, stats, skill_set, inventory, waypoint, Vec::new()), ); + Ok(()) +} + +// Error handling +impl core::fmt::Display for CreationError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + CreationError::InvalidWeapon => write!( + f, + "Invalid weapon.\nServer and client might be partially incompatible." + ), + CreationError::InvalidBody => write!( + f, + "Invalid Body.\nServer and client might be partially incompatible" + ), + } + } } diff --git a/server/src/sys/msg/character_screen.rs b/server/src/sys/msg/character_screen.rs index 5610bfedf2..b2b2618c5a 100644 --- a/server/src/sys/msg/character_screen.rs +++ b/server/src/sys/msg/character_screen.rs @@ -115,19 +115,34 @@ impl Sys { character_loader.load_character_list(entity, player.uuid().to_string()) } }, - ClientGeneral::CreateCharacter { alias, tool, body } => { + ClientGeneral::CreateCharacter { + alias, + mainhand, + offhand, + body, + } => { if let Err(error) = alias_validator.validate(&alias) { debug!(?error, ?alias, "denied alias as it contained a banned word"); client.send(ServerGeneral::CharacterActionError(error.to_string()))?; } else if let Some(player) = players.get(entity) { - character_creator::create_character( + if let Err(error) = character_creator::create_character( entity, player.uuid().to_string(), alias, - tool, + mainhand.clone(), + offhand.clone(), body, character_updater, - ); + ) { + debug!( + ?error, + ?mainhand, + ?offhand, + ?body, + "Denied creating character because of invalid input." + ); + client.send(ServerGeneral::CharacterActionError(error.to_string()))?; + } } }, ClientGeneral::DeleteCharacter(character_id) => { diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index e2c67b6c42..8941cfc8bf 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -109,10 +109,15 @@ impl PlayState for CharSelectionState { ui::Event::Logout => { return PlayStateResult::Pop; }, - ui::Event::AddCharacter { alias, tool, body } => { + ui::Event::AddCharacter { + alias, + mainhand, + offhand, + body, + } => { self.client .borrow_mut() - .create_character(alias, Some(tool), body); + .create_character(alias, mainhand, offhand, body); }, ui::Event::DeleteCharacter(character_id) => { self.client.borrow_mut().delete_character(character_id); diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index b26d7c8fbe..083086848c 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -52,7 +52,8 @@ const STARTER_BOW: &str = "common.items.weapons.bow.starter"; const STARTER_AXE: &str = "common.items.weapons.axe.starter_axe"; const STARTER_STAFF: &str = "common.items.weapons.staff.starter_staff"; const STARTER_SWORD: &str = "common.items.weapons.sword.starter"; -const STARTER_SCEPTRE: &str = "common.items.weapons.sceptre.starter_sceptre"; +const STARTER_SWORDS: &str = "common.items.weapons.sword_1h.starter"; + // TODO: what does this comment mean? // // Use in future MR to make this a starter weapon @@ -67,9 +68,9 @@ image_ids_ice! { slider_range: "voxygen.element.ui.generic.slider.track", slider_indicator: "voxygen.element.ui.generic.slider.indicator", - selection: "voxygen.element.ui.generic.frames.selection", - selection_hover: "voxygen.element.ui.generic.frames.selection_hover", - selection_press: "voxygen.element.ui.generic.frames.selection_press", + char_selection: "voxygen.element.ui.generic.frames.selection", + char_selection_hover: "voxygen.element.ui.generic.frames.selection_hover", + char_selection_press: "voxygen.element.ui.generic.frames.selection_press", delete_button: "voxygen.element.ui.char_select.icons.bin", delete_button_hover: "voxygen.element.ui.char_select.icons.bin_hover", @@ -78,7 +79,7 @@ image_ids_ice! { name_input: "voxygen.element.ui.generic.textbox", // Tool Icons - sceptre: "voxygen.element.weapons.sceptre", + swords: "voxygen.element.weapons.swords", sword: "voxygen.element.weapons.sword", axe: "voxygen.element.weapons.axe", hammer: "voxygen.element.weapons.hammer", @@ -124,7 +125,8 @@ pub enum Event { Play(CharacterId), AddCharacter { alias: String, - tool: String, + mainhand: Option, + offhand: Option, body: comp::Body, }, DeleteCharacter(CharacterId), @@ -148,7 +150,8 @@ enum Mode { name: String, body: humanoid::Body, inventory: Box, - tool: &'static str, + mainhand: Option<&'static str>, + offhand: Option<&'static str>, body_type_buttons: [button::State; 2], species_buttons: [button::State; 6], @@ -178,11 +181,15 @@ impl Mode { } pub fn create(name: String) -> Self { - let tool = STARTER_SWORD; + // TODO: Load these from the server (presumably from a .ron) to allow for easier + // modification of custom starting weapons + let mainhand = Some(STARTER_SWORD); + let offhand = None; let loadout = LoadoutBuilder::empty() .defaults() - .active_mainhand(Some(Item::new_from_asset_expect(tool))) + .active_mainhand(mainhand.map(Item::new_from_asset_expect)) + .active_offhand(offhand.map(Item::new_from_asset_expect)) .build(); let inventory = Box::new(Inventory::new_with_loadout(loadout)); @@ -191,7 +198,8 @@ impl Mode { name, body: humanoid::Body::random(), inventory, - tool, + mainhand, + offhand, body_type_buttons: Default::default(), species_buttons: Default::default(), tool_buttons: Default::default(), @@ -244,7 +252,7 @@ enum Message { Name(String), BodyType(humanoid::BodyType), Species(humanoid::Species), - Tool(&'static str), + Tool((Option<&'static str>, Option<&'static str>)), RandomizeCharacter, RandomizeName, CancelDeletion, @@ -468,12 +476,12 @@ impl Controls { .padding(10) .style( style::button::Style::new(if Some(i) == selected { - imgs.selection_hover + imgs.char_selection_hover } else { - imgs.selection + imgs.char_selection }) - .hover_image(imgs.selection_hover) - .press_image(imgs.selection_press) + .hover_image(imgs.char_selection_hover) + .press_image(imgs.char_selection_press) .image_color(Rgba::new( select_col.0, select_col.1, @@ -485,7 +493,7 @@ impl Controls { .height(Length::Fill) .on_press(Message::Select(character_id)), ) - .ratio_of_image(imgs.selection), + .ratio_of_image(imgs.char_selection), ) .padding(0) .align_x(Align::End) @@ -514,9 +522,9 @@ impl Controls { .center_y(), ) .style( - style::button::Style::new(imgs.selection) - .hover_image(imgs.selection_hover) - .press_image(imgs.selection_press) + style::button::Style::new(imgs.char_selection) + .hover_image(imgs.char_selection_hover) + .press_image(imgs.char_selection_press) .image_color(Rgba::new(color.0, color.1, color.2, 255)) .text_color(iced::Color::from_rgb8(color.0, color.1, color.2)) .disabled_text_color(iced::Color::from_rgb8( @@ -531,7 +539,7 @@ impl Controls { button } }) - .ratio_of_image(imgs.selection) + .ratio_of_image(imgs.char_selection) .into(), ); characters @@ -710,7 +718,8 @@ impl Controls { name, body, inventory: _, - tool, + mainhand, + offhand: _, ref mut scroll, ref mut body_type_buttons, ref mut species_buttons, @@ -863,30 +872,30 @@ impl Controls { ]) .spacing(1); - let [ref mut sword_button, ref mut sceptre_button, ref mut axe_button, ref mut hammer_button, ref mut bow_button, ref mut staff_button] = + let [ref mut sword_button, ref mut swords_button, ref mut axe_button, ref mut hammer_button, ref mut bow_button, ref mut staff_button] = tool_buttons; let tool = Column::with_children(vec![ Row::with_children(vec![ icon_button_tooltip( sword_button, - *tool == STARTER_SWORD, - Message::Tool(STARTER_SWORD), + *mainhand == Some(STARTER_SWORD), + Message::Tool((Some(STARTER_SWORD), None)), imgs.sword, - "common.weapons.sword", + "common.weapons.greatsword", ) .into(), icon_button_tooltip( hammer_button, - *tool == STARTER_HAMMER, - Message::Tool(STARTER_HAMMER), + *mainhand == Some(STARTER_HAMMER), + Message::Tool((Some(STARTER_HAMMER), None)), imgs.hammer, "common.weapons.hammer", ) .into(), icon_button_tooltip( axe_button, - *tool == STARTER_AXE, - Message::Tool(STARTER_AXE), + *mainhand == Some(STARTER_AXE), + Message::Tool((Some(STARTER_AXE), None)), imgs.axe, "common.weapons.axe", ) @@ -896,25 +905,26 @@ impl Controls { .into(), Row::with_children(vec![ icon_button_tooltip( - sceptre_button, - *tool == STARTER_SCEPTRE, - Message::Tool(STARTER_SCEPTRE), - imgs.sceptre, - "common.weapons.sceptre", + swords_button, + *mainhand == Some(STARTER_SWORDS), + Message::Tool((Some(STARTER_SWORDS), Some(STARTER_SWORDS))), + imgs.swords, + "common.weapons.greatsword + ", ) .into(), icon_button_tooltip( bow_button, - *tool == STARTER_BOW, - Message::Tool(STARTER_BOW), + *mainhand == Some(STARTER_BOW), + Message::Tool((Some(STARTER_BOW), None)), imgs.bow, "common.weapons.bow", ) .into(), icon_button_tooltip( staff_button, - *tool == STARTER_STAFF, - Message::Tool(STARTER_STAFF), + *mainhand == Some(STARTER_STAFF), + Message::Tool((Some(STARTER_STAFF), None)), imgs.staff, "common.weapons.staff", ) @@ -1076,8 +1086,8 @@ impl Controls { let column_content = vec![ body_type.into(), - species.into(), tool.into(), + species.into(), slider_options.into(), rand_character.into(), ]; @@ -1300,12 +1310,17 @@ impl Controls { }, Message::CreateCharacter => { if let Mode::Create { - name, body, tool, .. + name, + body, + mainhand, + offhand, + .. } = &self.mode { events.push(Event::AddCharacter { alias: name.clone(), - tool: String::from(*tool), + mainhand: mainhand.map(String::from), + offhand: offhand.map(String::from), body: comp::Body::Humanoid(*body), }); self.mode = Mode::select(Some(InfoContent::CreatingCharacter)); @@ -1330,13 +1345,21 @@ impl Controls { }, Message::Tool(value) => { if let Mode::Create { - tool, inventory, .. + mainhand, + offhand, + inventory, + .. } = &mut self.mode { - *tool = value; + *mainhand = value.0; + *offhand = value.1; inventory.replace_loadout_item( EquipSlot::ActiveMainhand, - Some(Item::new_from_asset_expect(*tool)), + mainhand.map(|specifier| Item::new_from_asset_expect(specifier)), + ); + inventory.replace_loadout_item( + EquipSlot::ActiveOffhand, + offhand.map(|specifier| Item::new_from_asset_expect(specifier)), ); } },