Abilities can now be referred to by the asset id.

This commit is contained in:
Sam 2021-11-10 17:25:37 -05:00
parent 4309e1ff9b
commit da677e8ea6
7 changed files with 223 additions and 226 deletions

View File

@ -29,6 +29,20 @@
(Some(Bow(UnlockShotgun)), "common.abilities.bow.shotgun"),
],
),
Tool(Staff): (
primary: "common.abilities.staff.firebomb",
secondary: "common.abilities.staff.flamethrower",
abilities: [
(Some(Staff(UnlockShockwave)), "common.abilities.staff.fireshockwave"),
],
),
Tool(Sceptre): (
primary: "common.abilities.sceptre.lifestealbeam",
secondary: "common.abilities.sceptre.healingaura",
abilities: [
(Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"),
],
),
Custom("Husk"): (
primary: "common.abilities.custom.husk.singlestrike",
secondary: "common.abilities.custom.husk.triplestrike",
@ -70,20 +84,6 @@
abilities: [
],
),
Tool(Staff): (
primary: "common.abilities.staff.firebomb",
secondary: "common.abilities.staff.flamethrower",
abilities: [
(Some(Staff(UnlockShockwave)), "common.abilities.staff.fireshockwave"),
],
),
Tool(Sceptre): (
primary: "common.abilities.sceptre.lifestealbeam",
secondary: "common.abilities.sceptre.healingaura",
abilities: [
(Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"),
],
),
Tool(Dagger): (
primary: "common.abilities.dagger.tempbasic",
secondary: "common.abilities.dagger.tempbasic",

View File

@ -30,10 +30,6 @@ use std::{convert::TryFrom, time::Duration};
pub const MAX_ABILITIES: usize = 5;
// TODO: Should primary, secondary, and dodge be moved into here? Would
// essentially require custom enum that are only used for those (except maybe
// dodge if we make movement and have potentially differ based off of armor) but
// would also allow logic to be a bit more centralized
// TODO: Potentially look into storing previous ability sets for weapon
// combinations and automatically reverting back to them on switching to that
// set of weapons. Consider after UI is set up and people weigh in on memory
@ -74,6 +70,17 @@ impl AbilityPool {
}
}
pub fn get_ability(&self, input: InputKind) -> Ability {
match input {
InputKind::Primary => Some(self.primary),
InputKind::Secondary => Some(self.secondary),
InputKind::Roll => Some(self.movement),
InputKind::Ability(index) => self.abilities.get(index).copied(),
_ => None,
}
.unwrap_or(Ability::Empty)
}
pub fn activate_ability(
&self,
input: InputKind,
@ -82,14 +89,7 @@ impl AbilityPool {
body: &Body,
// bool is from_offhand
) -> Option<(CharacterAbility, bool)> {
let ability = match input {
InputKind::Primary => Some(self.primary),
InputKind::Secondary => Some(self.secondary),
InputKind::Roll => Some(self.movement),
InputKind::Ability(index) => self.abilities.get(index).copied(),
_ => None,
}
.unwrap_or(Ability::Empty);
let ability = self.get_ability(input);
let ability_set = |equip_slot| {
inv.and_then(|inv| inv.equipped(equip_slot))
@ -184,6 +184,30 @@ pub enum Ability {
* ArmorAbility(usize), */
}
impl Ability {
pub fn ability_id(self, inv: Option<&Inventory>) -> Option<&String> {
let ability_id_set = |equip_slot| {
inv.and_then(|inv| inv.equipped(equip_slot))
.map(|i| &i.item_config_expect().ability_ids)
};
match self {
Ability::ToolPrimary => {
ability_id_set(EquipSlot::ActiveMainhand).map(|ids| &ids.primary)
},
Ability::ToolSecondary => ability_id_set(EquipSlot::ActiveOffhand)
.map(|ids| &ids.secondary)
.or_else(|| ability_id_set(EquipSlot::ActiveMainhand).map(|ids| &ids.secondary)),
Ability::SpeciesMovement => None, // TODO: Make not None
Ability::MainWeaponAbility(index) => ability_id_set(EquipSlot::ActiveMainhand)
.and_then(|ids| ids.abilities.get(index).map(|(_, id)| id)),
Ability::OffWeaponAbility(index) => ability_id_set(EquipSlot::ActiveOffhand)
.and_then(|ids| ids.abilities.get(index).map(|(_, id)| id)),
Ability::Empty => None,
}
}
}
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum CharacterAbilityType {
BasicMelee,

View File

@ -434,6 +434,7 @@ impl PartialEq for ItemDef {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ItemConfig {
pub abilities: AbilitySet<CharacterAbility>,
pub ability_ids: AbilitySet<String>,
}
#[derive(Debug)]
@ -448,32 +449,52 @@ impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
(item, ability_map, msm): (&Item, &AbilityMap, &MaterialStatManifest),
) -> Result<Self, Self::Error> {
if let ItemKind::Tool(tool) = &item.kind {
// TODO: Maybe try to make an ecs resource?
let ability_ids_map =
AbilityMap::<String>::load_expect_cloned("common.abilities.ability_set_manifest");
// If no custom ability set is specified, fall back to abilityset of tool kind.
let tool_default = ability_map
.get_ability_set(&AbilitySpec::Tool(tool.kind))
.cloned();
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, msm, &item.components)
let (tool_default, tool_default_ids) = {
let key = &AbilitySpec::Tool(tool.kind);
(
ability_map.get_ability_set(key).cloned(),
ability_ids_map.get_ability_set(key).cloned(),
)
};
let (abilities, ability_ids) = if let Some(set_key) = item.ability_spec() {
if let (Some(set), Some(ids)) = (
ability_map.get_ability_set(set_key),
ability_ids_map.get_ability_set(set_key),
) {
(
set.clone().modified_by_tool(tool, msm, &item.components),
ids.clone(),
)
} else {
error!(
"Custom ability set: {:?} references non-existent set, falling back to \
default ability set.",
set_key
);
tool_default.unwrap_or_default()
(
tool_default.unwrap_or_default(),
tool_default_ids.unwrap_or_default(),
)
}
} else if let Some(set) = tool_default {
set.modified_by_tool(tool, msm, &item.components)
} else if let (Some(set), Some(ids)) = (tool_default, tool_default_ids) {
(set.modified_by_tool(tool, msm, &item.components), ids)
} else {
error!(
"No ability set defined for tool: {:?}, falling back to default ability set.",
tool.kind
);
Default::default()
(Default::default(), Default::default())
};
Ok(ItemConfig { abilities })
Ok(ItemConfig {
abilities,
ability_ids,
})
} else {
Err(ItemConfigError::BadItemKind)
}

View File

@ -386,6 +386,16 @@ impl Default for AbilitySet<CharacterAbility> {
}
}
impl Default for AbilitySet<String> {
fn default() -> Self {
AbilitySet {
primary: "".to_string(),
secondary: "".to_string(),
abilities: vec![],
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum AbilitySpec {
Tool(ToolKind),

View File

@ -2,7 +2,7 @@ use super::{
hotbar,
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
slots, BarNumbers, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
slots, util, BarNumbers, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
QUALITY_EPIC, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
};
use crate::{
@ -21,11 +21,8 @@ use i18n::Localization;
use client::{self, Client};
use common::comp::{
self,
inventory::slot::EquipSlot,
item::{
tool::{Tool, ToolKind},
Hands, Item, ItemDesc, ItemKind, MaterialStatManifest,
},
controller::InputKind,
item::{ItemDesc, MaterialStatManifest},
AbilityPool, Body, Energy, Health, Inventory, SkillSet,
};
use conrod_core::{
@ -609,22 +606,11 @@ impl<'a> Skillbar<'a> {
hotbar::SlotContents::Inventory(i) => inventory
.get(i)
.map(|item| (item.name(), item.description())),
hotbar::SlotContents::Ability(i) => {
use comp::Ability;
ability_pool
.abilities
.get(i)
.and_then(|a| match a {
Ability::MainWeaponAbility(_) => Some(EquipSlot::ActiveMainhand),
Ability::OffWeaponAbility(_) => Some(EquipSlot::ActiveOffhand),
_ => None,
})
.and_then(|equip_slot| inventory.equipped(equip_slot))
.and_then(|item| match &item.kind {
ItemKind::Tool(Tool { kind, .. }) => ability_description(kind),
_ => None,
})
},
hotbar::SlotContents::Ability(i) => ability_pool
.abilities
.get(i)
.and_then(|a| a.ability_id(Some(inventory)))
.and_then(|id| util::ability_description(id)),
})
};
@ -693,30 +679,11 @@ impl<'a> Skillbar<'a> {
.right_from(state.ids.slot5, slot_offset)
.set(state.ids.m1_slot_bg, ui);
let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
let primary_ability_id = self.ability_pool.primary.ability_id(Some(self.inventory));
let tool = match (
active_tool.map(|(_, x)| x.hands),
second_tool.map(|(_, x)| x.hands),
) {
(Some(_), _) => active_tool,
(_, Some(_)) => second_tool,
(_, _) => None,
};
Button::image(match tool.map(|(_, t)| t.kind) {
Some(ToolKind::Sword) => self.imgs.twohsword_m1,
Some(ToolKind::Dagger) => self.imgs.onehdagger_m1,
Some(ToolKind::Shield) => self.imgs.onehshield_m1,
Some(ToolKind::Hammer) => self.imgs.twohhammer_m1,
Some(ToolKind::Axe) => self.imgs.twohaxe_m1,
Some(ToolKind::Bow) => self.imgs.bow_m1,
Some(ToolKind::Sceptre) => self.imgs.skill_sceptre_lifesteal,
Some(ToolKind::Staff) => self.imgs.fireball,
Some(ToolKind::Debug) => self.imgs.flyingrod_m1,
_ => self.imgs.nothing,
}) // Insert Icon here
Button::image(
primary_ability_id.map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)),
)
.w_h(36.0, 36.0)
.middle_of(state.ids.m1_slot_bg)
.set(state.ids.m1_content, ui);
@ -726,64 +693,30 @@ impl<'a> Skillbar<'a> {
.right_from(state.ids.m1_slot_bg, slot_offset)
.set(state.ids.m2_slot_bg, ui);
fn get_item_and_tool(
inventory: &Inventory,
equip_slot: EquipSlot,
) -> Option<(&Item, &Tool)> {
match inventory.equipped(equip_slot).map(|i| (i, i.kind())) {
Some((i, ItemKind::Tool(tool))) => Some((i, tool)),
_ => None,
}
}
let secondary_ability_id = self.ability_pool.secondary.ability_id(Some(self.inventory));
let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
let tool = match (
active_tool.map(|(_, x)| x.hands),
second_tool.map(|(_, x)| x.hands),
) {
(Some(Hands::Two), _) => active_tool,
(Some(_), Some(Hands::One)) => second_tool,
(Some(Hands::One), _) => active_tool,
(None, Some(_)) => second_tool,
(_, _) => None,
};
Button::image(match tool.map(|(_, t)| t.kind) {
Some(ToolKind::Sword) => self.imgs.twohsword_m2,
Some(ToolKind::Dagger) => self.imgs.onehdagger_m2,
Some(ToolKind::Shield) => self.imgs.onehshield_m2,
Some(ToolKind::Hammer) => self.imgs.hammergolf,
Some(ToolKind::Axe) => self.imgs.axespin,
Some(ToolKind::Bow) => self.imgs.bow_m2,
Some(ToolKind::Sceptre) => self.imgs.skill_sceptre_heal,
Some(ToolKind::Staff) => self.imgs.flamethrower,
Some(ToolKind::Debug) => self.imgs.flyingrod_m2,
_ => self.imgs.nothing,
})
Button::image(
secondary_ability_id.map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)),
)
.w_h(36.0, 36.0)
.middle_of(state.ids.m2_slot_bg)
.image_color(if let Some((item, tool)) = tool {
.image_color(
if self.energy.current()
>= item
.item_config_expect()
.abilities
.secondary
.clone()
.adjusted_by_skills(self.skillset, Some(tool.kind))
.get_energy_cost()
>= self
.ability_pool
.activate_ability(
InputKind::Secondary,
Some(self.inventory),
self.skillset,
self.body,
)
.map_or(0.0, |(a, _)| a.get_energy_cost())
{
Color::Rgba(1.0, 1.0, 1.0, 1.0)
} else {
Color::Rgba(0.3, 0.3, 0.3, 0.8)
}
} else {
match tool.map(|(_, t)| t.kind) {
None => Color::Rgba(1.0, 1.0, 1.0, 0.0),
_ => Color::Rgba(1.0, 1.0, 1.0, 1.0),
}
})
},
)
.set(state.ids.m2_content, ui);
// M1 and M2 icons
@ -885,48 +818,3 @@ impl<'a> Widget for Skillbar<'a> {
}
}
}
#[rustfmt::skip]
fn ability_description(tool: &ToolKind) -> Option<(&str, &str)> {
match tool {
ToolKind::Hammer => Some((
"Smash of Doom",
"\n\
An AOE attack with knockback.\n\
Leaps to position of cursor.",
)),
ToolKind::Axe => Some((
"Axe Jump",
"\n\
A jump with the slashing leap to position of cursor.",
)),
ToolKind::Staff => Some((
"Ring of Fire",
"\n\
Ignites the ground with fiery shockwave.",
)),
ToolKind::Sword => Some((
"Whirlwind",
"\n\
Move forward while spinning with your sword.",
)),
ToolKind::Bow => Some((
"Burst",
"\n\
Launches a burst of arrows",
)),
ToolKind::Sceptre => Some((
"Thorn Bulwark",
"\n\
Protects you and your group with thorns\n\
for a short amount of time.",
)),
ToolKind::Debug => Some((
"Possessing Arrow",
"\n\
Shoots a poisonous arrow.\n\
Lets you control your target.",
)),
_ => None,
}
}

View File

@ -2,17 +2,11 @@ use super::{
hotbar::{self, Slot as HotbarSlot},
img_ids,
item_imgs::{ItemImgs, ItemKey},
util,
};
use crate::ui::slot::{self, SlotKey, SumSlot};
use common::comp::{
self,
controller::InputKind,
item::{
tool::{Tool, ToolKind},
ItemKind,
},
slot::InvSlotId,
AbilityPool, Body, Energy, Inventory, SkillSet,
controller::InputKind, slot::InvSlotId, AbilityPool, Body, Energy, Inventory, SkillSet,
};
use conrod_core::{image, Color};
use specs::Entity as EcsEntity;
@ -115,13 +109,7 @@ impl SlotKey<Inventory, ItemImgs> for TradeSlot {
#[derive(Clone, PartialEq)]
pub enum HotbarImage {
Item(ItemKey),
FireAoe,
SnakeArrow,
SwordWhirlwind,
HammerLeap,
AxeLeapSlash,
BowJumpBurst,
SceptreAura,
Ability(String),
}
type HotbarSource<'a> = (
@ -147,22 +135,13 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
.map(|item| HotbarImage::Item(item.into()))
.map(|i| (i, None)),
hotbar::SlotContents::Ability(i) => {
use comp::Ability;
let tool_kind = ability_pool
let ability_id = ability_pool
.abilities
.get(i)
.and_then(|a| match a {
Ability::MainWeaponAbility(_) => Some(EquipSlot::ActiveMainhand),
Ability::OffWeaponAbility(_) => Some(EquipSlot::ActiveOffhand),
_ => None,
})
.and_then(|equip_slot| inventory.equipped(equip_slot))
.and_then(|item| match &item.kind {
ItemKind::Tool(Tool { kind, .. }) => Some(kind),
_ => None,
});
tool_kind
.and_then(|kind| hotbar_image(*kind))
.and_then(|a| a.ability_id(Some(inventory)));
ability_id
.map(|id| HotbarImage::Ability(id.to_string()))
.and_then(|image| {
ability_pool
.activate_ability(
@ -203,13 +182,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
) -> Vec<image::Id> {
match key {
HotbarImage::Item(key) => item_imgs.img_ids_or_not_found_img(key.clone()),
HotbarImage::SnakeArrow => vec![imgs.snake_arrow_0],
HotbarImage::FireAoe => vec![imgs.fire_aoe],
HotbarImage::SwordWhirlwind => vec![imgs.sword_whirlwind],
HotbarImage::HammerLeap => vec![imgs.hammerleap],
HotbarImage::AxeLeapSlash => vec![imgs.skill_axe_leap_slash],
HotbarImage::BowJumpBurst => vec![imgs.skill_bow_jump_burst],
HotbarImage::SceptreAura => vec![imgs.skill_sceptre_aura],
HotbarImage::Ability(ability_id) => vec![util::ability_image(imgs, ability_id)],
}
}
}
@ -230,16 +203,3 @@ impl From<TradeSlot> for SlotKind {
}
impl SumSlot for SlotKind {}
fn hotbar_image(tool: ToolKind) -> Option<HotbarImage> {
match tool {
ToolKind::Staff => Some(HotbarImage::FireAoe),
ToolKind::Hammer => Some(HotbarImage::HammerLeap),
ToolKind::Axe => Some(HotbarImage::AxeLeapSlash),
ToolKind::Bow => Some(HotbarImage::BowJumpBurst),
ToolKind::Debug => Some(HotbarImage::SnakeArrow),
ToolKind::Sword => Some(HotbarImage::SwordWhirlwind),
ToolKind::Sceptre => Some(HotbarImage::SceptreAura),
_ => None,
}
}

View File

@ -1,3 +1,4 @@
use super::img_ids;
use common::{
comp::{
inventory::trade_pricing::TradePricing,
@ -11,6 +12,7 @@ use common::{
effect::Effect,
trade::{Good, SitePrices},
};
use conrod_core::image;
use i18n::Localization;
use std::{borrow::Cow, fmt::Write};
@ -282,3 +284,95 @@ pub fn protec2string(stat: Protection) -> String {
Protection::Invincible => "Inf".to_string(),
}
}
pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id {
match ability_id {
// Debug stick
"common.abilities.debug.forwardboost" => imgs.flyingrod_m1,
"common.abilities.debug.upboost" => imgs.flyingrod_m2,
"common.abilities.debug.possess" => imgs.snake_arrow_0,
// Sword
"common.abilities.sword.triplestrike" => imgs.twohsword_m1,
"common.abilities.sword.dash" => imgs.twohsword_m2,
"common.abilities.sword.spin" => imgs.sword_whirlwind,
// Axe
"common.abilities.axe.doublestrike" => imgs.twohaxe_m1,
"common.abilities.axe.spin" => imgs.axespin,
"common.abilities.axe.leap" => imgs.skill_axe_leap_slash,
// Hammer
"common.abilities.hammer.singlestrike" => imgs.twohhammer_m1,
"common.abilities.hammer.charged" => imgs.hammergolf,
"common.abilities.hammer.leap" => imgs.hammerleap,
// Bow
"common.abilities.bow.charged" => imgs.bow_m1,
"common.abilities.bow.repeater" => imgs.bow_m2,
"common.abilities.bow.shotgun" => imgs.skill_bow_jump_burst,
// Staff
"common.abilities.staff.firebomb" => imgs.fireball,
"common.abilities.staff.flamethrower" => imgs.flamethrower,
"common.abilities.staff.fireshockwave" => imgs.fire_aoe,
// Sceptre
"common.abilities.sceptre.lifestealbeam" => imgs.skill_sceptre_lifesteal,
"common.abilities.sceptre.healingaura" => imgs.skill_sceptre_heal,
"common.abilities.sceptre.wardingaura" => imgs.skill_sceptre_aura,
// Shield
"common.abilities.shield.tempbasic" => imgs.onehshield_m1,
"common.abilities.shield.block" => imgs.onehshield_m2,
// Dagger
"common.abilities.dagger.tempbasic" => imgs.onehdagger_m1,
_ => imgs.nothing,
}
}
#[rustfmt::skip]
pub fn ability_description(ability_id: &str) -> Option<(&str, &str)> {
match ability_id {
// Debug stick
"common.abilities.debug.possess" => Some((
"Possessing Arrow",
"\n\
Shoots a poisonous arrow.\n\
Lets you control your target.",
)),
// Sword
"common.abilities.sword.spin" => Some((
"Whirlwind",
"\n\
Move forward while spinning with your sword.",
)),
// Axe
"common.abilities.axe.leap" => Some((
"Axe Jump",
"\n\
A jump with the slashing leap to position of cursor.",
)),
// Hammer
"common.abilities.hammer.leap" => Some((
"Smash of Doom",
"\n\
An AOE attack with knockback.\n\
Leaps to position of cursor.",
)),
// Bow
"common.abilities.bow.shotgun" => Some((
"Burst",
"\n\
Launches a burst of arrows",
)),
// Staff
"common.abilities.staff.fireshockwave" => Some((
"Ring of Fire",
"\n\
Ignites the ground with fiery shockwave.",
)),
// Sceptre
"common.abilities.sceptre.wardingaura" => Some((
"Thorn Bulwark",
"\n\
Protects you and your group with thorns\n\
for a short amount of time.",
)),
_ => None,
}
}