Merge branch 'pfau/dualwield_starters' into 'master'

Remove sceptre, add swords

See merge request veloren/veloren!2692
This commit is contained in:
Monty Marz 2021-07-29 22:38:36 +00:00
commit 8827d40660
17 changed files with 215 additions and 81 deletions

View File

@ -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

View File

@ -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,
)

View File

@ -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": (

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/weapons/swords.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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",

View File

@ -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,

View File

@ -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

BIN
assets/voxygen/voxel/weapon/sword_1h/starter.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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();

View File

@ -826,9 +826,20 @@ impl Client {
}
/// New character creation
pub fn create_character(&mut self, alias: String, tool: Option<String>, body: comp::Body) {
pub fn create_character(
&mut self,
alias: String,
mainhand: Option<String>,
offhand: Option<String>,
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

View File

@ -51,7 +51,8 @@ pub enum ClientGeneral {
RequestCharacterList,
CreateCharacter {
alias: String,
tool: Option<String>,
mainhand: Option<String>,
offhand: Option<String>,
body: comp::Body,
},
DeleteCharacter(CharacterId),

View File

@ -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<String>,
character_mainhand: Option<String>,
character_offhand: Option<String>,
body: Body,
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
) {
) -> Result<(), CreationError> {
// quick fix whitelist validation for now; eventually replace the
// `Option<String>` 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"
),
}
}
}

View File

@ -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) => {

View File

@ -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);

View File

@ -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<String>,
offhand: Option<String>,
body: comp::Body,
},
DeleteCharacter(CharacterId),
@ -148,7 +150,8 @@ enum Mode {
name: String,
body: humanoid::Body,
inventory: Box<comp::inventory::Inventory>,
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)),
);
}
},