Route character state to Diary UI

This commit is contained in:
juliancoffee 2024-02-27 23:15:30 +02:00
parent 3283eb6916
commit 4dcf6215d2
7 changed files with 198 additions and 109 deletions

View File

@ -39,6 +39,11 @@ use std::{borrow::Cow, convert::TryFrom, time::Duration};
use super::shockwave::ShockwaveDodgeable;
pub const BASE_ABILITY_LIMIT: usize = 5;
// FIXME: different AbilitySpec on same ToolKind share the same key
// FIXME: only really works with weapons, glider just fallback to (None, None),
// but maybe that's ok?
/// Descriptor to pick the right (auxiliary) ability set
pub type AuxiliaryKey = (Option<ToolKind>, Option<ToolKind>);
// TODO: Potentially look into storing previous ability sets for weapon
@ -131,27 +136,38 @@ impl ActiveAbilities {
}
}
pub fn active_auxiliary_key(inv: Option<&Inventory>) -> AuxiliaryKey {
let tool_kind = |slot| {
inv.and_then(|inv| inv.equipped(slot))
.and_then(|item| match &*item.kind() {
ItemKind::Tool(tool) => Some(tool.kind),
_ => None,
})
};
pub fn active_auxiliary_key(
inv: Option<&Inventory>,
char_state: Option<&CharacterState>,
) -> AuxiliaryKey {
let source = AbilitySource::determine(char_state);
(
tool_kind(EquipSlot::ActiveMainhand),
tool_kind(EquipSlot::ActiveOffhand),
)
match source {
AbilitySource::Weapons => {
let tool_kind = |slot| {
inv.and_then(|inv| inv.equipped(slot))
.and_then(|item| match &*item.kind() {
ItemKind::Tool(tool) => Some(tool.kind),
_ => None,
})
};
(
tool_kind(EquipSlot::ActiveMainhand),
tool_kind(EquipSlot::ActiveOffhand),
)
},
AbilitySource::Glider => (None, None),
}
}
pub fn auxiliary_set(
&self,
inv: Option<&Inventory>,
skill_set: Option<&SkillSet>,
char_state: Option<&CharacterState>,
) -> Cow<Vec<AuxiliaryAbility>> {
let aux_key = Self::active_auxiliary_key(inv);
let aux_key = Self::active_auxiliary_key(inv, char_state);
self.auxiliary_sets
.get(&aux_key)
@ -164,6 +180,7 @@ impl ActiveAbilities {
input: AbilityInput,
inventory: Option<&Inventory>,
skill_set: Option<&SkillSet>,
char_state: Option<&CharacterState>,
) -> Ability {
match input {
AbilityInput::Guard => self.guard.into(),
@ -171,7 +188,7 @@ impl ActiveAbilities {
AbilityInput::Secondary => self.secondary.into(),
AbilityInput::Movement => self.movement.into(),
AbilityInput::Auxiliary(index) => self
.auxiliary_set(inventory, skill_set)
.auxiliary_set(inventory, skill_set, char_state)
.get(index)
.copied()
.map(|a| a.into())
@ -191,7 +208,7 @@ impl ActiveAbilities {
context: &AbilityContext,
// bool is from_offhand
) -> Option<(CharacterAbility, bool, SpecifiedAbility)> {
let ability = self.get_ability(input, inv, Some(skill_set));
let ability = self.get_ability(input, inv, Some(skill_set), char_state);
let ability_set = |equip_slot| {
inv.and_then(|inv| inv.equipped(equip_slot))
@ -232,40 +249,48 @@ impl ActiveAbilities {
};
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 => {
match ability {
Ability::ToolGuard => match source {
AbilitySource::Weapons => {
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,
AbilitySource::Glider => None,
},
Ability::ToolPrimary => match source {
AbilitySource::Weapons => inst_ability(EquipSlot::ActiveMainhand, false),
AbilitySource::Glider => inst_ability(EquipSlot::Glider, false),
},
Ability::ToolSecondary => match source {
AbilitySource::Weapons => inst_ability(EquipSlot::ActiveOffhand, true)
.or_else(|| inst_ability(EquipSlot::ActiveMainhand, false)),
AbilitySource::Glider => inst_ability(EquipSlot::Glider, false),
},
Ability::MainWeaponAux(_) => match source {
AbilitySource::Weapons => inst_ability(EquipSlot::ActiveMainhand, false),
// TODO: add auxiliary abilities in the future?
AbilitySource::Glider => None,
},
Ability::OffWeaponAux(_) => match source {
AbilitySource::Weapons => inst_ability(EquipSlot::ActiveOffhand, true),
// TODO: add auxiliary abilities in the future?
AbilitySource::Glider => None,
},
Ability::Empty => None,
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),
)
}),
}
}
pub fn iter_available_abilities<'a>(
pub fn iter_available_abilities_on<'a>(
inv: Option<&'a Inventory>,
skill_set: Option<&'a SkillSet>,
equip_slot: EquipSlot,
@ -290,15 +315,66 @@ impl ActiveAbilities {
})
}
pub fn all_available_abilities(
inv: Option<&Inventory>,
skill_set: Option<&SkillSet>,
char_state: Option<&CharacterState>,
) -> Vec<AuxiliaryAbility> {
let source = AbilitySource::determine(char_state);
match source {
AbilitySource::Weapons => {
// Check if uses combo of two "equal" weapons
let paired = inv
.and_then(|inv| {
let a = inv.equipped(EquipSlot::ActiveMainhand)?;
let b = inv.equipped(EquipSlot::ActiveOffhand)?;
if let (ItemKind::Tool(tool_a), ItemKind::Tool(tool_b)) =
(&*a.kind(), &*b.kind())
{
Some((a.ability_spec(), tool_a.kind, b.ability_spec(), tool_b.kind))
} else {
None
}
})
.is_some_and(|(a_spec, a_kind, b_spec, b_kind)| {
(a_spec, a_kind) == (b_spec, b_kind)
});
// If equal, just take the first
if paired {
Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveMainhand)
.map(AuxiliaryAbility::MainWeapon)
.collect()
} else {
Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveMainhand)
.map(AuxiliaryAbility::MainWeapon)
.chain(
Self::iter_available_abilities_on(
inv,
skill_set,
EquipSlot::ActiveOffhand,
)
.map(AuxiliaryAbility::OffWeapon),
)
.collect()
}
},
// TODO: add auxiliary abilities to gliders
AbilitySource::Glider => vec![],
}
}
fn default_ability_set<'a>(
inv: Option<&'a Inventory>,
skill_set: Option<&'a SkillSet>,
limit: Option<usize>,
) -> Vec<AuxiliaryAbility> {
let mut iter = Self::iter_available_abilities(inv, skill_set, EquipSlot::ActiveMainhand)
let mut iter = Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveMainhand)
.map(AuxiliaryAbility::MainWeapon)
.chain(
Self::iter_available_abilities(inv, skill_set, EquipSlot::ActiveOffhand)
Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveOffhand)
.map(AuxiliaryAbility::OffWeapon),
);
@ -406,6 +482,7 @@ impl Ability {
Ability::ToolPrimary => inst_ability(EquipSlot::Glider),
Ability::ToolSecondary => inst_ability(EquipSlot::Glider),
Ability::SpeciesMovement => None, // TODO: Make not None
// TODO: add aux abilities to gliders in the future?
Ability::MainWeaponAux(_) | Ability::OffWeaponAux(_) => None,
Ability::Empty => None,
},
@ -415,8 +492,8 @@ impl Ability {
inst_ability(equip_slot)
},
Ability::ToolPrimary => inst_ability(EquipSlot::ActiveMainhand),
Ability::ToolSecondary => inst_ability(EquipSlot::ActiveMainhand)
.or_else(|| inst_ability(EquipSlot::ActiveOffhand)),
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),
@ -504,6 +581,7 @@ impl SpecifiedAbility {
Ability::ToolPrimary => inst_ability(EquipSlot::Glider),
Ability::ToolSecondary => inst_ability(EquipSlot::Glider),
Ability::SpeciesMovement => None,
// TODO: add aux abilities to gliders in the future?
Ability::MainWeaponAux(_) | Ability::OffWeaponAux(_) => None,
Ability::Empty => None,
},
@ -524,14 +602,13 @@ 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 | PrimaryAbility::Glider => Ability::ToolPrimary,
PrimaryAbility::Tool => Ability::ToolPrimary,
PrimaryAbility::Empty => Ability::Empty,
}
}
@ -540,14 +617,13 @@ 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 | SecondaryAbility::Glider => Ability::ToolSecondary,
SecondaryAbility::Tool => Ability::ToolSecondary,
SecondaryAbility::Empty => Ability::Empty,
}
}

