rpg_base/rpg_components/src/items/ability_addon.rs

470 lines
13 KiB
Rust
Raw Normal View History

2025-02-28 07:43:35 +00:00
use anyhow::Result;
use assetpath::AssetPath;
use engine::prelude::*;
2025-03-03 18:06:15 +00:00
use serde::{Deserialize, Serialize};
2025-02-28 07:43:35 +00:00
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)
}
}