veloren/server/src/character_creator.rs
2023-10-08 11:35:01 +00:00

116 lines
3.9 KiB
Rust

use crate::persistence::{character_updater::CharacterUpdater, PersistedComponents};
use common::{
character::CharacterId,
comp::{
inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, SkillSet, Stats,
Waypoint, BASE_ABILITY_LIMIT,
},
};
use specs::{Entity, WriteExpect};
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_mainhand: Option<String>,
character_offhand: Option<String>,
body: Body,
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
waypoint: Option<Waypoint>,
) -> 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
if !matches!(body, Body::Humanoid(_)) {
return Err(CreationError::InvalidBody);
}
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(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::with_loadout_humanoid(loadout);
let stats = Stats::new(character_alias.to_string(), body);
let skill_set = SkillSet::default();
// Default items for new characters
inventory
.push(Item::new_from_asset_expect(
"common.items.consumable.potion_minor",
))
.expect("Inventory has at least 2 slots left!");
inventory
.push(Item::new_from_asset_expect("common.items.food.cheese"))
.expect("Inventory has at least 1 slot left!");
let map_marker = None;
character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents {
body,
stats,
skill_set,
inventory,
waypoint,
pets: Vec::new(),
active_abilities: common::comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT),
map_marker,
});
Ok(())
}
pub fn edit_character(
entity: Entity,
player_uuid: String,
id: CharacterId,
character_alias: String,
body: Body,
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
) -> Result<(), CreationError> {
if !matches!(body, Body::Humanoid(_)) {
return Err(CreationError::InvalidBody);
}
character_updater.edit_character(entity, player_uuid, id, character_alias, (body,));
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"
),
}
}
}