View File

@ -434,7 +434,8 @@ impl<'a> AgentData<'a> {
agent.combat_state.int_counters[IntCounters::Tactics as usize] = tactic as u8;
let auxiliary_key = ActiveAbilities::active_auxiliary_key(Some(self.inventory));
let auxiliary_key =
ActiveAbilities::active_auxiliary_key(Some(self.inventory), Some(self.char_state));
let set_sword_ability = |controller: &mut Controller, slot, skill| {
controller.push_event(ControlEvent::ChangeAbility {
slot,
@ -1219,7 +1220,8 @@ impl<'a> AgentData<'a> {
agent.combat_state.int_counters[IntCounters::Tactic as usize] = tactic as u8;
let auxiliary_key = ActiveAbilities::active_auxiliary_key(Some(self.inventory));
let auxiliary_key =
ActiveAbilities::active_auxiliary_key(Some(self.inventory), Some(self.char_state));
let set_axe_ability = |controller: &mut Controller, slot, skill| {
controller.push_event(ControlEvent::ChangeAbility {
slot,

View File

@ -37,7 +37,7 @@ use common::{
RollSkill, SceptreSkill, Skill, StaffSkill, SwimSkill, SwordSkill, SKILL_MODIFIERS,
},
skillset::{SkillGroupKind, SkillSet},
Body, Energy, Health, Inventory, Poise,
Body, CharacterState, Energy, Health, Inventory, Poise,
},
consts::{ENERGY_PER_LEVEL, HP_PER_LEVEL},
};
@ -212,6 +212,7 @@ pub struct Diary<'a> {
skill_set: &'a SkillSet,
active_abilities: &'a ActiveAbilities,
inventory: &'a Inventory,
char_state: &'a CharacterState,
health: &'a Health,
energy: &'a Energy,
poise: &'a Poise,
@ -258,6 +259,7 @@ impl<'a> Diary<'a> {
skill_set: &'a SkillSet,
active_abilities: &'a ActiveAbilities,
inventory: &'a Inventory,
char_state: &'a CharacterState,
health: &'a Health,
energy: &'a Energy,
poise: &'a Poise,
@ -280,6 +282,7 @@ impl<'a> Diary<'a> {
skill_set,
active_abilities,
inventory,
char_state,
health,
energy,
poise,
@ -838,6 +841,7 @@ impl<'a> Widget for Diary<'a> {
self.inventory,
self.skill_set,
self.context,
Some(self.char_state),
),
image_source: self.imgs,
slot_manager: Some(self.slot_manager),
@ -851,9 +855,10 @@ impl<'a> Widget for Diary<'a> {
AbilityInput::Auxiliary(i),
Some(self.inventory),
Some(self.skill_set),
Some(self.char_state),
)
.ability_id(
None,
Some(self.char_state),
Some(self.inventory),
Some(self.skill_set),
self.context,
@ -917,61 +922,24 @@ impl<'a> Widget for Diary<'a> {
.set(state.ids.active_abilities_keys[i], ui);
}
let same_weap_kinds = self
.inventory
.equipped(EquipSlot::ActiveMainhand)
.zip(self.inventory.equipped(EquipSlot::ActiveOffhand))
.map_or(false, |(a, b)| {
if let (ItemKind::Tool(tool_a), ItemKind::Tool(tool_b)) =
(&*a.kind(), &*b.kind())
{
(a.ability_spec(), tool_a.kind) == (b.ability_spec(), tool_b.kind)
} else {
false
}
});
let main_weap_abilities = ActiveAbilities::iter_available_abilities(
let abilities: Vec<_> = ActiveAbilities::all_available_abilities(
Some(self.inventory),
Some(self.skill_set),
EquipSlot::ActiveMainhand,
Some(self.char_state),
)
.map(AuxiliaryAbility::MainWeapon)
.into_iter()
.map(|a| {
(
Ability::from(a).ability_id(
None,
Some(self.char_state),
Some(self.inventory),
Some(self.skill_set),
self.context,
),
a,
)
});
let off_weap_abilities = ActiveAbilities::iter_available_abilities(
Some(self.inventory),
Some(self.skill_set),
EquipSlot::ActiveOffhand,
)
.map(AuxiliaryAbility::OffWeapon)
.map(|a| {
(
Ability::from(a).ability_id(
None,
Some(self.inventory),
Some(self.skill_set),
self.context,
),
a,
)
});
let abilities: Vec<_> = if same_weap_kinds {
// When the weapons have the same ability kind take only the main weapons,
main_weap_abilities.collect()
} else {
main_weap_abilities.chain(off_weap_abilities).collect()
};
})
.collect();
const ABILITIES_PER_PAGE: usize = 12;
@ -1072,12 +1040,27 @@ impl<'a> Widget for Diary<'a> {
self.inventory,
self.skill_set,
self.context,
Some(self.char_state),
),
image_source: self.imgs,
slot_manager: Some(self.slot_manager),
pulse: 0.0,
};
let same_weap_kinds = self
.inventory
.equipped(EquipSlot::ActiveMainhand)
.zip(self.inventory.equipped(EquipSlot::ActiveOffhand))
.map_or(false, |(a, b)| {
if let (ItemKind::Tool(tool_a), ItemKind::Tool(tool_b)) =
(&*a.kind(), &*b.kind())
{
(a.ability_spec(), tool_a.kind) == (b.ability_spec(), tool_b.kind)
} else {
false
}
});
for (id_index, (ability_id, ability)) in abilities
.iter()
.skip(ability_start)

View File

@ -81,6 +81,10 @@ impl State {
.state()
.read_storage::<comp::SkillSet>()
.get(info.viewpoint_entity),
client
.state()
.read_storage::<comp::CharacterState>()
.get(info.viewpoint_entity),
)
.iter()
.enumerate()

View File

@ -3684,6 +3684,7 @@ impl Hud {
if let (
Some(skill_set),
Some(inventory),
Some(char_state),
Some(health),
Some(energy),
Some(body),
@ -3691,6 +3692,7 @@ impl Hud {
) = (
skill_sets.get(entity),
inventories.get(entity),
char_states.get(entity),
healths.get(entity),
energies.get(entity),
bodies.get(entity),
@ -3704,6 +3706,7 @@ impl Hud {
skill_set,
active_abilities.get(entity).unwrap_or(&Default::default()),
inventory,
char_state,
health,
energy,
poise,
@ -4002,12 +4005,20 @@ impl Hud {
let me = info.viewpoint_entity;
if let Some(active_abilities) = active_abilities.get(me) {
let ability_a = active_abilities
.auxiliary_set(inventories.get(me), skill_sets.get(me))
.auxiliary_set(
inventories.get(me),
skill_sets.get(me),
char_states.get(me),
)
.get(a)
.copied()
.unwrap_or(AuxiliaryAbility::Empty);
let ability_b = active_abilities
.auxiliary_set(inventories.get(me), skill_sets.get(me))
.auxiliary_set(
inventories.get(me),
skill_sets.get(me),
char_states.get(me),
)
.get(b)
.copied()
.unwrap_or(AuxiliaryAbility::Empty);
@ -4152,12 +4163,20 @@ impl Hud {
let me = info.viewpoint_entity;
if let Some(active_abilities) = active_abilities.get(me) {
let ability_a = active_abilities
.auxiliary_set(inventories.get(me), skill_sets.get(me))
.auxiliary_set(
inventories.get(me),
skill_sets.get(me),
char_states.get(me),
)
.get(a)
.copied()
.unwrap_or(AuxiliaryAbility::Empty);
let ability_b = active_abilities
.auxiliary_set(inventories.get(me), skill_sets.get(me))
.auxiliary_set(
inventories.get(me),
skill_sets.get(me),
char_states.get(me),
)
.get(b)
.copied()
.unwrap_or(AuxiliaryAbility::Empty);

View File

@ -1028,7 +1028,7 @@ impl<'a> Skillbar<'a> {
// Helper
let tooltip_text = |slot| {
let (hotbar, inventory, _, skill_set, active_abilities, _, contexts, _, _, _) =
let (hotbar, inventory, _, skill_set, active_abilities, _, contexts, _, char_state, _) =
content_source;
hotbar.get(slot).and_then(|content| match content {
hotbar::SlotContents::Inventory(i, _) => inventory.get_by_hash(i).map(|item| {
@ -1039,7 +1039,7 @@ impl<'a> Skillbar<'a> {
}),
hotbar::SlotContents::Ability(i) => active_abilities
.and_then(|a| {
a.auxiliary_set(Some(inventory), Some(skill_set))
a.auxiliary_set(Some(inventory), Some(skill_set), char_state)
.get(i)
.and_then(|a| {
Ability::from(*a).ability_id(

View File

@ -165,7 +165,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
},
hotbar::SlotContents::Ability(i) => {
let ability_id = active_abilities.and_then(|a| {
a.auxiliary_set(Some(inventory), Some(skillset))
a.auxiliary_set(Some(inventory), Some(skillset), *char_state)
.get(i)
.and_then(|a| {
Ability::from(*a).ability_id(
@ -246,6 +246,7 @@ type AbilitiesSource<'a> = (
&'a Inventory,
&'a SkillSet,
&'a AbilityContext,
Option<&'a CharacterState>,
);
impl<'a> SlotKey<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
@ -253,7 +254,7 @@ impl<'a> SlotKey<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
fn image_key(
&self,
(active_abilities, inventory, skillset, contexts): &AbilitiesSource<'a>,
(active_abilities, inventory, skillset, contexts, char_state): &AbilitiesSource<'a>,
) -> Option<(Self::ImageKey, Option<Color>)> {
let ability_id = match self {
Self::Slot(index) => active_abilities
@ -261,11 +262,15 @@ impl<'a> SlotKey<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
AbilityInput::Auxiliary(*index),
Some(inventory),
Some(skillset),
*char_state,
)
.ability_id(None, Some(inventory), Some(skillset), contexts),
Self::Ability(ability) => {
Ability::from(*ability).ability_id(None, Some(inventory), Some(skillset), contexts)
},
.ability_id(*char_state, Some(inventory), Some(skillset), contexts),
Self::Ability(ability) => Ability::from(*ability).ability_id(
*char_state,
Some(inventory),
Some(skillset),
contexts,
),
};
ability_id.map(|id| (String::from(id), None))