Vroom Glider

- GlideBoost ability
- Vroom Glider item
- Made possible to attach ability_spec to gliders
- Activate glider ability while Glide/GlideWield

TODO:
- make it visible in voxygen
This commit is contained in:
juliancoffee 2024-01-27 15:40:17 +02:00
parent 7016ebfff3
commit 3283eb6916
26 changed files with 418 additions and 239 deletions

View File

@ -953,6 +953,11 @@
secondary: Simple(None, "common.abilities.empty.basic"), secondary: Simple(None, "common.abilities.empty.basic"),
abilities: [], abilities: [],
), ),
Custom("Admin's Eagle"): (
primary: Simple(None, "common.abilities.debug.glide_speeder"),
secondary: Simple(None, "common.abilities.debug.glide_boost"),
abilities: [],
),
// Adlets // Adlets
// TODO: Do we want to eventually convert these to simple variants of weapons? // TODO: Do we want to eventually convert these to simple variants of weapons?
Custom("Adlet Hunter"): ( Custom("Adlet Hunter"): (

View File

@ -0,0 +1,3 @@
GlideBoost(
booster: Upward(400.0),
)

View File

@ -0,0 +1,3 @@
GlideBoost(
booster: Forward(0.75),
)

View File

@ -3,4 +3,4 @@ Boost(
only_up: true, only_up: true,
speed: 250.0, speed: 250.0,
max_exit_velocity: 70.0, max_exit_velocity: 70.0,
) )

View File

@ -1974,6 +1974,9 @@
Simple( Simple(
"common.items.debug.admin", "common.items.debug.admin",
): "armor-tabard-admin_tabard", ): "armor-tabard-admin_tabard",
Simple(
"common.items.debug.glider",
): "other-glider-vroom-debug",
Simple( Simple(
"common.items.debug.admin_back", "common.items.debug.admin_back",
): "armor-misc-back-admin_back", ): "armor-misc-back-admin_back",
@ -7099,4 +7102,4 @@
), ),
): "weapon-hammer-hammer-cobalt-1h", ): "weapon-hammer-hammer-cobalt-1h",
}, },
) )

View File

@ -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")),
)

View File

@ -5,6 +5,7 @@
(Item("common.items.debug.admin_sword"),1), (Item("common.items.debug.admin_sword"),1),
(Item("common.items.debug.velorite_bow_debug"), 1), (Item("common.items.debug.velorite_bow_debug"), 1),
(Item("common.items.debug.admin"),1), (Item("common.items.debug.admin"),1),
(Item("common.items.debug.glider"),1),
(Item("common.items.debug.golden_cheese"),100), (Item("common.items.debug.golden_cheese"),100),
], ],
"consumables": [ "consumables": [

View File

@ -1,20 +1,7 @@
common-abilities-debug-possess = Possessing Arrow ## Internal terms, currently only used in zh-Hans.
.desc = Shoots a poisonous arrow. Lets you control your target. ## If we remove them here, they also get auto-removed in zh-Hans,
common-abilities-debug-evolve = Evolve ## so please keep them, even when not used in English file.
.desc = You become your better self. ## See https://github.com/WeblateOrg/weblate/issues/9895
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
-heavy_stance = "" -heavy_stance = ""
-agile_stance = "" -agile_stance = ""
@ -39,7 +26,27 @@ common-abilities-sceptre-wardingaura = Warding Aura
-enter_stance = "" -enter_stance = ""
-require_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 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. .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 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. A powerful strike that purportedly can even split the sky, but will split through enemies.
Requires cleaving stance. Requires cleaving stance.
# Axe abilities ## Axe abilities
common-abilities-axe-triple_chop = Triple Chop common-abilities-axe-triple_chop = Triple Chop
.desc = .desc =
Three quick strikes. Three quick strikes.

View File

@ -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-rank_up = New skillpoint
hud-skill-sp_available = hud-skill-sp_available =
{ $number -> { $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_title = Increase energy
hud-skill-inc_energy = Increases your maximum energy by { $boost } points.{ $SP } 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_title = Sword proficiency
hud-skill-unlck_sword = Unlocks the sword skill tree.{ $SP } hud-skill-unlck_sword = Unlocks the sword skill tree.{ $SP }
hud-skill-unlck_axe_title = Axe proficiency hud-skill-unlck_axe_title = Axe proficiency

View File

@ -51,3 +51,5 @@ weapon-sword-frost-admin_sword = Admin Greatsword
weapon-bow-velorite-debug = Admin Velorite Bow weapon-bow-velorite-debug = Admin Velorite Bow
.desc = Infused with Velorite power. .desc = Infused with Velorite power.
other-glider-vroom-debug = Vroom Glider
.desc = goes brrr

View File

@ -3816,6 +3816,10 @@
"voxel.weapon.tool.broom_belzeshrub_purple", "voxel.weapon.tool.broom_belzeshrub_purple",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1, (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 // Misc
Simple("common.items.weapons.tool.golf_club"): VoxTrans( Simple("common.items.weapons.tool.golf_club"): VoxTrans(
"voxel.weapon.tool.golf_club", "voxel.weapon.tool.golf_club",

View File

@ -68,5 +68,9 @@
vox_spec: ("glider.winter_wings", (-26.0, -26.0, 0.0)), vox_spec: ("glider.winter_wings", (-26.0, -26.0, 0.0)),
color: None color: None
), ),
"common.items.debug.glider": (
vox_spec: ("glider.cloverleaf", (-19.0, -5.0, 0.0)),
color: None
),
}, },
)) ))

