veloren_server/
character_creator.rs1use crate::persistence::{PersistedComponents, character_updater::CharacterUpdater};
2use common::{
3 character::CharacterId,
4 comp::{
5 BASE_ABILITY_LIMIT, Body, 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)]
25pub enum CreationError {
26 InvalidWeapon,
27 InvalidBody,
28}
29
30pub fn create_character(
31 entity: Entity,
32 player_uuid: String,
33 character_alias: String,
34 character_mainhand: Option<String>,
35 character_offhand: Option<String>,
36 body: Body,
37 hardcore: bool,
38 character_updater: &mut WriteExpect<'_, CharacterUpdater>,
39 waypoint: Option<Waypoint>,
40) -> Result<(), CreationError> {
41 if !matches!(body, Body::Humanoid(_)) {
46 return Err(CreationError::InvalidBody);
47 }
48 if !VALID_STARTER_ITEMS.contains(&[character_mainhand.as_deref(), character_offhand.as_deref()])
49 {
50 return Err(CreationError::InvalidWeapon);
51 };
52 let loadout = LoadoutBuilder::empty()
54 .defaults()
55 .active_mainhand(character_mainhand.map(|x| Item::new_from_asset_expect(&x)))
56 .active_offhand(character_offhand.map(|x| Item::new_from_asset_expect(&x)))
57 .build();
58 let mut inventory = Inventory::with_loadout_humanoid(loadout);
59
60 let stats = Stats::new(character_alias.to_string(), body);
61 let skill_set = SkillSet::default();
62 inventory
64 .push(Item::new_from_asset_expect(
65 "common.items.consumable.potion_minor",
66 ))
67 .expect("Inventory has at least 2 slots left!");
68 inventory
69 .push(Item::new_from_asset_expect("common.items.food.cheese"))
70 .expect("Inventory has at least 1 slot left!");
71 inventory
72 .push_recipe_group(Item::new_from_asset_expect("common.items.recipes.default"))
73 .expect("New inventory should not already have default recipe group.");
74
75 let map_marker = None;
76
77 character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents {
78 body,
79 hardcore: hardcore.then_some(common::comp::Hardcore),
80 stats,
81 skill_set,
82 inventory,
83 waypoint,
84 pets: Vec::new(),
85 active_abilities: common::comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT),
86 map_marker,
87 });
88 Ok(())
89}
90
91pub fn edit_character(
92 entity: Entity,
93 player_uuid: String,
94 id: CharacterId,
95 character_alias: String,
96 body: Body,
97 character_updater: &mut WriteExpect<'_, CharacterUpdater>,
98) -> Result<(), CreationError> {
99 if !matches!(body, Body::Humanoid(_)) {
100 return Err(CreationError::InvalidBody);
101 }
102
103 character_updater.edit_character(entity, player_uuid, id, character_alias, (body,));
104 Ok(())
105}
106
107impl core::fmt::Display for CreationError {
109 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110 match self {
111 CreationError::InvalidWeapon => write!(
112 f,
113 "Invalid weapon.\nServer and client might be partially incompatible."
114 ),
115 CreationError::InvalidBody => write!(
116 f,
117 "Invalid Body.\nServer and client might be partially incompatible"
118 ),
119 }
120 }
121}