use alloc::string::{
String,
ToString,
};
use core::str::FromStr;
use anyhow::Result;
use hashbrown::{
HashMap,
hash_map::Entry,
};
use itertools::Itertools;
use crate::{
battle::SpeedOrderable,
effect::fxlang::{
BattleEvent,
BattleEventModifier,
Callback,
Callbacks,
ConditionAttributes,
LocalData,
ParsedProgram,
ProgramMetadata,
},
error::WrapResultError,
general_error,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedCallback {
pub program: ParsedProgram,
pub order: u32,
pub priority: i32,
pub sub_order: u32,
pub metadata: ProgramMetadata,
}
impl ParsedCallback {
fn extend(&mut self, other: Self) {
if !other.program.block.is_empty() {
self.program = other.program;
}
self.order = other.order;
self.priority = other.priority;
self.sub_order = other.sub_order;
if other.metadata != ProgramMetadata::default() {
self.metadata = other.metadata;
}
}
}
impl SpeedOrderable for ParsedCallback {
fn order(&self) -> u32 {
self.order
}
fn priority(&self) -> i32 {
self.priority
}
fn sub_priority(&self) -> i32 {
0
}
fn speed(&self) -> u32 {
0
}
fn sub_order(&self) -> u32 {
self.sub_order
}
}
#[derive(Debug, Default, Clone)]
pub struct ParsedEffect {
callbacks: HashMap<(BattleEvent, BattleEventModifier), ParsedCallback>,
condition: ConditionAttributes,
local_data: LocalData,
}
impl ParsedEffect {
fn parse_and_save(&mut self, name: &str, callback: &Callback) -> Result<()> {
let (event, modifier) = Self::callback_name_to_event_key(name)
.wrap_error_with_format(format_args!("invalid callback {name}"))?;
let program = match callback.program() {
Some(program) => ParsedProgram::from(program)
.wrap_error_with_format(format_args!("error parsing {event} callback"))?,
None => ParsedProgram::default(),
};
self.callbacks.insert(
(event, modifier),
ParsedCallback {
program,
order: callback.order(),
priority: callback.priority(),
sub_order: callback.sub_order(),
metadata: callback.metadata().cloned().unwrap_or_default(),
},
);
Ok(())
}
pub fn callback_name_to_event_key(name: &str) -> Result<(BattleEvent, BattleEventModifier)> {
let mut parts = name.split('_').multipeek();
match parts.peek().map(|s| *s) {
Some("on") => {
parts.next();
}
Some(_) => (),
None => return Err(general_error("callback name cannot be empty")),
}
parts.reset_peek();
let modifier = match parts.peek().map(|s| *s) {
Some(part) => match BattleEventModifier::from_str(part) {
Ok(modifier) => match parts.peek().map(|s| *s) {
Some("end" | "residual" | "restart" | "start") => {
BattleEventModifier::default()
}
Some(_) | None => {
parts.next();
modifier
}
},
Err(_) => BattleEventModifier::default(),
},
None => BattleEventModifier::default(),
};
let capitalize = |s: &str| {
let mut chars = s.chars();
match chars.next() {
Some(char) => char.to_uppercase().collect::<String>() + chars.as_str(),
None => String::default(),
}
};
let event = parts.map(capitalize).join("");
match BattleEvent::from_str(&event).map_err(general_error) {
Ok(event) => Ok((event, modifier)),
Err(_) => {
let event = BattleEvent::from_str(&(capitalize(&modifier.to_string()) + &event))
.map_err(general_error)?;
Ok((event, BattleEventModifier::None))
}
}
}
pub fn new(
callbacks: &Callbacks,
condition: ConditionAttributes,
local_data: LocalData,
) -> Result<Self> {
let mut parsed = Self {
callbacks: HashMap::default(),
condition,
local_data,
};
for (name, callback) in callbacks {
parsed.parse_and_save(name, callback)?;
}
Ok(parsed)
}
pub fn extend(&mut self, other: Self) {
for (key, callback) in other.callbacks {
match self.callbacks.entry(key) {
Entry::Occupied(mut entry) => entry.get_mut().extend(callback),
Entry::Vacant(entry) => {
entry.insert(callback);
}
}
}
self.condition.extend(other.condition);
self.local_data.extend(other.local_data);
}
pub fn event(
&self,
event: BattleEvent,
modifier: BattleEventModifier,
) -> Option<&ParsedCallback> {
self.callbacks.get(&(event, modifier))
}
pub(in crate::effect) fn set_event(
&mut self,
event: BattleEvent,
modifier: BattleEventModifier,
callback: ParsedCallback,
) {
self.callbacks.insert((event, modifier), callback);
}
pub(in crate::effect) fn take_event(
&mut self,
event: BattleEvent,
modifier: BattleEventModifier,
) -> Option<ParsedCallback> {
self.callbacks.remove(&(event, modifier))
}
pub fn condition(&self) -> &ConditionAttributes {
&self.condition
}
pub fn local_data(&self) -> &LocalData {
&self.local_data
}
}
#[cfg(test)]
mod parsed_effect_test {
use crate::effect::fxlang::{
BattleEvent,
BattleEventModifier,
ParsedEffect,
};
#[test]
fn parses_callback_name_to_event_and_modifier() {
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_set_status"),
Ok((BattleEvent::SetStatus, BattleEventModifier::None))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_ally_set_status"),
Ok((BattleEvent::SetStatus, BattleEventModifier::Ally))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_any_set_status"),
Ok((BattleEvent::SetStatus, BattleEventModifier::Any))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_foe_set_status"),
Ok((BattleEvent::SetStatus, BattleEventModifier::Foe))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_side_modify_atk"),
Ok((BattleEvent::ModifyAtk, BattleEventModifier::Side))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_field_modify_atk"),
Ok((BattleEvent::ModifyAtk, BattleEventModifier::Field))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_source_modify_damage"),
Ok((BattleEvent::ModifyDamage, BattleEventModifier::Source))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_residual"),
Ok((BattleEvent::Residual, BattleEventModifier::None))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_field_residual"),
Ok((BattleEvent::FieldResidual, BattleEventModifier::None))
);
assert_matches::assert_matches!(
ParsedEffect::callback_name_to_event_key("on_side_start"),
Ok((BattleEvent::SideStart, BattleEventModifier::None))
);
}
}