View File

@ -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 { impl ActiveAbilities {
pub fn from_auxiliary( pub fn from_auxiliary(
auxiliary_sets: HashMap<AuxiliaryKey, Vec<AuxiliaryAbility>>, auxiliary_sets: HashMap<AuxiliaryKey, Vec<AuxiliaryAbility>>,
@ -176,7 +195,7 @@ impl ActiveAbilities {
let ability_set = |equip_slot| { let ability_set = |equip_slot| {
inv.and_then(|inv| inv.equipped(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| { let scale_ability = |ability: CharacterAbility, equip_slot| {
@ -194,100 +213,55 @@ impl ActiveAbilities {
context_index, context_index,
}; };
match ability { let inst_ability = |slot: EquipSlot, offhand: bool| {
Ability::ToolGuard => { ability_set(slot).and_then(|abilities| {
let equip_slot = combat::get_equip_slot_by_block_priority(inv); use AbilityInput as I;
ability_set(equip_slot)
.and_then(|abilities| { let dispatched = match input {
abilities I::Guard => abilities.guard(Some(skill_set), context),
.guard(Some(skill_set), context) I::Primary => abilities.primary(Some(skill_set), context),
.map(|(a, i)| (a.ability.clone(), i)) I::Secondary => abilities.secondary(Some(skill_set), context),
}) I::Auxiliary(index) => abilities.auxiliary(index, Some(skill_set), context),
.map(|(ability, i)| { I::Movement => return None,
( };
scale_ability(ability, equip_slot),
matches!(equip_slot, EquipSlot::ActiveOffhand), dispatched
spec_ability(i), .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>, skill_set: Option<&'a SkillSet>,
equip_slot: EquipSlot, equip_slot: EquipSlot,
) -> impl Iterator<Item = usize> + 'a { ) -> impl Iterator<Item = usize> + '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() .into_iter()
.flat_map(|i| &i.item_config_expect().abilities.abilities) .flat_map(|config| &config.abilities.abilities)
.enumerate() .enumerate()
.filter_map(move |(i, a)| match a { .filter_map(move |(i, a)| match a {
AbilityKind::Simple(skill, _) => skill AbilityKind::Simple(skill, _) => skill
@ -338,6 +312,7 @@ impl ActiveAbilities {
} }
} }
#[derive(Copy, Clone)]
pub enum AbilityInput { pub enum AbilityInput {
Guard, Guard,
Primary, Primary,
@ -360,15 +335,29 @@ pub enum Ability {
} }
impl Ability { impl Ability {
fn try_input(&self) -> Option<AbilityInput> {
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>( pub fn ability_id<'a>(
self, self,
char_state: Option<&CharacterState>,
inv: Option<&'a Inventory>, inv: Option<&'a Inventory>,
skillset: Option<&'a SkillSet>, skill_set: Option<&'a SkillSet>,
context: &AbilityContext, context: &AbilityContext,
) -> Option<&'a str> { ) -> Option<&'a str> {
let ability_set = |equip_slot| { let ability_set = |equip_slot| {
inv.and_then(|inv| inv.equipped(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> { let contextual_id = |kind: Option<&'a AbilityKind<_>>| -> Option<&'a str> {
@ -383,62 +372,60 @@ impl Ability {
} }
}; };
match self { let inst_ability = |slot: EquipSlot| {
Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv)) ability_set(slot).and_then(|abilities| {
.and_then(|abilities| { use AbilityInput as I;
abilities
.guard(skillset, context) let dispatched = match self.try_input()? {
.map(|a| a.0.id.as_str()) I::Guard => abilities.guard(skill_set, context),
.or_else(|| { I::Primary => abilities.primary(skill_set, context),
abilities I::Secondary => abilities.secondary(skill_set, context),
.guard I::Auxiliary(index) => abilities.auxiliary(index, skill_set, context),
.as_ref() I::Movement => return None,
.and_then(|g| contextual_id(Some(g))) };
})
}), dispatched
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| { .map(|(a, _)| a.id.as_str())
abilities .or_else(|| match self.try_input()? {
.primary(skillset, context) I::Guard => abilities
.map(|a| a.0.id.as_str()) .guard
.or_else(|| contextual_id(Some(&abilities.primary))) .as_ref()
}), .and_then(|g| contextual_id(Some(g))),
Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand) I::Primary => contextual_id(Some(&abilities.primary)),
.and_then(|abilities| { I::Secondary => contextual_id(Some(&abilities.secondary)),
abilities I::Auxiliary(index) => contextual_id(abilities.abilities.get(index)),
.secondary(skillset, context) I::Movement => None,
.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)))
}) })
}), })
Ability::SpeciesMovement => None, // TODO: Make not None };
Ability::MainWeaponAux(index) => {
ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| { let source = AbilitySource::determine(char_state);
abilities match source {
.auxiliary(index, skillset, context) AbilitySource::Glider => match self {
.map(|a| a.0.id.as_str()) Ability::ToolGuard => None,
.or_else(|| contextual_id(abilities.abilities.get(index))) 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) => { AbilitySource::Weapons => match self {
ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| { Ability::ToolGuard => {
abilities let equip_slot = combat::get_equip_slot_by_block_priority(inv);
.auxiliary(index, skillset, context) inst_ability(equip_slot)
.map(|a| a.0.id.as_str()) },
.or_else(|| contextual_id(abilities.abilities.get(index))) 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 { match self {
Ability::ToolPrimary Ability::ToolPrimary
| Ability::ToolSecondary | Ability::ToolSecondary
@ -449,6 +436,7 @@ impl Ability {
} }
} }
} }
#[derive(Copy, Clone, Serialize, Deserialize, Debug)] #[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub enum GuardAbility { pub enum GuardAbility {
Tool, Tool,
@ -471,10 +459,14 @@ pub struct SpecifiedAbility {
} }
impl 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| { let ability_set = |equip_slot| {
inv.and_then(|inv| inv.equipped(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<AbilityItem>) -> &str { fn ability_id(spec_ability: SpecifiedAbility, ability: &AbilityKind<AbilityItem>) -> &str {
@ -490,23 +482,41 @@ impl SpecifiedAbility {
} }
} }
match self.ability { let inst_ability = |slot: EquipSlot| {
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand) ability_set(slot).and_then(|abilities| {
.map(|abilities| ability_id(self, &abilities.primary)), use AbilityInput as I;
Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand)
.map(|abilities| ability_id(self, &abilities.secondary)) let dispatched = match self.ability.try_input()? {
.or_else(|| { I::Guard => abilities.guard.as_ref(),
ability_set(EquipSlot::ActiveMainhand) I::Primary => Some(&abilities.primary),
.map(|abilities| ability_id(self, &abilities.secondary)) I::Secondary => Some(&abilities.secondary),
}), I::Auxiliary(index) => abilities.abilities.get(index),
Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv)) I::Movement => return None,
.and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a))), };
Ability::SpeciesMovement => None, // TODO: Make not None dispatched.map(|a| ability_id(self, a))
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))), let source = AbilitySource::determine(char_state);
Ability::Empty => None, 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)] #[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub enum PrimaryAbility { pub enum PrimaryAbility {
Tool, Tool,
Glider,
Empty, Empty,
} }
impl From<PrimaryAbility> for Ability { impl From<PrimaryAbility> for Ability {
fn from(primary: PrimaryAbility) -> Self { fn from(primary: PrimaryAbility) -> Self {
match primary { match primary {
PrimaryAbility::Tool => Ability::ToolPrimary, PrimaryAbility::Tool | PrimaryAbility::Glider => Ability::ToolPrimary,
PrimaryAbility::Empty => Ability::Empty, PrimaryAbility::Empty => Ability::Empty,
} }
} }
@ -529,13 +540,14 @@ impl From<PrimaryAbility> for Ability {
#[derive(Copy, Clone, Serialize, Deserialize, Debug)] #[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub enum SecondaryAbility { pub enum SecondaryAbility {
Tool, Tool,
Glider,
Empty, Empty,
} }
impl From<SecondaryAbility> for Ability { impl From<SecondaryAbility> for Ability {
fn from(primary: SecondaryAbility) -> Self { fn from(primary: SecondaryAbility) -> Self {
match primary { match primary {
SecondaryAbility::Tool => Ability::ToolSecondary, SecondaryAbility::Tool | SecondaryAbility::Glider => Ability::ToolSecondary,
SecondaryAbility::Empty => Ability::Empty, SecondaryAbility::Empty => Ability::Empty,
} }
} }
@ -708,6 +720,11 @@ pub enum CharacterAbility {
#[serde(default)] #[serde(default)]
meta: AbilityMeta, meta: AbilityMeta,
}, },
GlideBoost {
booster: glide::Boost,
#[serde(default)]
meta: AbilityMeta,
},
DashMelee { DashMelee {
energy_cost: f32, energy_cost: f32,
energy_drain: f32, energy_drain: f32,
@ -1125,6 +1142,7 @@ impl CharacterAbility {
}, },
CharacterAbility::ComboMeleeDeprecated { .. } CharacterAbility::ComboMeleeDeprecated { .. }
| CharacterAbility::Boost { .. } | CharacterAbility::Boost { .. }
| CharacterAbility::GlideBoost { .. }
| CharacterAbility::BasicBeam { .. } | CharacterAbility::BasicBeam { .. }
| CharacterAbility::Blink { .. } | CharacterAbility::Blink { .. }
| CharacterAbility::Music { .. } | CharacterAbility::Music { .. }
@ -1688,6 +1706,7 @@ impl CharacterAbility {
*buildup_duration /= stats.speed; *buildup_duration /= stats.speed;
*recover_duration /= stats.speed; *recover_duration /= stats.speed;
}, },
GlideBoost { .. } => {},
} }
self self
} }
@ -1724,6 +1743,7 @@ impl CharacterAbility {
} }
}, },
Boost { .. } Boost { .. }
| GlideBoost { .. }
| ComboMeleeDeprecated { .. } | ComboMeleeDeprecated { .. }
| Blink { .. } | Blink { .. }
| Music { .. } | Music { .. }
@ -1773,6 +1793,7 @@ impl CharacterAbility {
| RiposteMelee { .. } | RiposteMelee { .. }
| BasicBeam { .. } | BasicBeam { .. }
| Boost { .. } | Boost { .. }
| GlideBoost { .. }
| ComboMeleeDeprecated { .. } | ComboMeleeDeprecated { .. }
| Blink { .. } | Blink { .. }
| Music { .. } | Music { .. }
@ -1801,6 +1822,7 @@ impl CharacterAbility {
| SelfBuff { meta, .. } | SelfBuff { meta, .. }
| BasicBeam { meta, .. } | BasicBeam { meta, .. }
| Boost { meta, .. } | Boost { meta, .. }
| GlideBoost { meta, .. }
| ComboMeleeDeprecated { meta, .. } | ComboMeleeDeprecated { meta, .. }
| ComboMelee2 { meta, .. } | ComboMelee2 { meta, .. }
| Blink { meta, .. } | Blink { meta, .. }
@ -2312,6 +2334,13 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
}, },
timer: Duration::default(), 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 { CharacterAbility::DashMelee {
energy_cost: _, energy_cost: _,
energy_drain, energy_drain,

View File

@ -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 { pub fn is_stealthy(&self) -> bool {
matches!( matches!(
self, self,

View File

@ -729,38 +729,47 @@ impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
// TODO: Either remove msm or use it as argument in fn kind // TODO: Either remove msm or use it as argument in fn kind
(item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest), (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
if let ItemKind::Tool(tool) = &*item.kind() { match &*item.kind() {
// If no custom ability set is specified, fall back to abilityset of tool kind. ItemKind::Tool(tool) => {
let tool_default = |tool_kind| { // If no custom ability set is specified, fall back to abilityset of tool kind.
let key = &AbilitySpec::Tool(tool_kind); let tool_default = |tool_kind| {
ability_map.get_ability_set(key) 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) { 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() set.clone()
.modified_by_tool(tool, item.stats_durability_multiplier()) .modified_by_tool(tool, item.stats_durability_multiplier())
} else { } else {
error!( error!(
"Custom ability set: {:?} references non-existent set, falling back to \ "No ability set defined for tool: {:?}, falling back to default ability \
default ability set.", set.",
set_key tool.kind
); );
tool_default(tool.kind).cloned().unwrap_or_default() Default::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()
};
Ok(ItemConfig { abilities }) Ok(ItemConfig { abilities })
} else { },
Err(ItemConfigError::BadItemKind) 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 slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
pub fn item_config_expect(&self) -> &ItemConfig { pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
self.item_config
.as_ref()
.expect("Item was expected to have an ItemConfig")
}
pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() } pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }

View File

@ -37,14 +37,13 @@ pub enum ToolKind {
Farming, Farming,
Pick, Pick,
Shovel, Shovel,
/// Music Instruments
Instrument,
// npcs // npcs
/// Intended for invisible weapons (e.g. a creature using its claws or /// Intended for invisible weapons (e.g. a creature using its claws or
/// biting) /// biting)
Natural, Natural,
/// This is an placeholder item, it is used by non-humanoid npcs to attack /// 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, Empty,
} }

View File

@ -18,6 +18,14 @@ use vek::*;
const PITCH_SLOW_TIME: f32 = 0.5; 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)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data { pub struct Data {
/// The aspect ratio is the ratio of the span squared to actual planform /// The aspect ratio is the ratio of the span squared to actual planform
@ -28,6 +36,7 @@ pub struct Data {
last_vel: Vel, last_vel: Vel,
pub timer: Duration, pub timer: Duration,
inputs_disabled: bool, inputs_disabled: bool,
pub booster: Option<Boost>,
} }
impl Data { impl Data {
@ -47,6 +56,7 @@ impl Data {
last_vel: Vel::zero(), last_vel: Vel::zero(),
timer: Duration::default(), timer: Duration::default(),
inputs_disabled: true, inputs_disabled: true,
booster: None,
} }
} }
@ -75,12 +85,32 @@ impl Data {
} }
impl CharacterBehavior for 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); 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() if data.physics.on_ground.is_some()
&& (data.vel.0 - data.physics.ground_vel).magnitude_squared() < 2_f32.powi(2) && (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)); update.character = CharacterState::GlideWield(glide_wield::Data::from(data));
} else if data.physics.in_liquid().is_some() } else if data.physics.in_liquid().is_some()
@ -189,11 +219,34 @@ impl CharacterBehavior for Data {
slerp_s, 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 { update.character = CharacterState::Glide(Self {
ori, ori,
last_vel: *data.vel, last_vel: *data.vel,
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(data, self.timer, None),
inputs_disabled, inputs_disabled,
// booster is consumed, set to None
booster: None,
..*self ..*self
}); });
} }

View File

@ -66,7 +66,7 @@ impl CharacterBehavior for Data {
if input_is_pressed(data, InputKind::Roll) { if input_is_pressed(data, InputKind::Roll) {
handle_input(data, output_events, &mut update, 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 still in this state, do the things
if matches!(update.character, CharacterState::GlideWield(_)) { if matches!(update.character, CharacterState::GlideWield(_)) {

View File

@ -95,7 +95,7 @@ impl CharacterBehavior for Data {
.static_data .static_data
.ability_info .ability_info
.ability .ability
.map_or(false, |a| a.ability.is_from_tool()) .map_or(false, |a| a.ability.is_from_wielded())
{ {
vec![BuffCategory::RemoveOnLoadoutChange] vec![BuffCategory::RemoveOnLoadoutChange]
} else { } else {

View File

@ -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( pub fn attempt_input(
data: &JoinData<'_>, data: &JoinData<'_>,
output_events: &mut OutputEvents, output_events: &mut OutputEvents,

View File

@ -162,8 +162,8 @@ impl<'a> System<'a> for Sys {
CharacterState::Idle(_) CharacterState::Idle(_)
| CharacterState::Talk | CharacterState::Talk
| CharacterState::Dance | CharacterState::Dance
| CharacterState::Glide(_)
| CharacterState::Skate(_) | CharacterState::Skate(_)
| CharacterState::Glide(_)
| CharacterState::GlideWield(_) | CharacterState::GlideWield(_)
| CharacterState::Wielding(_) | CharacterState::Wielding(_)
| CharacterState::Equipping(_) | CharacterState::Equipping(_)

View File

@ -852,7 +852,12 @@ impl<'a> Widget for Diary<'a> {
Some(self.inventory), Some(self.inventory),
Some(self.skill_set), 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 { let (ability_title, ability_desc) = if let Some(ability_id) = ability_id {
util::ability_description(ability_id, self.localized_strings) util::ability_description(ability_id, self.localized_strings)
} else { } else {
@ -935,6 +940,7 @@ impl<'a> Widget for Diary<'a> {
.map(|a| { .map(|a| {
( (
Ability::from(a).ability_id( Ability::from(a).ability_id(
None,
Some(self.inventory), Some(self.inventory),
Some(self.skill_set), Some(self.skill_set),
self.context, self.context,
@ -951,6 +957,7 @@ impl<'a> Widget for Diary<'a> {
.map(|a| { .map(|a| {
( (
Ability::from(a).ability_id( Ability::from(a).ability_id(
None,
Some(self.inventory), Some(self.inventory),
Some(self.skill_set), Some(self.skill_set),
self.context, self.context,

View File

@ -1043,6 +1043,7 @@ impl<'a> Skillbar<'a> {
.get(i) .get(i)
.and_then(|a| { .and_then(|a| {
Ability::from(*a).ability_id( Ability::from(*a).ability_id(
self.char_state,
Some(inventory), Some(inventory),
Some(skill_set), Some(skill_set),
contexts, contexts,
@ -1118,6 +1119,7 @@ impl<'a> Skillbar<'a> {
let primary_ability_id = self.active_abilities.and_then(|a| { let primary_ability_id = self.active_abilities.and_then(|a| {
Ability::from(a.primary).ability_id( Ability::from(a.primary).ability_id(
self.char_state,
Some(self.inventory), Some(self.inventory),
Some(self.skillset), Some(self.skillset),
self.context, self.context,
@ -1148,6 +1150,7 @@ impl<'a> Skillbar<'a> {
let secondary_ability_id = self.active_abilities.and_then(|a| { let secondary_ability_id = self.active_abilities.and_then(|a| {
Ability::from(a.secondary).ability_id( Ability::from(a.secondary).ability_id(
self.char_state,
Some(self.inventory), Some(self.inventory),
Some(self.skillset), Some(self.skillset),
self.context, self.context,

View File

@ -168,7 +168,12 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
a.auxiliary_set(Some(inventory), Some(skillset)) a.auxiliary_set(Some(inventory), Some(skillset))
.get(i) .get(i)
.and_then(|a| { .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<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
Some(inventory), Some(inventory),
Some(skillset), Some(skillset),
) )
.ability_id(Some(inventory), Some(skillset), contexts), .ability_id(None, Some(inventory), Some(skillset), contexts),
Self::Ability(ability) => { Self::Ability(ability) => {
Ability::from(*ability).ability_id(Some(inventory), Some(skillset), contexts) Ability::from(*ability).ability_id(None, Some(inventory), Some(skillset), contexts)
}, },
}; };

View File

@ -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.washboard" => imgs.instrument,
"common.abilities.music.steeltonguedrum" => imgs.instrument, "common.abilities.music.steeltonguedrum" => imgs.instrument,
"common.abilities.music.shamisen" => 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, _ => imgs.not_found,
} }
} }

View File

@ -1062,7 +1062,7 @@ impl FigureMgr {
let ability_id = character.and_then(|c| { let ability_id = character.and_then(|c| {
c.ability_info() c.ability_info()
.and_then(|a| a.ability) .and_then(|a| a.ability)
.and_then(|a| a.ability_id(inventory)) .and_then(|a| a.ability_id(Some(c), inventory))
}); });
let move_dir = { let move_dir = {