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"),
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"): (

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,
speed: 250.0,
max_exit_velocity: 70.0,
)
)

View File

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

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.velorite_bow_debug"), 1),
(Item("common.items.debug.admin"),1),
(Item("common.items.debug.glider"),1),
(Item("common.items.debug.golden_cheese"),100),
],
"consumables": [

View File

@ -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.

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-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

View File

@ -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

View File

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

View File

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

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 {
pub fn from_auxiliary(
auxiliary_sets: HashMap<AuxiliaryKey, Vec<AuxiliaryAbility>>,
@ -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<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()
.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<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>(
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<AbilityItem>) -> &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<PrimaryAbility> 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<PrimaryAbility> for Ability {
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub enum SecondaryAbility {
Tool,
Glider,
Empty,
}
impl From<SecondaryAbility> 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,

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 {
matches!(
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
(item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
) -> Result<Self, Self::Error> {
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() }

View File

@ -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,
}

View File

@ -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<Boost>,
}
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
});
}

View File

@ -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(_)) {

View File

@ -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 {

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

View File

@ -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(_)

View File

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

View File

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

View File

@ -168,7 +168,12 @@ impl<'a> SlotKey<HotbarSource<'a>, 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<AbilitiesSource<'a>, 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)
},
};

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.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,
}
}

View File

@ -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 = {