veloren_server/
character_creator.rs1use crate::persistence::{PersistedComponents, character_updater::CharacterUpdater};
2use common::{
3 character::CharacterId,
4 comp::{
5 BASE_ABILITY_LIMIT, Body, Content, Inventory, Item, SkillSet, Stats, Waypoint,
6 inventory::loadout_builder::LoadoutBuilder,
7 },
8};
9use specs::{Entity, WriteExpect};
10
11const VALID_STARTER_ITEMS: &[[Option<&str>; 2]] = &[
12 [None, None], [Some("common.items.weapons.hammer.starter_hammer"), None],
14 [Some("common.items.weapons.bow.starter"), None],
15 [Some("common.items.weapons.axe.starter_axe"), None],
16 [Some("common.items.weapons.staff.starter_staff"), None],
17 [Some("common.items.weapons.sword.starter"), None],
18 [
19 Some("common.items.weapons.sword_1h.starter"),
20 Some("common.items.weapons.sword_1h.starter"),
21 ],
22];
23
24#[derive(Debug)]
25#[expect(clippy::enum_variant_names)]
26pub enum CreationError {
27 InvalidWeapon,
28 InvalidBody,
29 InvalidAlias,
30}
31
32pub fn create_character(
33 entity: Entity,
34 player_uuid: String,
35 character_alias: String,
36 character_mainhand: Option<String>,
37 character_offhand: Option<String>,
38 body: Body,
39 hardcore: bool,
40 character_updater: &mut WriteExpect<'_, CharacterUpdater>,
41 waypoint: Option<Waypoint>,
42) -> Result<(), CreationError> {
43 if !common::character::verify_character_name(&character_alias) {
44 return Err(CreationError::InvalidAlias);
45 }
46 if !matches!(body, Body::Humanoid(_)) {
51 return Err(CreationError::InvalidBody);
52 }
53 if !VALID_STARTER_ITEMS.contains(&[character_mainhand.as_deref(), character_offhand.as_deref()])
54 {
55 return Err(CreationError::InvalidWeapon);
56 };
57 let loadout = LoadoutBuilder::empty()
59 .defaults()
60 .active_mainhand(character_mainhand.map(|x| Item::new_from_asset_expect(&x)))
61 .active_offhand(character_offhand.map(|x| Item::new_from_asset_expect(&x)))
62 .build();
63 let mut inventory = Inventory::with_loadout_humanoid(loadout);
64
65 let stats = Stats::new(Content::Plain(character_alias.to_string()), body);
66 let skill_set = SkillSet::default();
67 inventory
69 .push(Item::new_from_asset_expect(
70 "common.items.consumable.potion_minor",
71 ))
72 .expect("Inventory has at least 2 slots left!");
73 inventory
74 .push(Item::new_from_asset_expect("common.items.food.cheese"))
75 .expect("Inventory has at least 1 slot left!");
76 inventory
77 .push_recipe_group(Item::new_from_asset_expect("common.items.recipes.default"))
78 .expect("New inventory should not already have default recipe group.");
79
80 let map_marker = None;
81
82 character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents {
83 body,
84 hardcore: hardcore.then_some(common::comp::Hardcore),
85 stats,
86 skill_set,
87 inventory,
88 waypoint,
89 pets: Vec::new(),
90 active_abilities: common::comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT),
91 map_marker,
92 });
93 Ok(())
94}
95
96pub fn edit_character(
97 entity: Entity,
98 player_uuid: String,
99 id: CharacterId,
100 character_alias: String,
101 body: Body,
102 character_updater: &mut WriteExpect<'_, CharacterUpdater>,
103) -> Result<(), CreationError> {
104 if !common::character::verify_character_name(&character_alias) {
105 return Err(CreationError::InvalidAlias);
106 }
107
108 if !matches!(body, Body::Humanoid(_)) {
109 return Err(CreationError::InvalidBody);
110 }
111
112 character_updater.edit_character(
113 entity,
114 player_uuid,
115 id,
116 Some(character_alias),
117 (body,),
118 None,
119 );
120 Ok(())
121}
122
123impl core::fmt::Display for CreationError {
125 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126 match self {
127 CreationError::InvalidWeapon => write!(
128 f,
129 "Invalid weapon.\nServer and client might be partially incompatible."
130 ),
131 CreationError::InvalidBody => write!(
132 f,
133 "Invalid Body.\nServer and client might be partially incompatible"
134 ),
135 CreationError::InvalidAlias => write!(
136 f,
137 "Invalid Alias.\nServer and client might be partially incompatible"
138 ),
139 }
140 }
141}