use alloc::{
boxed::Box,
format,
vec::Vec,
};
use anyhow::Error;
use battler_data::{
Fraction,
HitEffect,
Id,
Identifiable,
MaxMoveData,
MoveCategory,
MoveData,
MoveFlag,
SecondaryEffectData,
Type,
ZMoveData,
};
use hashbrown::{
HashMap,
HashSet,
hash_map::Entry,
};
use crate::{
battle::{
EventResult,
MonHandle,
},
effect::fxlang,
general_error,
};
#[derive(Clone)]
pub struct MoveHitData {
pub crit: bool,
pub type_modifier: i8,
pub damage: u64,
pub flags: HashSet<Id>,
}
impl MoveHitData {
pub fn new() -> Self {
Self {
crit: false,
type_modifier: 0,
damage: 0,
flags: HashSet::default(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MoveHitEffectType {
PrimaryEffect,
SecondaryEffect(MonHandle, u8, usize),
}
impl MoveHitEffectType {
pub fn secondary_index(&self) -> Option<(MonHandle, u8, usize)> {
match self {
Self::PrimaryEffect => None,
Self::SecondaryEffect(mon, hit, index) => Some((*mon, *hit, *index)),
}
}
}
#[derive(Clone)]
pub struct SecondaryEffect {
pub data: SecondaryEffectData,
pub effect: fxlang::Effect,
}
impl SecondaryEffect {
pub fn new(data: SecondaryEffectData) -> Self {
let effect = data.effect.clone().try_into().unwrap_or_default();
Self { data, effect }
}
}
#[derive(Debug, Clone)]
pub enum UpgradedMoveSource {
ZMove { base_move: Id },
MaxMove { base_move: Id },
}
impl UpgradedMoveSource {
pub fn base_move(&self) -> Option<Id> {
match self {
Self::ZMove { base_move, .. } => Some(base_move.clone()),
Self::MaxMove { base_move, .. } => Some(base_move.clone()),
}
}
}
fn default_z_move(
id: &Id,
category: MoveCategory,
multihit: bool,
mut base_power: u32,
) -> Option<ZMoveData> {
if category == MoveCategory::Status || id == "struggle" {
return None;
}
if multihit {
base_power *= 3;
}
let base_power = if base_power == 0 {
100
} else if base_power >= 140 {
200
} else if base_power >= 130 {
195
} else if base_power >= 120 {
190
} else if base_power >= 110 {
185
} else if base_power >= 100 {
180
} else if base_power >= 90 {
175
} else if base_power >= 80 {
160
} else if base_power >= 70 {
140
} else if base_power >= 60 {
120
} else {
100
};
Some(ZMoveData {
base_power,
..Default::default()
})
}
fn default_max_move(
id: &Id,
category: MoveCategory,
primary_type: Type,
base_power: u32,
) -> Option<MaxMoveData> {
if category == MoveCategory::Status || id == "struggle" {
return None;
}
let base_power = if primary_type == Type::Fighting || primary_type == Type::Poison {
if base_power >= 150 {
100
} else if base_power >= 110 {
95
} else if base_power >= 75 {
90
} else if base_power >= 65 {
85
} else if base_power >= 55 {
80
} else if base_power >= 45 {
75
} else {
70
}
} else {
if base_power >= 150 {
150
} else if base_power >= 110 {
140
} else if base_power >= 75 {
130
} else if base_power >= 65 {
120
} else if base_power >= 55 {
110
} else if base_power >= 45 {
100
} else {
90
}
};
Some(MaxMoveData { base_power })
}
#[derive(Clone)]
pub struct Move {
id: Id,
pub data: MoveData,
pub effect: fxlang::Effect,
pub condition: fxlang::Effect,
pub priority: i8,
pub used_by: Option<MonHandle>,
pub external: bool,
pub spread_hit: bool,
pub hit: u8,
pub total_damage: u64,
pub original_targets: Vec<MonHandle>,
pub damaged_targets: Vec<MonHandle>,
pub primary_user_effect_applied: bool,
pub upgraded: Option<UpgradedMoveSource>,
pub ignore_all_secondary_effects: bool,
pub last_move_log: Option<usize>,
pub fail_reported: bool,
pub force_try_hit_result: Option<EventResult>,
pub target_original_hps: HashMap<MonHandle, u16>,
pub effect_state: fxlang::EffectState,
pub unlinked: bool,
pub secondary_effects: HashMap<(MonHandle, u8), Vec<SecondaryEffect>>,
hit_data: HashMap<(MonHandle, u8), MoveHitData>,
}
impl Move {
fn apply_defaults_to_data(mut data: MoveData, id: &Id) -> MoveData {
if data.z_move.is_none() && !data.flags.contains(&MoveFlag::Z) {
data.z_move =
default_z_move(id, data.category, data.multihit.is_some(), data.base_power);
}
if data.max_move.is_none() && !data.flags.contains(&MoveFlag::Max) {
data.max_move = default_max_move(id, data.category, data.primary_type, data.base_power);
}
data
}
fn new_internal(id: Id, data: MoveData, unlinked: bool) -> Self {
let data = Self::apply_defaults_to_data(data, &id);
let effect = data.effect.clone().try_into().unwrap_or_default();
let condition = data.condition.clone().try_into().unwrap_or_default();
Self {
id,
data,
effect,
condition,
priority: 0,
used_by: None,
external: false,
spread_hit: false,
hit: 0,
total_damage: 0,
original_targets: Vec::default(),
damaged_targets: Vec::default(),
primary_user_effect_applied: false,
upgraded: None,
ignore_all_secondary_effects: false,
last_move_log: None,
fail_reported: false,
force_try_hit_result: None,
target_original_hps: HashMap::default(),
effect_state: fxlang::EffectState::default(),
unlinked,
secondary_effects: HashMap::default(),
hit_data: HashMap::default(),
}
}
pub fn new(id: Id, data: MoveData) -> Self {
Self::new_internal(id, data, false)
}
pub fn new_unlinked(id: Id, data: MoveData) -> Self {
Self::new_internal(id, data, true)
}
pub fn clone_for_battle(&self) -> Self {
let mut clone = Self::new(self.id.clone(), self.data.clone());
clone.total_damage = self.total_damage;
clone.effect_state = self.effect_state.clone();
clone
}
pub fn hit_data(&self, target: MonHandle) -> Option<&MoveHitData> {
self.hit_data.get(&(target, self.hit))
}
pub fn hit_data_mut(&mut self, target: MonHandle) -> &mut MoveHitData {
self.hit_data
.entry((target, self.hit))
.or_insert(MoveHitData::new())
}
pub fn total_damage(&self, target: MonHandle) -> u64 {
self.hit_data
.iter()
.filter(|((mon, _), _)| *mon == target)
.map(|(_, hit_data)| hit_data.damage)
.sum()
}
pub fn target_hit_effect(&self, hit_effect_type: MoveHitEffectType) -> Option<&HitEffect> {
match hit_effect_type {
MoveHitEffectType::PrimaryEffect => self.data.hit_effect.as_ref(),
MoveHitEffectType::SecondaryEffect(target, hit, index) => self
.secondary_effects
.get(&(target, hit))?
.get(index)?
.data
.target
.as_ref(),
}
}
pub fn target_hit_effect_mut(
&mut self,
hit_effect_type: MoveHitEffectType,
) -> Option<&mut HitEffect> {
match hit_effect_type {
MoveHitEffectType::PrimaryEffect => self.data.hit_effect.as_mut(),
MoveHitEffectType::SecondaryEffect(target, hit, index) => self
.secondary_effects
.get_mut(&(target, hit))?
.get_mut(index)?
.data
.target
.as_mut(),
}
}
pub fn user_hit_effect(&self, hit_effect_type: MoveHitEffectType) -> Option<&HitEffect> {
match hit_effect_type {
MoveHitEffectType::PrimaryEffect => self.data.user_effect.as_ref(),
MoveHitEffectType::SecondaryEffect(target, hit, index) => self
.secondary_effects
.get(&(target, hit))?
.get(index)?
.data
.user
.as_ref(),
}
}
pub fn user_hit_effect_mut(
&mut self,
hit_effect_type: MoveHitEffectType,
) -> Option<&mut HitEffect> {
match hit_effect_type {
MoveHitEffectType::PrimaryEffect => self.data.user_effect.as_mut(),
MoveHitEffectType::SecondaryEffect(target, hit, index) => self
.secondary_effects
.get_mut(&(target, hit))?
.get_mut(index)?
.data
.user
.as_mut(),
}
}
pub fn hit_effect_source_effect(&self, hit_effect_type: MoveHitEffectType) -> Option<&str> {
match hit_effect_type {
MoveHitEffectType::PrimaryEffect => None,
MoveHitEffectType::SecondaryEffect(target, hit, index) => self
.secondary_effects
.get(&(target, hit))?
.get(index)?
.data
.source_effect
.as_ref()
.map(|s| s.as_str()),
}
}
pub fn fxlang_effect(&self, hit_effect_type: MoveHitEffectType) -> Option<&fxlang::Effect> {
match hit_effect_type {
MoveHitEffectType::PrimaryEffect => Some(&self.effect),
MoveHitEffectType::SecondaryEffect(target, hit, index) => Some(
&self
.secondary_effects
.get(&(target, hit))?
.get(index)?
.effect,
),
}
}
pub fn save_secondary_effects(
&mut self,
target: MonHandle,
secondary_effects: Vec<SecondaryEffect>,
) -> Result<(), Error> {
match self.secondary_effects.entry((target, self.hit)) {
Entry::Occupied(_) => Err(general_error(format!(
"target {target} already has secondary effects saved for hit {}",
self.hit,
))),
Entry::Vacant(entry) => {
entry.insert(secondary_effects);
Ok(())
}
}
}
pub fn secondary_effect_chances<'a>(
&'a self,
target: MonHandle,
) -> Box<dyn Iterator<Item = (usize, Option<Fraction<u16>>)> + 'a> {
match self.secondary_effects.get(&(target, self.hit)) {
Some(secondary_effects) => Box::new(
secondary_effects
.iter()
.map(|secondary_effect| secondary_effect.data.chance)
.enumerate(),
),
None => Box::new(core::iter::empty::<(usize, Option<Fraction<u16>>)>()),
}
}
pub fn callable(&self) -> bool {
!self.data.flags.contains(&MoveFlag::Max)
}
}
impl Identifiable for Move {
fn id(&self) -> &Id {
&self.id
}
}