diff --git a/CHANGELOG.md b/CHANGELOG.md index 30384b5e59..ede0b763ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - There's now a checkbox in the graphics tab to opt-in to receiving lossily-compressed terrain colors. - /buff command which allows you to cast a buff on player - Warn the user with an animated red text in the second phase of a trade in which a party is offering nothing. +- /skill_preset command which allows you to apply skill presets ### Changed diff --git a/assets/server/manifests/presets.ron b/assets/server/manifests/presets.ron new file mode 100644 index 0000000000..ab7764d36a --- /dev/null +++ b/assets/server/manifests/presets.ron @@ -0,0 +1,129 @@ +// NOTE: Order matters here +// (you need to unlock skillgroup to be able to unlock skills in it +({ + "max": [ + // General skills + (General(HealthIncrease), 10), + (General(EnergyIncrease), 5), + (Roll(Cost), 2), + (Roll(Strength), 2), + (Roll(Duration), 2), + (Climb(Cost), 2), + (Climb(Speed), 2), + (Swim(Speed), 2), + + // Sword + (UnlockGroup(Weapon(Sword)), 1), + + (Sword(InterruptingAttacks), 1), + + (Sword(TsCombo), 1), + (Sword(TsDamage), 3), + (Sword(TsRegen), 2), + (Sword(TsSpeed), 3), + + (Sword(DCost), 2), + (Sword(DDrain), 2), + (Sword(DDamage), 2), + (Sword(DScaling), 3), + (Sword(DSpeed), 1), + (Sword(DInfinite), 1), + + (Sword(UnlockSpin), 1), + (Sword(SDamage), 2), + (Sword(SSpeed), 2), + (Sword(SCost), 2), + (Sword(SSpins), 2), + // Axe + (UnlockGroup(Weapon(Axe)), 1), + + (Axe(DsCombo), 1), + (Axe(DsDamage), 3), + (Axe(DsRegen), 2), + (Axe(DsSpeed), 3), + + (Axe(SInfinite), 1), + (Axe(SHelicopter), 1), + (Axe(SDamage), 3), + (Axe(SSpeed), 2), + (Axe(SCost), 2), + + (Axe(UnlockLeap), 1), + (Axe(LDamage), 2), + (Axe(LKnockback), 2), + (Axe(LCost), 2), + (Axe(LDistance), 2), + // Hammer + (UnlockGroup(Weapon(Hammer)), 1), + + (Hammer(SsKnockback), 2), + (Hammer(SsDamage), 3), + (Hammer(SsRegen), 2), + (Hammer(SsSpeed), 3), + + (Hammer(CDamage), 3), + (Hammer(CKnockback), 3), + (Hammer(CDrain), 2), + (Hammer(CSpeed), 2), + + (Hammer(UnlockLeap), 1), + (Hammer(LDamage), 2), + (Hammer(LCost), 2), + (Hammer(LDistance), 2), + (Hammer(LKnockback), 2), + (Hammer(LRange), 2), + // Bow + (UnlockGroup(Weapon(Bow)), 1), + + (Bow(ProjSpeed), 2), + (Bow(BDamage), 3), + (Bow(BRegen), 2), + + (Bow(CDamage), 3), + (Bow(CKnockback), 2), + (Bow(CProjSpeed), 2), + (Bow(CDrain), 2), + (Bow(CSpeed), 2), + (Bow(CMove), 2), + + (Bow(UnlockRepeater), 1), + (Bow(RGlide), 1), + (Bow(RDamage), 2), + (Bow(RArrows), 2), + (Bow(RCost), 2), + // Staff + (UnlockGroup(Weapon(Staff)), 1), + + (Staff(BDamage), 3), + (Staff(BRegen), 2), + (Staff(BRadius), 3), + + (Staff(FRange), 2), + (Staff(FDamage), 3), + (Staff(FDrain), 2), + (Staff(FVelocity), 2), + + (Staff(UnlockShockwave), 1), + (Staff(SDamage), 2), + (Staff(SKnockback), 2), + (Staff(SRange), 2), + (Staff(SCost), 2), + // Sceptre + (UnlockGroup(Weapon(Sceptre)), 1), + + (Sceptre(LDamage), 3), + (Sceptre(LRange), 2), + (Sceptre(LLifesteal), 3), + (Sceptre(LRegen), 2), + + (Sceptre(HHeal), 3), + (Sceptre(HCost), 2), + (Sceptre(HRange), 2), + + (Sceptre(UnlockAura), 1), + (Sceptre(AStrength), 2), + (Sceptre(ADuration), 2), + (Sceptre(ARange), 2), + (Sceptre(ACost), 2), + ], +}) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 911f30d32c..1f2fa1365d 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -1,6 +1,6 @@ use crate::{ assets, - comp::{self, buff::BuffKind}, + comp::{self, buff::BuffKind, Skill}, npc, terrain, }; use assets::AssetExt; @@ -92,6 +92,7 @@ pub enum ChatCommand { SetMotd, Site, SkillPoint, + SkillPreset, Spawn, Sudo, Tell, @@ -100,8 +101,8 @@ pub enum ChatCommand { Unban, Version, Waypoint, - Wiring, Whitelist, + Wiring, World, } @@ -157,6 +158,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[ ChatCommand::SetMotd, ChatCommand::Site, ChatCommand::SkillPoint, + ChatCommand::SkillPreset, ChatCommand::Spawn, ChatCommand::Sudo, ChatCommand::Tell, @@ -165,8 +167,8 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[ ChatCommand::Unban, ChatCommand::Version, ChatCommand::Waypoint, - ChatCommand::Wiring, ChatCommand::Whitelist, + ChatCommand::Wiring, ChatCommand::World, ]; @@ -178,6 +180,14 @@ impl assets::Asset for KitManifest { const EXTENSION: &'static str = "ron"; } +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +pub struct SkillPresetManifest(pub HashMap>); +impl assets::Asset for SkillPresetManifest { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + lazy_static! { pub static ref CHAT_SHORTCUTS: HashMap = [ ('f', ChatCommand::Faction), @@ -290,6 +300,22 @@ lazy_static! { Vec::new() } }; + + static ref PRESETS: HashMap> = { + if let Ok(presets) = SkillPresetManifest::load("server.manifests.presets") { + presets.read().0.clone() + } else { + warn!("Error while loading presets"); + HashMap::new() + } + }; + + static ref PRESET_LIST: Vec = { + let mut preset_list: Vec = PRESETS.keys().cloned().collect(); + preset_list.push("clear".to_owned()); + + preset_list + }; } impl ChatCommand { @@ -543,6 +569,11 @@ impl ChatCommand { "Give yourself skill points for a particular skill tree", Admin, ), + ChatCommand::SkillPreset => cmd( + vec![Enum("preset_name", PRESET_LIST.to_vec(), Required)], + "Gives your character desired skills.", + Admin, + ), ChatCommand::Spawn => cmd( vec![ Enum("alignment", ALIGNMENTS.clone(), Required), @@ -649,6 +680,7 @@ impl ChatCommand { ChatCommand::SetMotd => "set_motd", ChatCommand::Site => "site", ChatCommand::SkillPoint => "skill_point", + ChatCommand::SkillPreset => "skill_preset", ChatCommand::Spawn => "spawn", ChatCommand::Sudo => "sudo", ChatCommand::Tell => "tell", @@ -754,7 +786,7 @@ pub enum ArgumentSpec { /// * suggested tab-completion /// * whether it's optional Float(&'static str, f32, Requirement), - /// The argument is a float. The associated values are + /// The argument is an integer. The associated values are /// * label /// * suggested tab-completion /// * whether it's optional diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 61fc9c55af..4ba7c43dd9 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -143,6 +143,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler { ChatCommand::SetMotd => handle_set_motd, ChatCommand::Site => handle_site, ChatCommand::SkillPoint => handle_skill_point, + ChatCommand::SkillPreset => handle_skill_preset, ChatCommand::Spawn => handle_spawn, ChatCommand::Sudo => handle_sudo, ChatCommand::Tell => handle_tell, @@ -2714,3 +2715,61 @@ fn cast_buff(kind: &str, data: BuffData, server: &mut Server, target: EcsEntity) } fn parse_buffkind(buff: &str) -> Option { BUFF_PARSER.get(buff).copied() } + +fn handle_skill_preset( + server: &mut Server, + _client: EcsEntity, + target: EcsEntity, + args: String, + action: &ChatCommand, +) -> CmdResult<()> { + if let Some(preset) = scan_fmt_some!(&args, &action.arg_fmt(), String) { + if let Some(mut skill_set) = server + .state + .ecs_mut() + .write_storage::() + .get_mut(target) + { + match preset.as_str() { + "clear" => { + clear_skillset(&mut skill_set); + Ok(()) + }, + preset => set_skills(&mut skill_set, preset), + } + } else { + Err("Player has no stats!".into()) + } + } else { + Err(action.help_string()) + } +} + +fn clear_skillset(skill_set: &mut comp::SkillSet) { *skill_set = comp::SkillSet::default(); } + +fn set_skills(skill_set: &mut comp::SkillSet, preset: &str) -> CmdResult<()> { + let presets = + if let Ok(presets) = common::cmd::SkillPresetManifest::load("server.manifests.presets") { + presets.read().0.clone() + } else { + return Err("Error while loading presets".to_owned()); + }; + if let Some(preset) = presets.get(preset) { + for (skill, level) in preset { + let group = if let Some(group) = skill.skill_group_kind() { + group + } else { + warn!("Skill in preset doesn't exist in any group"); + return Err("Preset is broken".to_owned()); + }; + for _ in 0..*level { + let cost = skill_set.skill_cost(*skill); + skill_set.add_skill_points(group, cost); + skill_set.unlock_skill(*skill); + } + } + Ok(()) + } else { + Err("Such preset doesn't exist".to_owned()) + } +}