469 lines
13 KiB
Rust
469 lines
13 KiB
Rust
use anyhow::Result;
|
|
use assetpath::AssetPath;
|
|
use engine::prelude::*;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::{
|
|
fmt,
|
|
fmt::Debug,
|
|
hash::{Hash, Hasher},
|
|
slice::Iter,
|
|
str::FromStr,
|
|
};
|
|
|
|
use std::sync::Arc;
|
|
|
|
use crate::{components::inventory::Storable, config::abilities::AbilitySettings};
|
|
|
|
use super::{ability_book::Ability, ItemSystem, Rarities, Tooltip};
|
|
|
|
const COOL_DOWN_REDUCTION_CAP: f32 = 0.7;
|
|
|
|
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
|
pub enum AbilityAddonTypes {
|
|
Damage(u32),
|
|
ProjectileSpeed(f32),
|
|
Bounce,
|
|
Explosion(f32), // radius
|
|
Size(f32),
|
|
Projectiles(u32),
|
|
CoolDown(f32), // %
|
|
Distance(f32), // m
|
|
// TODO: Pierce, // bool
|
|
}
|
|
|
|
impl AbilityAddonTypes {
|
|
pub const COUNT: u32 = 8;
|
|
|
|
pub fn iter() -> Iter<'static, Self> {
|
|
use AbilityAddonTypes::*;
|
|
|
|
static ADD_ON_TYPES: [AbilityAddonTypes; AbilityAddonTypes::COUNT as usize] = [
|
|
Damage(0),
|
|
ProjectileSpeed(0.0),
|
|
Bounce,
|
|
Explosion(0.0),
|
|
Size(0.0),
|
|
Projectiles(0),
|
|
CoolDown(0.0),
|
|
Distance(0.0),
|
|
];
|
|
|
|
ADD_ON_TYPES.iter()
|
|
}
|
|
|
|
pub fn random() -> Self {
|
|
let n = Random::range(0, Self::COUNT);
|
|
|
|
Self::from(n)
|
|
}
|
|
|
|
pub fn val_as_str(&self) -> String {
|
|
match self {
|
|
Self::Damage(v) => format!("{} (*lvl)", v),
|
|
Self::ProjectileSpeed(v) => format!("+ {:.1}", v),
|
|
Self::Bounce => "Enabled".to_string(),
|
|
Self::Explosion(v) => format!("+ {:.1}", v),
|
|
Self::Size(v) => format!("+ {:.0}%", v * 100.0),
|
|
Self::Projectiles(v) => format!("+ {}", v),
|
|
Self::CoolDown(v) => format!("- {:.0}%", v * 100.0),
|
|
Self::Distance(v) => format!("+ {}", v),
|
|
}
|
|
}
|
|
|
|
pub fn apply_rarity(&mut self, rarity: Rarities, ability_settings: &AbilitySettings) {
|
|
match self {
|
|
Self::Damage(v) => *v = ability_settings.damage.from_rarity(rarity),
|
|
Self::ProjectileSpeed(v) => *v = ability_settings.projectile_speed.from_rarity(rarity),
|
|
Self::Bounce => (),
|
|
Self::Explosion(v) => *v = ability_settings.explosion.from_rarity(rarity),
|
|
Self::Size(v) => *v = ability_settings.size.from_rarity(rarity),
|
|
Self::Projectiles(v) => {
|
|
*v = ability_settings.additional_projectiles.from_rarity(rarity)
|
|
}
|
|
Self::CoolDown(v) => {
|
|
*v = ability_settings.cool_down.from_rarity(rarity);
|
|
}
|
|
Self::Distance(v) => *v = ability_settings.distance.from_rarity(rarity),
|
|
}
|
|
}
|
|
|
|
pub fn get_path<'a>(&self, ability_settings: &'a AbilitySettings) -> &'a AssetPath {
|
|
match self {
|
|
Self::Damage(_) => &ability_settings.icons.damage,
|
|
Self::ProjectileSpeed(_) => &ability_settings.icons.projectile_speed,
|
|
Self::Bounce => &ability_settings.icons.bounce,
|
|
Self::Explosion(_) => &ability_settings.icons.explosion,
|
|
Self::Size(_) => &ability_settings.icons.size,
|
|
Self::Projectiles(_) => &ability_settings.icons.additional_projectiles,
|
|
Self::CoolDown(_) => &ability_settings.icons.cool_down,
|
|
Self::Distance(_) => &ability_settings.icons.distance,
|
|
}
|
|
}
|
|
|
|
pub fn into_zero(self) -> Self {
|
|
match self {
|
|
AbilityAddonTypes::Damage(_) => AbilityAddonTypes::Damage(0),
|
|
AbilityAddonTypes::ProjectileSpeed(_) => AbilityAddonTypes::ProjectileSpeed(0.0),
|
|
AbilityAddonTypes::Bounce => AbilityAddonTypes::Bounce,
|
|
AbilityAddonTypes::Explosion(_) => AbilityAddonTypes::Explosion(0.0),
|
|
AbilityAddonTypes::Size(_) => AbilityAddonTypes::Size(0.0),
|
|
AbilityAddonTypes::Projectiles(_) => AbilityAddonTypes::Projectiles(0),
|
|
AbilityAddonTypes::CoolDown(_) => AbilityAddonTypes::CoolDown(0.0),
|
|
AbilityAddonTypes::Distance(_) => AbilityAddonTypes::Distance(0.0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::str::FromStr for AbilityAddonTypes {
|
|
type Err = anyhow::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
match s {
|
|
"Damage" => Ok(Self::Damage(0)),
|
|
"Projectile Speed" => Ok(Self::ProjectileSpeed(0.0)),
|
|
"Bounce" => Ok(Self::Bounce),
|
|
"Explosion" => Ok(Self::Explosion(0.0)),
|
|
"Size" => Ok(Self::Size(0.0)),
|
|
"Projectiles" => Ok(Self::Projectiles(0)),
|
|
"Cool Down" => Ok(Self::CoolDown(0.0)),
|
|
"Distance" => Ok(Self::Distance(0.0)),
|
|
|
|
_ => {
|
|
return Err(anyhow::Error::msg(format!(
|
|
"Failed parsing AbilityAddonTypes from {}",
|
|
s
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for AbilityAddonTypes {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Damage(_) => write!(f, "Damage"),
|
|
Self::ProjectileSpeed(_) => write!(f, "Projectile Speed"),
|
|
Self::Bounce => write!(f, "Bounce"),
|
|
Self::Explosion(_) => write!(f, "Explosion"),
|
|
Self::Size(_) => write!(f, "Size"),
|
|
Self::Projectiles(_) => write!(f, "Projectiles"),
|
|
Self::CoolDown(_) => write!(f, "Cool Down"),
|
|
Self::Distance(_) => write!(f, "Distance"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<u32> for AbilityAddonTypes {
|
|
fn from(n: u32) -> Self {
|
|
match n {
|
|
0 => Self::Damage(0),
|
|
1 => Self::ProjectileSpeed(0.0),
|
|
2 => Self::Bounce,
|
|
3 => Self::Explosion(0.0),
|
|
4 => Self::Size(0.0),
|
|
5 => Self::Projectiles(0),
|
|
6 => Self::CoolDown(0.0),
|
|
7 => Self::Distance(0.0),
|
|
|
|
_ => panic!(
|
|
"can't convert AbilityAddonTypes from bigger than {}",
|
|
Self::COUNT
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::derive_hash_xor_eq)]
|
|
impl Hash for AbilityAddonTypes {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
match self {
|
|
Self::Damage(_) => 0u32.hash(state),
|
|
Self::ProjectileSpeed(_) => 1u32.hash(state),
|
|
Self::Bounce => 2u32.hash(state),
|
|
Self::Explosion(_) => 3u32.hash(state),
|
|
Self::Size(_) => 4u32.hash(state),
|
|
Self::Projectiles(_) => 5u32.hash(state),
|
|
Self::CoolDown(_) => 6u32.hash(state),
|
|
Self::Distance(_) => 7u32.hash(state),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Eq for AbilityAddonTypes {}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AbilityAddon {
|
|
icon: Arc<Image>,
|
|
|
|
addon_type: AbilityAddonTypes,
|
|
|
|
rarity: Rarities,
|
|
}
|
|
|
|
impl AbilityAddon {
|
|
pub fn new(rarity: Rarities, addon_type: AbilityAddonTypes, icon: Arc<Image>) -> Self {
|
|
AbilityAddon {
|
|
addon_type,
|
|
rarity,
|
|
|
|
icon,
|
|
}
|
|
}
|
|
|
|
pub fn load(
|
|
mut addon_type: AbilityAddonTypes,
|
|
rarity: Rarities,
|
|
icon: Arc<Image>,
|
|
ability_settings: &AbilitySettings,
|
|
) -> Self {
|
|
addon_type.apply_rarity(rarity, ability_settings);
|
|
|
|
AbilityAddon {
|
|
addon_type,
|
|
rarity,
|
|
|
|
icon,
|
|
}
|
|
}
|
|
|
|
pub fn addon_type(&self) -> &AbilityAddonTypes {
|
|
&self.addon_type
|
|
}
|
|
|
|
pub fn into_persistent(&self) -> String {
|
|
format!("{}|{}", self.addon_type(), self.rarity())
|
|
}
|
|
|
|
pub fn from_persistent<'a, A: Ability>(
|
|
mut split: impl Iterator<Item = &'a str>,
|
|
item_system: &ItemSystem<A>,
|
|
) -> Result<Self> {
|
|
let addon_type = AbilityAddonTypes::from_str(split.next().unwrap())?;
|
|
let rarity = Rarities::from_str(split.next().unwrap())?;
|
|
|
|
Ok(item_system.addon(rarity, addon_type))
|
|
}
|
|
|
|
pub fn create_tooltip(
|
|
&self,
|
|
gui_handler: &Arc<GuiHandler>,
|
|
position: (i32, i32),
|
|
) -> Result<Tooltip> {
|
|
let gui = GuiBuilder::from_str(
|
|
gui_handler,
|
|
include_str!("../../resources/addon_snippet.xml"),
|
|
)?;
|
|
|
|
let icon: Arc<Icon> = gui.element("addon_icon")?;
|
|
let rarity_label: Arc<Label> = gui.element("rarity_label")?;
|
|
let type_label: Arc<Label> = gui.element("type")?;
|
|
let value_label: Arc<Label> = gui.element("value")?;
|
|
let grid: Arc<Grid> = gui.element("addon_grid")?;
|
|
|
|
grid.change_position_unscaled(position.0, position.1)?;
|
|
|
|
icon.set_icon(&self.icon())?;
|
|
rarity_label.set_text(&format!("{}", self.rarity()))?;
|
|
|
|
type_label.set_text(&format!("{}", self.addon_type()))?;
|
|
value_label.set_text(&self.addon_type().val_as_str())?;
|
|
|
|
Ok(Tooltip::new(grid, gui, gui_handler.clone()))
|
|
}
|
|
}
|
|
|
|
impl PartialEq for AbilityAddon {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.addon_type == other.addon_type && self.rarity == other.rarity
|
|
}
|
|
}
|
|
|
|
impl Storable for AbilityAddon {
|
|
fn rarity(&self) -> Rarities {
|
|
self.rarity
|
|
}
|
|
|
|
fn icon(&self) -> Arc<Image> {
|
|
self.icon.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct AbilityAddonCollection {
|
|
attached_addons: usize,
|
|
addons: Vec<Option<AbilityAddon>>,
|
|
}
|
|
|
|
impl AbilityAddonCollection {
|
|
pub fn new(rarity: Rarities, ability_settings: &AbilitySettings) -> Self {
|
|
let addons = (0..ability_settings.slot_count.from_rarity(rarity))
|
|
.map(|_| None)
|
|
.collect();
|
|
|
|
Self {
|
|
attached_addons: 0,
|
|
addons,
|
|
}
|
|
}
|
|
|
|
pub fn load(
|
|
mut addons: Vec<Option<AbilityAddon>>,
|
|
rarity: Rarities,
|
|
ability_settings: &AbilitySettings,
|
|
) -> Self {
|
|
let attached = addons.len();
|
|
let max = ability_settings.slot_count.from_rarity(rarity) as usize;
|
|
|
|
assert!(attached <= max);
|
|
|
|
let difference = max - attached;
|
|
|
|
// fill not set addons with None
|
|
for _ in 0..difference {
|
|
addons.push(None);
|
|
}
|
|
|
|
Self {
|
|
attached_addons: attached,
|
|
addons,
|
|
}
|
|
}
|
|
|
|
pub fn iter(&self) -> Iter<'_, Option<AbilityAddon>> {
|
|
self.addons.iter()
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.addons.len()
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.addons.is_empty()
|
|
}
|
|
|
|
pub fn has_free_addon_slots(&self) -> bool {
|
|
self.attached_addons < self.addons.len()
|
|
}
|
|
|
|
pub fn attached_count(&self) -> usize {
|
|
self.attached_addons
|
|
}
|
|
|
|
pub fn insert_addon(&mut self, addon: AbilityAddon) {
|
|
if !self.has_free_addon_slots() {
|
|
panic!("Ability Book does not have any free slots left");
|
|
}
|
|
|
|
self.addons[self.attached_addons] = Some(addon);
|
|
self.attached_addons += 1;
|
|
}
|
|
|
|
#[inline]
|
|
fn map<F>(&self, mut f: F)
|
|
where
|
|
F: FnMut(&AbilityAddonTypes),
|
|
{
|
|
for addon in self.addons.iter().flatten() {
|
|
f(addon.addon_type());
|
|
}
|
|
}
|
|
|
|
pub fn damage(&self) -> u32 {
|
|
let mut damage = 0;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::Damage(dmg) = addon_type {
|
|
damage += dmg;
|
|
}
|
|
});
|
|
|
|
damage
|
|
}
|
|
|
|
pub fn projectile_speed(&self) -> f32 {
|
|
let mut speed = 0.0;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::ProjectileSpeed(s) = addon_type {
|
|
speed += s;
|
|
}
|
|
});
|
|
|
|
speed
|
|
}
|
|
|
|
pub fn bounce(&self) -> bool {
|
|
let mut bounce = false;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::Bounce = addon_type {
|
|
bounce = true;
|
|
}
|
|
});
|
|
|
|
bounce
|
|
}
|
|
|
|
pub fn explosion_radius(&self) -> f32 {
|
|
let mut radius = 0.0;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::Explosion(r) = addon_type {
|
|
radius += r
|
|
}
|
|
});
|
|
|
|
radius
|
|
}
|
|
|
|
pub fn size(&self) -> f32 {
|
|
let mut size = 1.0;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::Size(s) = addon_type {
|
|
size += s;
|
|
}
|
|
});
|
|
|
|
size
|
|
}
|
|
|
|
pub fn additional_projectiles(&self) -> u32 {
|
|
let mut projectiles = 0;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::Projectiles(p) = addon_type {
|
|
projectiles += p;
|
|
}
|
|
});
|
|
|
|
projectiles
|
|
}
|
|
|
|
pub fn distance(&self) -> f32 {
|
|
let mut distance = 0.0;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::Distance(d) = addon_type {
|
|
distance += d;
|
|
}
|
|
});
|
|
|
|
distance
|
|
}
|
|
|
|
pub fn cool_down_reduction(&self) -> f32 {
|
|
let mut cdr = 0.0;
|
|
|
|
self.map(|addon_type| {
|
|
if let AbilityAddonTypes::CoolDown(cd) = addon_type {
|
|
cdr += cd;
|
|
}
|
|
});
|
|
|
|
let cap = COOL_DOWN_REDUCTION_CAP;
|
|
|
|
cdr.min(cap)
|
|
}
|
|
}
|