diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index ec7fd52fd4..3a4ff30abe 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -953,6 +953,11 @@ secondary: Simple(None, "common.abilities.empty.basic"), abilities: [], ), + Custom("Admin's Eagle"): ( + primary: Simple(None, "common.abilities.debug.glide_speeder"), + secondary: Simple(None, "common.abilities.debug.glide_boost"), + abilities: [], + ), // Adlets // TODO: Do we want to eventually convert these to simple variants of weapons? Custom("Adlet Hunter"): ( diff --git a/assets/common/abilities/debug/glide_boost.ron b/assets/common/abilities/debug/glide_boost.ron new file mode 100644 index 0000000000..d5d53ab4b2 --- /dev/null +++ b/assets/common/abilities/debug/glide_boost.ron @@ -0,0 +1,3 @@ +GlideBoost( + booster: Upward(400.0), +) diff --git a/assets/common/abilities/debug/glide_speeder.ron b/assets/common/abilities/debug/glide_speeder.ron new file mode 100644 index 0000000000..e3bbdee719 --- /dev/null +++ b/assets/common/abilities/debug/glide_speeder.ron @@ -0,0 +1,3 @@ +GlideBoost( + booster: Forward(0.75), +) diff --git a/assets/common/abilities/debug/upboost.ron b/assets/common/abilities/debug/upboost.ron index 643ee1f97f..596bc84360 100644 --- a/assets/common/abilities/debug/upboost.ron +++ b/assets/common/abilities/debug/upboost.ron @@ -3,4 +3,4 @@ Boost( only_up: true, speed: 250.0, max_exit_velocity: 70.0, -) \ No newline at end of file +) diff --git a/assets/common/item_i18n_manifest.ron b/assets/common/item_i18n_manifest.ron index 7611850fb3..560c80b549 100644 --- a/assets/common/item_i18n_manifest.ron +++ b/assets/common/item_i18n_manifest.ron @@ -1974,6 +1974,9 @@ Simple( "common.items.debug.admin", ): "armor-tabard-admin_tabard", + Simple( + "common.items.debug.glider", + ): "other-glider-vroom-debug", Simple( "common.items.debug.admin_back", ): "armor-misc-back-admin_back", @@ -7099,4 +7102,4 @@ ), ): "weapon-hammer-hammer-cobalt-1h", }, -) \ No newline at end of file +) diff --git a/assets/common/items/debug/glider.ron b/assets/common/items/debug/glider.ron new file mode 100644 index 0000000000..eb4ab5f5f2 --- /dev/null +++ b/assets/common/items/debug/glider.ron @@ -0,0 +1,8 @@ +ItemDef( + legacy_name: "Vroom Glider", + legacy_description: "goes brrr.", + kind: Glider, + quality: Debug, + tags: [], + ability_spec: Some(Custom("Admin's Eagle")), +) diff --git a/assets/server/manifests/kits.ron b/assets/server/manifests/kits.ron index ca25cd024a..84d518b786 100644 --- a/assets/server/manifests/kits.ron +++ b/assets/server/manifests/kits.ron @@ -5,6 +5,7 @@ (Item("common.items.debug.admin_sword"),1), (Item("common.items.debug.velorite_bow_debug"), 1), (Item("common.items.debug.admin"),1), + (Item("common.items.debug.glider"),1), (Item("common.items.debug.golden_cheese"),100), ], "consumables": [ diff --git a/assets/voxygen/i18n/en/hud/ability.ftl b/assets/voxygen/i18n/en/hud/ability.ftl index 6dae14834b..0ae34020e5 100644 --- a/assets/voxygen/i18n/en/hud/ability.ftl +++ b/assets/voxygen/i18n/en/hud/ability.ftl @@ -1,20 +1,7 @@ -common-abilities-debug-possess = Possessing Arrow - .desc = Shoots a poisonous arrow. Lets you control your target. -common-abilities-debug-evolve = Evolve - .desc = You become your better self. -common-abilities-hammer-leap = Smash of Doom - .desc = An AOE attack with knockback. Leaps to position of cursor. -common-abilities-bow-shotgun = Burst - .desc = Launches a burst of arrows. -common-abilities-staff-fireshockwave = Ring of Fire - .desc = Ignites the ground with fiery shockwave. -common-abilities-sceptre-wardingaura = Warding Aura - .desc = Wards your allies against enemy attacks. - -# Internal terms, currently only used in zh-Hans. -# If we remove them here, they also get auto-removed in zh-Hans, -# so please keep them, even when not used in English file. -# See https://github.com/WeblateOrg/weblate/issues/9895 +## Internal terms, currently only used in zh-Hans. +## If we remove them here, they also get auto-removed in zh-Hans, +## so please keep them, even when not used in English file. +## See https://github.com/WeblateOrg/weblate/issues/9895 -heavy_stance = "" -agile_stance = "" @@ -39,7 +26,27 @@ common-abilities-sceptre-wardingaura = Warding Aura -enter_stance = "" -require_stance = "" -# Sword abilities +## Debug abilities +common-abilities-debug-possess = Possessing Arrow + .desc = Shoots a poisonous arrow. Lets you control your target. +common-abilities-debug-evolve = Evolve + .desc = You become your better self. +common-abilities-debug-glide_boost = Vroom + .desc = Gives you the force to reach the sky +common-abilities-debug-glide_speeder = Vroom + .desc = Gives you the force to reach wherever your eyes look + +## Hotbar abilities +common-abilities-hammer-leap = Smash of Doom + .desc = An AOE attack with knockback. Leaps to position of cursor. +common-abilities-bow-shotgun = Burst + .desc = Launches a burst of arrows +common-abilities-staff-fireshockwave = Ring of Fire + .desc = Ignites the ground with fiery shockwave. +common-abilities-sceptre-wardingaura = Warding Aura + .desc = Wards your allies against enemy attacks. + +## Sword abilities veloren-core-pseudo_abilities-sword-heavy_stance = Heavy Stance .desc = Attacks in this stance can stagger enemies and deal more damage to staggered enemies but are slower. veloren-core-pseudo_abilities-sword-agile_stance = Agile Stance @@ -295,7 +302,7 @@ common-abilities-sword-cleaving_sky_splitter = Sky Splitter A powerful strike that purportedly can even split the sky, but will split through enemies. Requires cleaving stance. -# Axe abilities +## Axe abilities common-abilities-axe-triple_chop = Triple Chop .desc = Three quick strikes. diff --git a/assets/voxygen/i18n/en/hud/skills.ftl b/assets/voxygen/i18n/en/hud/skills.ftl index 4052729e57..4ea32f4f56 100644 --- a/assets/voxygen/i18n/en/hud/skills.ftl +++ b/assets/voxygen/i18n/en/hud/skills.ftl @@ -1,3 +1,12 @@ +## internal terms, currently only used in ES +## If we remove them here, they also get auto-removed in ES, +## so please keep them, even when not used in English file. +## See https://github.com/WeblateOrg/weblate/issues/9895 +-hud-skill-sc_wardaura_title = "" +-hud-skill-bow_shotgun_title = "" +-hud-skill-st_shockwave_title = "" + +## SkillTree UI hud-rank_up = New skillpoint hud-skill-sp_available = { $number -> @@ -13,11 +22,6 @@ hud-skill-inc_health = Increases your maximum health by { $boost } points.{ $SP hud-skill-inc_energy_title = Increase energy hud-skill-inc_energy = Increases your maximum energy by { $boost } points.{ $SP } -# internally translations, used by ES file currently --hud-skill-sc_wardaura_title = "" --hud-skill-bow_shotgun_title = "" --hud-skill-st_shockwave_title = "" - hud-skill-unlck_sword_title = Sword proficiency hud-skill-unlck_sword = Unlocks the sword skill tree.{ $SP } hud-skill-unlck_axe_title = Axe proficiency diff --git a/assets/voxygen/i18n/en/item/admin.ftl b/assets/voxygen/i18n/en/item/admin.ftl index 376703da00..6f8ec748e6 100644 --- a/assets/voxygen/i18n/en/item/admin.ftl +++ b/assets/voxygen/i18n/en/item/admin.ftl @@ -51,3 +51,5 @@ weapon-sword-frost-admin_sword = Admin Greatsword weapon-bow-velorite-debug = Admin Velorite Bow .desc = Infused with Velorite power. +other-glider-vroom-debug = Vroom Glider + .desc = goes brrr diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index a2eca4b5d2..c984458d70 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -3816,6 +3816,10 @@ "voxel.weapon.tool.broom_belzeshrub_purple", (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1, ), + Simple("common.items.debug.glider"): VoxTrans( + "voxel.glider.cloverleaf", + (-2.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.9, + ), // Misc Simple("common.items.weapons.tool.golf_club"): VoxTrans( "voxel.weapon.tool.golf_club", diff --git a/assets/voxygen/voxel/humanoid_glider_manifest.ron b/assets/voxygen/voxel/humanoid_glider_manifest.ron index 03e2984150..6bc0e601fe 100644 --- a/assets/voxygen/voxel/humanoid_glider_manifest.ron +++ b/assets/voxygen/voxel/humanoid_glider_manifest.ron @@ -68,5 +68,9 @@ vox_spec: ("glider.winter_wings", (-26.0, -26.0, 0.0)), color: None ), + "common.items.debug.glider": ( + vox_spec: ("glider.cloverleaf", (-19.0, -5.0, 0.0)), + color: None + ), }, )) diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 2e26ed2d06..cb9e62e62c 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -72,6 +72,25 @@ impl Default for ActiveAbilities { } } +// make it pub, for UI stuff, if you want +enum AbilitySource { + Weapons, + Glider, +} + +impl AbilitySource { + // Get all needed data here and pick the right ability source + // + // make it pub, for UI stuff, if you want + fn determine(char_state: Option<&CharacterState>) -> Self { + if char_state.is_some_and(|c| c.is_glide_wielded()) { + Self::Glider + } else { + Self::Weapons + } + } +} + impl ActiveAbilities { pub fn from_auxiliary( auxiliary_sets: HashMap>, @@ -176,7 +195,7 @@ impl ActiveAbilities { let ability_set = |equip_slot| { inv.and_then(|inv| inv.equipped(equip_slot)) - .map(|i| &i.item_config_expect().abilities) + .and_then(|i| i.item_config().map(|c| &c.abilities)) }; let scale_ability = |ability: CharacterAbility, equip_slot| { @@ -194,100 +213,55 @@ impl ActiveAbilities { context_index, }; - match ability { - Ability::ToolGuard => { - let equip_slot = combat::get_equip_slot_by_block_priority(inv); - ability_set(equip_slot) - .and_then(|abilities| { - abilities - .guard(Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, equip_slot), - matches!(equip_slot, EquipSlot::ActiveOffhand), - spec_ability(i), - ) - }) + let inst_ability = |slot: EquipSlot, offhand: bool| { + ability_set(slot).and_then(|abilities| { + use AbilityInput as I; + + let dispatched = match input { + I::Guard => abilities.guard(Some(skill_set), context), + I::Primary => abilities.primary(Some(skill_set), context), + I::Secondary => abilities.secondary(Some(skill_set), context), + I::Auxiliary(index) => abilities.auxiliary(index, Some(skill_set), context), + I::Movement => return None, + }; + + dispatched + .map(|(a, i)| (a.ability.clone(), i)) + .map(|(a, i)| (scale_ability(a, slot), offhand, spec_ability(i))) + }) + }; + + let source = AbilitySource::determine(char_state); + match source { + AbilitySource::Glider => match ability { + Ability::ToolGuard => None, + Ability::ToolPrimary => inst_ability(EquipSlot::Glider, false), + Ability::ToolSecondary => inst_ability(EquipSlot::Glider, false), + Ability::SpeciesMovement => None, + Ability::MainWeaponAux(_) | Ability::OffWeaponAux(_) => None, + Ability::Empty => None, + }, + AbilitySource::Weapons => match ability { + Ability::ToolGuard => { + let equip_slot = combat::get_equip_slot_by_block_priority(inv); + inst_ability(equip_slot, matches!(equip_slot, EquipSlot::ActiveOffhand)) + }, + Ability::ToolPrimary => inst_ability(EquipSlot::ActiveMainhand, false), + Ability::ToolSecondary => inst_ability(EquipSlot::ActiveOffhand, true) + .or_else(|| inst_ability(EquipSlot::ActiveMainhand, false)), + Ability::SpeciesMovement => matches!(body, Some(Body::Humanoid(_))) + .then(|| CharacterAbility::default_roll(char_state)) + .map(|ability| { + ( + ability.adjusted_by_skills(skill_set, None), + false, + spec_ability(None), + ) + }), + Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand, false), + Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand, true), + Ability::Empty => None, }, - Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand) - .and_then(|abilities| { - abilities - .primary(Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, EquipSlot::ActiveMainhand), - false, - spec_ability(i), - ) - }), - Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand) - .and_then(|abilities| { - abilities - .secondary(Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, EquipSlot::ActiveOffhand), - true, - spec_ability(i), - ) - }) - .or_else(|| { - ability_set(EquipSlot::ActiveMainhand) - .and_then(|abilities| { - abilities - .secondary(Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, EquipSlot::ActiveMainhand), - false, - spec_ability(i), - ) - }) - }), - Ability::SpeciesMovement => matches!(body, Some(Body::Humanoid(_))) - .then(|| CharacterAbility::default_roll(char_state)) - .map(|ability| { - ( - ability.adjusted_by_skills(skill_set, None), - false, - spec_ability(None), - ) - }), - Ability::MainWeaponAux(index) => ability_set(EquipSlot::ActiveMainhand) - .and_then(|abilities| { - abilities - .auxiliary(index, Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, EquipSlot::ActiveMainhand), - false, - spec_ability(i), - ) - }), - Ability::OffWeaponAux(index) => ability_set(EquipSlot::ActiveOffhand) - .and_then(|abilities| { - abilities - .auxiliary(index, Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, EquipSlot::ActiveOffhand), - true, - spec_ability(i), - ) - }), - Ability::Empty => None, } } @@ -296,9 +270,9 @@ impl ActiveAbilities { skill_set: Option<&'a SkillSet>, equip_slot: EquipSlot, ) -> impl Iterator + 'a { - inv.and_then(|inv| inv.equipped(equip_slot)) + inv.and_then(|inv| inv.equipped(equip_slot).and_then(|i| i.item_config())) .into_iter() - .flat_map(|i| &i.item_config_expect().abilities.abilities) + .flat_map(|config| &config.abilities.abilities) .enumerate() .filter_map(move |(i, a)| match a { AbilityKind::Simple(skill, _) => skill @@ -338,6 +312,7 @@ impl ActiveAbilities { } } +#[derive(Copy, Clone)] pub enum AbilityInput { Guard, Primary, @@ -360,15 +335,29 @@ pub enum Ability { } impl Ability { + fn try_input(&self) -> Option { + let input = match self { + Self::ToolGuard => AbilityInput::Guard, + Self::ToolPrimary => AbilityInput::Primary, + Self::ToolSecondary => AbilityInput::Secondary, + Self::SpeciesMovement => AbilityInput::Movement, + Self::OffWeaponAux(idx) | Self::MainWeaponAux(idx) => AbilityInput::Auxiliary(*idx), + Self::Empty => return None, + }; + + Some(input) + } + pub fn ability_id<'a>( self, + char_state: Option<&CharacterState>, inv: Option<&'a Inventory>, - skillset: Option<&'a SkillSet>, + skill_set: Option<&'a SkillSet>, context: &AbilityContext, ) -> Option<&'a str> { let ability_set = |equip_slot| { inv.and_then(|inv| inv.equipped(equip_slot)) - .map(|i| &i.item_config_expect().abilities) + .and_then(|i| i.item_config().map(|c| &c.abilities)) }; let contextual_id = |kind: Option<&'a AbilityKind<_>>| -> Option<&'a str> { @@ -383,62 +372,60 @@ impl Ability { } }; - match self { - Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv)) - .and_then(|abilities| { - abilities - .guard(skillset, context) - .map(|a| a.0.id.as_str()) - .or_else(|| { - abilities - .guard - .as_ref() - .and_then(|g| contextual_id(Some(g))) - }) - }), - Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| { - abilities - .primary(skillset, context) - .map(|a| a.0.id.as_str()) - .or_else(|| contextual_id(Some(&abilities.primary))) - }), - Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand) - .and_then(|abilities| { - abilities - .secondary(skillset, context) - .map(|a| a.0.id.as_str()) - .or_else(|| contextual_id(Some(&abilities.secondary))) - }) - .or_else(|| { - ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| { - abilities - .secondary(skillset, context) - .map(|a| a.0.id.as_str()) - .or_else(|| contextual_id(Some(&abilities.secondary))) + let inst_ability = |slot: EquipSlot| { + ability_set(slot).and_then(|abilities| { + use AbilityInput as I; + + let dispatched = match self.try_input()? { + I::Guard => abilities.guard(skill_set, context), + I::Primary => abilities.primary(skill_set, context), + I::Secondary => abilities.secondary(skill_set, context), + I::Auxiliary(index) => abilities.auxiliary(index, skill_set, context), + I::Movement => return None, + }; + + dispatched + .map(|(a, _)| a.id.as_str()) + .or_else(|| match self.try_input()? { + I::Guard => abilities + .guard + .as_ref() + .and_then(|g| contextual_id(Some(g))), + I::Primary => contextual_id(Some(&abilities.primary)), + I::Secondary => contextual_id(Some(&abilities.secondary)), + I::Auxiliary(index) => contextual_id(abilities.abilities.get(index)), + I::Movement => None, }) - }), - Ability::SpeciesMovement => None, // TODO: Make not None - Ability::MainWeaponAux(index) => { - ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| { - abilities - .auxiliary(index, skillset, context) - .map(|a| a.0.id.as_str()) - .or_else(|| contextual_id(abilities.abilities.get(index))) - }) + }) + }; + + let source = AbilitySource::determine(char_state); + match source { + AbilitySource::Glider => match self { + Ability::ToolGuard => None, + Ability::ToolPrimary => inst_ability(EquipSlot::Glider), + Ability::ToolSecondary => inst_ability(EquipSlot::Glider), + Ability::SpeciesMovement => None, // TODO: Make not None + Ability::MainWeaponAux(_) | Ability::OffWeaponAux(_) => None, + Ability::Empty => None, }, - Ability::OffWeaponAux(index) => { - ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| { - abilities - .auxiliary(index, skillset, context) - .map(|a| a.0.id.as_str()) - .or_else(|| contextual_id(abilities.abilities.get(index))) - }) + AbilitySource::Weapons => match self { + Ability::ToolGuard => { + let equip_slot = combat::get_equip_slot_by_block_priority(inv); + inst_ability(equip_slot) + }, + Ability::ToolPrimary => inst_ability(EquipSlot::ActiveMainhand), + Ability::ToolSecondary => inst_ability(EquipSlot::ActiveMainhand) + .or_else(|| inst_ability(EquipSlot::ActiveOffhand)), + Ability::SpeciesMovement => None, // TODO: Make not None + Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand), + Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand), + Ability::Empty => None, }, - Ability::Empty => None, } } - pub fn is_from_tool(&self) -> bool { + pub fn is_from_wielded(&self) -> bool { match self { Ability::ToolPrimary | Ability::ToolSecondary @@ -449,6 +436,7 @@ impl Ability { } } } + #[derive(Copy, Clone, Serialize, Deserialize, Debug)] pub enum GuardAbility { Tool, @@ -471,10 +459,14 @@ pub struct SpecifiedAbility { } impl SpecifiedAbility { - pub fn ability_id(self, inv: Option<&Inventory>) -> Option<&str> { + pub fn ability_id<'a>( + self, + char_state: Option<&CharacterState>, + inv: Option<&'a Inventory>, + ) -> Option<&'a str> { let ability_set = |equip_slot| { inv.and_then(|inv| inv.equipped(equip_slot)) - .map(|i| &i.item_config_expect().abilities) + .and_then(|i| i.item_config().map(|c| &c.abilities)) }; fn ability_id(spec_ability: SpecifiedAbility, ability: &AbilityKind) -> &str { @@ -490,23 +482,41 @@ impl SpecifiedAbility { } } - match self.ability { - Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand) - .map(|abilities| ability_id(self, &abilities.primary)), - Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand) - .map(|abilities| ability_id(self, &abilities.secondary)) - .or_else(|| { - ability_set(EquipSlot::ActiveMainhand) - .map(|abilities| ability_id(self, &abilities.secondary)) - }), - Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv)) - .and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a))), - Ability::SpeciesMovement => None, // TODO: Make not None - Ability::MainWeaponAux(index) => ability_set(EquipSlot::ActiveMainhand) - .and_then(|abilities| abilities.abilities.get(index).map(|a| ability_id(self, a))), - Ability::OffWeaponAux(index) => ability_set(EquipSlot::ActiveOffhand) - .and_then(|abilities| abilities.abilities.get(index).map(|a| ability_id(self, a))), - Ability::Empty => None, + let inst_ability = |slot: EquipSlot| { + ability_set(slot).and_then(|abilities| { + use AbilityInput as I; + + let dispatched = match self.ability.try_input()? { + I::Guard => abilities.guard.as_ref(), + I::Primary => Some(&abilities.primary), + I::Secondary => Some(&abilities.secondary), + I::Auxiliary(index) => abilities.abilities.get(index), + I::Movement => return None, + }; + dispatched.map(|a| ability_id(self, a)) + }) + }; + + let source = AbilitySource::determine(char_state); + match source { + AbilitySource::Glider => match self.ability { + Ability::ToolGuard => None, + Ability::ToolPrimary => inst_ability(EquipSlot::Glider), + Ability::ToolSecondary => inst_ability(EquipSlot::Glider), + Ability::SpeciesMovement => None, + Ability::MainWeaponAux(_) | Ability::OffWeaponAux(_) => None, + Ability::Empty => None, + }, + AbilitySource::Weapons => match self.ability { + Ability::ToolGuard => inst_ability(combat::get_equip_slot_by_block_priority(inv)), + Ability::ToolPrimary => inst_ability(EquipSlot::ActiveMainhand), + Ability::ToolSecondary => inst_ability(EquipSlot::ActiveOffhand) + .or_else(|| inst_ability(EquipSlot::ActiveMainhand)), + Ability::SpeciesMovement => None, // TODO: Make not None + Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand), + Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand), + Ability::Empty => None, + }, } } } @@ -514,13 +524,14 @@ impl SpecifiedAbility { #[derive(Copy, Clone, Serialize, Deserialize, Debug)] pub enum PrimaryAbility { Tool, + Glider, Empty, } impl From for Ability { fn from(primary: PrimaryAbility) -> Self { match primary { - PrimaryAbility::Tool => Ability::ToolPrimary, + PrimaryAbility::Tool | PrimaryAbility::Glider => Ability::ToolPrimary, PrimaryAbility::Empty => Ability::Empty, } } @@ -529,13 +540,14 @@ impl From for Ability { #[derive(Copy, Clone, Serialize, Deserialize, Debug)] pub enum SecondaryAbility { Tool, + Glider, Empty, } impl From for Ability { fn from(primary: SecondaryAbility) -> Self { match primary { - SecondaryAbility::Tool => Ability::ToolSecondary, + SecondaryAbility::Tool | SecondaryAbility::Glider => Ability::ToolSecondary, SecondaryAbility::Empty => Ability::Empty, } } @@ -708,6 +720,11 @@ pub enum CharacterAbility { #[serde(default)] meta: AbilityMeta, }, + GlideBoost { + booster: glide::Boost, + #[serde(default)] + meta: AbilityMeta, + }, DashMelee { energy_cost: f32, energy_drain: f32, @@ -1125,6 +1142,7 @@ impl CharacterAbility { }, CharacterAbility::ComboMeleeDeprecated { .. } | CharacterAbility::Boost { .. } + | CharacterAbility::GlideBoost { .. } | CharacterAbility::BasicBeam { .. } | CharacterAbility::Blink { .. } | CharacterAbility::Music { .. } @@ -1688,6 +1706,7 @@ impl CharacterAbility { *buildup_duration /= stats.speed; *recover_duration /= stats.speed; }, + GlideBoost { .. } => {}, } self } @@ -1724,6 +1743,7 @@ impl CharacterAbility { } }, Boost { .. } + | GlideBoost { .. } | ComboMeleeDeprecated { .. } | Blink { .. } | Music { .. } @@ -1773,6 +1793,7 @@ impl CharacterAbility { | RiposteMelee { .. } | BasicBeam { .. } | Boost { .. } + | GlideBoost { .. } | ComboMeleeDeprecated { .. } | Blink { .. } | Music { .. } @@ -1801,6 +1822,7 @@ impl CharacterAbility { | SelfBuff { meta, .. } | BasicBeam { meta, .. } | Boost { meta, .. } + | GlideBoost { meta, .. } | ComboMeleeDeprecated { meta, .. } | ComboMelee2 { meta, .. } | Blink { meta, .. } @@ -2312,6 +2334,13 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { }, timer: Duration::default(), }), + CharacterAbility::GlideBoost { booster, meta: _ } => { + let scale = data.body.dimensions().z.sqrt(); + let mut glide_data = glide::Data::new(scale * 4.5, scale, *data.ori); + glide_data.booster = Some(*booster); + + CharacterState::Glide(glide_data) + }, CharacterAbility::DashMelee { energy_cost: _, energy_drain, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index d19ce99033..f6b5d18f47 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -228,6 +228,13 @@ impl CharacterState { } } + pub fn is_glide_wielded(&self) -> bool { + matches!( + self, + CharacterState::Glide { .. } | CharacterState::GlideWield { .. } + ) + } + pub fn is_stealthy(&self) -> bool { matches!( self, diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 49b237bc96..04365b2756 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -729,38 +729,47 @@ impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig { // TODO: Either remove msm or use it as argument in fn kind (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest), ) -> Result { - if let ItemKind::Tool(tool) = &*item.kind() { - // If no custom ability set is specified, fall back to abilityset of tool kind. - let tool_default = |tool_kind| { - let key = &AbilitySpec::Tool(tool_kind); - ability_map.get_ability_set(key) - }; - let abilities = if let Some(set_key) = item.ability_spec() { - if let Some(set) = ability_map.get_ability_set(&set_key) { + match &*item.kind() { + ItemKind::Tool(tool) => { + // If no custom ability set is specified, fall back to abilityset of tool kind. + let tool_default = |tool_kind| { + let key = &AbilitySpec::Tool(tool_kind); + ability_map.get_ability_set(key) + }; + let abilities = if let Some(set_key) = item.ability_spec() { + if let Some(set) = ability_map.get_ability_set(&set_key) { + set.clone() + .modified_by_tool(tool, item.stats_durability_multiplier()) + } else { + error!( + "Custom ability set: {:?} references non-existent set, falling back \ + to default ability set.", + set_key + ); + tool_default(tool.kind).cloned().unwrap_or_default() + } + } else if let Some(set) = tool_default(tool.kind) { set.clone() .modified_by_tool(tool, item.stats_durability_multiplier()) } else { error!( - "Custom ability set: {:?} references non-existent set, falling back to \ - default ability set.", - set_key + "No ability set defined for tool: {:?}, falling back to default ability \ + set.", + tool.kind ); - tool_default(tool.kind).cloned().unwrap_or_default() - } - } else if let Some(set) = tool_default(tool.kind) { - set.clone() - .modified_by_tool(tool, item.stats_durability_multiplier()) - } else { - error!( - "No ability set defined for tool: {:?}, falling back to default ability set.", - tool.kind - ); - Default::default() - }; + Default::default() + }; - Ok(ItemConfig { abilities }) - } else { - Err(ItemConfigError::BadItemKind) + Ok(ItemConfig { abilities }) + }, + ItemKind::Glider => item + .ability_spec() + .and_then(|set_key| ability_map.get_ability_set(&set_key)) + .map(|abilities| ItemConfig { + abilities: abilities.clone(), + }) + .ok_or(ItemConfigError::BadItemKind), + _ => Err(ItemConfigError::BadItemKind), } } } @@ -1324,11 +1333,7 @@ impl Item { pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots } - pub fn item_config_expect(&self) -> &ItemConfig { - self.item_config - .as_ref() - .expect("Item was expected to have an ItemConfig") - } + pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() } pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() } diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 744192f56a..e35e694514 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -37,14 +37,13 @@ pub enum ToolKind { Farming, Pick, Shovel, + /// Music Instruments + Instrument, // npcs /// Intended for invisible weapons (e.g. a creature using its claws or /// biting) Natural, /// This is an placeholder item, it is used by non-humanoid npcs to attack - /// Music Instruments - Instrument, - /// This is an placeholder item, it is used by non-humanoid npcs to attack Empty, } diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index 04bb31c8f9..723ea82a45 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -18,6 +18,14 @@ use vek::*; const PITCH_SLOW_TIME: f32 = 0.5; +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Boost { + /// Slowly increases XY speed + Forward(f32), + /// Gives Z impulse + Upward(f32), +} + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { /// The aspect ratio is the ratio of the span squared to actual planform @@ -28,6 +36,7 @@ pub struct Data { last_vel: Vel, pub timer: Duration, inputs_disabled: bool, + pub booster: Option, } impl Data { @@ -47,6 +56,7 @@ impl Data { last_vel: Vel::zero(), timer: Duration::default(), inputs_disabled: true, + booster: None, } } @@ -75,12 +85,32 @@ impl Data { } impl CharacterBehavior for Data { - fn behavior(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { + fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); - // If player is on ground, end glide + handle_glider_input_or(data, &mut update, output_events, |_, _| {}); + + // If switched state, let it do its thing + if !matches!(update.character, CharacterState::Glide { .. }) { + return update; + } + + // Alternatively, we could end up in the same state, but gained booster + let gained_booster = if let CharacterState::Glide(Data { + booster: Some(booster), + .. + }) = update.character + { + Some(booster) + } else { + None + }; + + // If player is on the ground and effectively doesn't have any gliding + // power left, end the glide if data.physics.on_ground.is_some() && (data.vel.0 - data.physics.ground_vel).magnitude_squared() < 2_f32.powi(2) + && gained_booster.is_none() { update.character = CharacterState::GlideWield(glide_wield::Data::from(data)); } else if data.physics.in_liquid().is_some() @@ -189,11 +219,34 @@ impl CharacterBehavior for Data { slerp_s, ) }; + + // If we gained a booster + if let Some(booster) = gained_booster { + match booster { + Boost::Upward(speed) => { + update.vel.0.z += speed * data.dt.0; + }, + Boost::Forward(speed) => { + if data.physics.on_ground.is_some() { + // quality of life hack: help with starting + // + // other velocities are intentionally ignored + update.vel.0.z += 500.0 * data.dt.0; + } else { + update.vel.0.x *= 1.0 + speed * data.dt.0; + update.vel.0.y *= 1.0 + speed * data.dt.0; + } + }, + } + }; + update.character = CharacterState::Glide(Self { ori, last_vel: *data.vel, timer: tick_attack_or_default(data, self.timer, None), inputs_disabled, + // booster is consumed, set to None + booster: None, ..*self }); } diff --git a/common/src/states/glide_wield.rs b/common/src/states/glide_wield.rs index d775ed1d2a..2745a26cc0 100644 --- a/common/src/states/glide_wield.rs +++ b/common/src/states/glide_wield.rs @@ -66,7 +66,7 @@ impl CharacterBehavior for Data { if input_is_pressed(data, InputKind::Roll) { handle_input(data, output_events, &mut update, InputKind::Roll); } - handle_wield(data, &mut update); + handle_glider_input_or(data, &mut update, output_events, handle_wield); // If still in this state, do the things if matches!(update.character, CharacterState::GlideWield(_)) { diff --git a/common/src/states/self_buff.rs b/common/src/states/self_buff.rs index e034ce0df1..85bc52372f 100644 --- a/common/src/states/self_buff.rs +++ b/common/src/states/self_buff.rs @@ -95,7 +95,7 @@ impl CharacterBehavior for Data { .static_data .ability_info .ability - .map_or(false, |a| a.ability.is_from_tool()) + .map_or(false, |a| a.ability.is_from_wielded()) { vec![BuffCategory::RemoveOnLoadoutChange] } else { diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index e1b98b8821..b4a3ad3657 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -1330,6 +1330,30 @@ pub fn handle_input( } } +// NOTE: Quality of Life hack +// +// Uses glider ability if has any, otherwise fallback +pub fn handle_glider_input_or( + data: &JoinData<'_>, + update: &mut StateUpdate, + output_events: &mut OutputEvents, + fallback_fn: fn(&JoinData<'_>, &mut StateUpdate), +) { + if data + .inventory + .and_then(|inv| inv.equipped(EquipSlot::Glider)) + .and_then(|glider| glider.item_config()) + .is_none() + { + fallback_fn(data, update); + return; + }; + + if let Some(input) = data.controller.queued_inputs.keys().next() { + handle_ability(data, update, output_events, *input); + }; +} + pub fn attempt_input( data: &JoinData<'_>, output_events: &mut OutputEvents, diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index 830e234d55..64aecc7057 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -162,8 +162,8 @@ impl<'a> System<'a> for Sys { CharacterState::Idle(_) | CharacterState::Talk | CharacterState::Dance - | CharacterState::Glide(_) | CharacterState::Skate(_) + | CharacterState::Glide(_) | CharacterState::GlideWield(_) | CharacterState::Wielding(_) | CharacterState::Equipping(_) diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 54d981003f..e1dea2ae56 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -852,7 +852,12 @@ impl<'a> Widget for Diary<'a> { Some(self.inventory), Some(self.skill_set), ) - .ability_id(Some(self.inventory), Some(self.skill_set), self.context); + .ability_id( + None, + Some(self.inventory), + Some(self.skill_set), + self.context, + ); let (ability_title, ability_desc) = if let Some(ability_id) = ability_id { util::ability_description(ability_id, self.localized_strings) } else { @@ -935,6 +940,7 @@ impl<'a> Widget for Diary<'a> { .map(|a| { ( Ability::from(a).ability_id( + None, Some(self.inventory), Some(self.skill_set), self.context, @@ -951,6 +957,7 @@ impl<'a> Widget for Diary<'a> { .map(|a| { ( Ability::from(a).ability_id( + None, Some(self.inventory), Some(self.skill_set), self.context, diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index d7f4ce6cf2..60bdfc21dd 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -1043,6 +1043,7 @@ impl<'a> Skillbar<'a> { .get(i) .and_then(|a| { Ability::from(*a).ability_id( + self.char_state, Some(inventory), Some(skill_set), contexts, @@ -1118,6 +1119,7 @@ impl<'a> Skillbar<'a> { let primary_ability_id = self.active_abilities.and_then(|a| { Ability::from(a.primary).ability_id( + self.char_state, Some(self.inventory), Some(self.skillset), self.context, @@ -1148,6 +1150,7 @@ impl<'a> Skillbar<'a> { let secondary_ability_id = self.active_abilities.and_then(|a| { Ability::from(a.secondary).ability_id( + self.char_state, Some(self.inventory), Some(self.skillset), self.context, diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 1383bd4964..032faf39ed 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -168,7 +168,12 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { a.auxiliary_set(Some(inventory), Some(skillset)) .get(i) .and_then(|a| { - Ability::from(*a).ability_id(Some(inventory), Some(skillset), contexts) + Ability::from(*a).ability_id( + *char_state, + Some(inventory), + Some(skillset), + contexts, + ) }) }); @@ -257,9 +262,9 @@ impl<'a> SlotKey, img_ids::Imgs> for AbilitySlot { Some(inventory), Some(skillset), ) - .ability_id(Some(inventory), Some(skillset), contexts), + .ability_id(None, Some(inventory), Some(skillset), contexts), Self::Ability(ability) => { - Ability::from(*ability).ability_id(Some(inventory), Some(skillset), contexts) + Ability::from(*ability).ability_id(None, Some(inventory), Some(skillset), contexts) }, }; diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index 805742c70a..1f6d99be7e 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -590,6 +590,9 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id { "common.abilities.music.washboard" => imgs.instrument, "common.abilities.music.steeltonguedrum" => imgs.instrument, "common.abilities.music.shamisen" => imgs.instrument, + // Glider + "common.abilities.debug.glide_boost" => imgs.flyingrod_m2, + "common.abilities.debug.glide_speeder" => imgs.flyingrod_m1, _ => imgs.not_found, } } diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index ec3fd34dbb..329b68761c 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1062,7 +1062,7 @@ impl FigureMgr { let ability_id = character.and_then(|c| { c.ability_info() .and_then(|a| a.ability) - .and_then(|a| a.ability_id(inventory)) + .and_then(|a| a.ability_id(Some(c), inventory)) }); let move_dir = {