use alloc::{
borrow::ToOwned,
format,
string::{
String,
ToString,
},
vec::Vec,
};
use anyhow::Result;
use battler_data::{
Boost,
CopyVolatileType,
Id,
Stat,
Type,
};
use itertools::Itertools;
use crate::{
battle::{
ActiveMoveContext,
ApplyingEffectContext,
Context,
CoreBattle,
FieldEffectContext,
Mon,
MonContext,
MonHandle,
PlayerContext,
SideEffectContext,
},
battle_log_entry,
effect::{
Effect,
EffectHandle,
NonExistentEffect,
fxlang,
},
general_error,
log::UncommittedBattleLogEntry,
};
#[derive(Default)]
pub struct EffectActivationContext {
pub effect_flag_name: Option<String>,
pub effect: Option<EffectHandle>,
pub side: Option<usize>,
pub slot: Option<usize>,
pub player: Option<usize>,
pub target: Option<MonHandle>,
pub ignore_active_move_source_effect: bool,
pub ignore_source_effect_equal_to_effect: bool,
pub source_effect: Option<EffectHandle>,
pub source: Option<MonHandle>,
pub additional: Vec<String>,
}
pub fn effect_activation(
context: &mut Context,
header: String,
activation_context: EffectActivationContext,
) -> Result<()> {
let event = effect_activation_internal(context, header, activation_context)?;
context.battle_mut().log(event);
Ok(())
}
fn add_effect_to_log_entry(
context: &mut Context,
entry: &mut UncommittedBattleLogEntry,
effect_handle: &EffectHandle,
effect_flag_name: Option<&str>,
) -> Result<()> {
let effect = CoreBattle::get_effect_by_handle(context, effect_handle)?;
match effect_flag_name {
Some(effect_flag_name) => {
entry.set(effect_flag_name, effect.full_name());
}
None => match effect {
Effect::ActiveMove(active_move, _) => {
entry.set("move", &active_move.data.name);
}
Effect::MoveCondition(condition) | Effect::InactiveMove(condition) => {
entry.set("move", &condition.data.name);
}
Effect::Ability(ability) | Effect::AbilityCondition(ability) => {
entry.set("ability", &ability.data.name);
}
Effect::Item(item) | Effect::ItemCondition(item) => {
entry.set("item", &item.data.name);
}
Effect::Condition(condition) => {
entry.set(
condition.non_empty_condition_type_name(),
&condition.data.name,
);
}
Effect::Clause(clause) => {
entry.set("clause", &clause.data.name);
}
Effect::Species(species) => {
entry.set("species", &species.data.name);
}
Effect::NonExistent(_) => (),
},
}
Ok(())
}
fn effect_activation_internal(
context: &mut Context,
header: String,
activation_context: EffectActivationContext,
) -> Result<UncommittedBattleLogEntry> {
let mut event = battle_log_entry!(header);
if let Some(side) = activation_context.side {
event.set("side", side);
}
if let Some(slot) = activation_context.slot {
event.set("slot", slot);
}
if let Some(player) = activation_context.player {
event.set("player", &context.player_context(player)?.player().id);
}
if let Some(target) = activation_context.target {
event.set("mon", Mon::position_details(&context.mon_context(target)?)?);
}
if let Some(effect) = &activation_context.effect {
add_effect_to_log_entry(
context,
&mut event,
effect,
activation_context
.effect_flag_name
.as_ref()
.map(|s| s.as_str()),
)?;
}
for additional in activation_context.additional {
match additional.split_once(':') {
Some((first, second)) => event.set(first, second),
None => event.add_flag(additional),
}
}
let mut ignore_source = false;
if let Some(source_effect) = activation_context.source_effect.as_ref() {
let ignore_source_effect = (activation_context.ignore_active_move_source_effect
&& source_effect.is_active_move())
|| (activation_context.ignore_source_effect_equal_to_effect
&& activation_context
.effect
.as_ref()
.is_some_and(|effect| effect == source_effect));
ignore_source = ignore_source_effect;
if !ignore_source_effect {
add_effect_to_log_entry(context, &mut event, &source_effect, Some("from"))?
}
}
if activation_context.target != activation_context.source && !ignore_source {
if let Some(source) = activation_context.source {
event.set("of", Mon::position_details(&context.mon_context(source)?)?);
}
}
Ok(event)
}
fn full_mon_details(context: &mut MonContext, header: &str) -> Result<()> {
let private_event = battle_log_entry!(header, Mon::private_active_details(context)?);
let public_event = battle_log_entry!(header, Mon::public_active_details(context)?);
let side = context.mon().side;
context
.battle_mut()
.log_private_public(side, private_event, public_event);
Ok(())
}
pub fn switch(context: &mut MonContext, is_drag: bool) -> Result<()> {
let title = if is_drag {
"drag"
} else if context.player().player_type.wild() {
"appear"
} else {
"switch"
};
full_mon_details(context, title)
}
pub fn switch_out(
context: &mut MonContext,
copy_volatile_type: Option<CopyVolatileType>,
) -> Result<()> {
let mut event = battle_log_entry!("switchout", ("mon", Mon::position_details(context)?));
if let Some(copy_volatile_type) = copy_volatile_type {
let flag = match copy_volatile_type {
CopyVolatileType::AllCopyable => "copyvolatile",
CopyVolatileType::SubstituteOnly => "copysubstitute",
};
event.add_flag(flag);
};
context.battle_mut().log(event);
Ok(())
}
pub fn species_change(context: &mut MonContext) -> Result<()> {
full_mon_details(context, "specieschange")
}
pub fn replace(context: &mut MonContext) -> Result<()> {
full_mon_details(context, "replace")
}
fn forme_change_internal(
context: &mut ApplyingEffectContext,
header: String,
with_effect: bool,
) -> Result<()> {
let species = context
.battle()
.dex
.species
.get_by_id(&context.target().volatile_state.species)?
.data
.name
.clone();
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: with_effect.then(|| context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("species:{species}")]),
..Default::default()
};
effect_activation(context.as_battle_context_mut(), header, activation)
}
pub fn forme_change(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "formechange".to_owned(), true)
}
pub fn mega_evolution(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "mega".to_owned(), true)
}
pub fn revert_mega_evolution(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "revertmega".to_owned(), true)
}
pub fn primal_reversion(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "primal".to_owned(), true)
}
pub fn revert_primal_reversion(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "revertprimal".to_owned(), true)
}
pub fn ultra_burst(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "ultra".to_owned(), true)
}
pub fn revert_ultra_burst(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "revertultra".to_owned(), true)
}
pub fn dynamax(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"dynamax".to_owned(),
activation,
)
}
pub fn revert_dynamax(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"revertdynamax".to_owned(),
activation,
)
}
pub fn gigantamax(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "gigantamax".to_owned(), false)
}
pub fn revert_gigantamax(context: &mut ApplyingEffectContext) -> Result<()> {
forme_change_internal(context, "revertgigantamax".to_owned(), false)
}
pub fn terastallize(context: &mut ApplyingEffectContext, typ: Type) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source: context.source_handle(),
additional: Vec::from_iter([format!("type:{typ}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"tera".to_owned(),
activation,
)
}
pub fn revert_terastallization(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"reverttera".to_owned(),
activation,
)
}
pub fn cant(
context: &mut MonContext,
effect: EffectHandle,
source: Option<MonHandle>,
) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.mon_handle()),
source_effect: Some(effect),
source,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"cant".to_owned(),
activation,
)
}
pub fn fail(
context: &mut MonContext,
what: Option<EffectHandle>,
from: Option<EffectHandle>,
) -> Result<()> {
let activation = EffectActivationContext {
effect_flag_name: Some("what".to_owned()),
effect: what,
target: Some(context.mon_handle()),
ignore_active_move_source_effect: true,
source_effect: from,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"fail".to_owned(),
activation,
)
}
pub fn fail_unboost(
context: &mut MonContext,
boosts: &[Boost],
from: Option<EffectHandle>,
) -> Result<()> {
let mut activation = EffectActivationContext {
effect_flag_name: Some("what".to_owned()),
effect: Some(EffectHandle::NonExistent(NonExistentEffect::new(
Id::from_known("unboost"),
))),
target: Some(context.mon_handle()),
ignore_active_move_source_effect: true,
source_effect: from,
..Default::default()
};
if !boosts.is_empty() {
let boosts = boosts
.iter()
.map(|boost| boost.to_string().to_lowercase())
.join(",");
activation.additional.push(format!("boosts:{boosts}"));
}
effect_activation(
context.as_battle_context_mut(),
"fail".to_owned(),
activation,
)
}
pub fn fail_heal(context: &mut MonContext) -> Result<()> {
let activation = EffectActivationContext {
effect_flag_name: Some("what".to_owned()),
effect: Some(EffectHandle::NonExistent(NonExistentEffect::new(
Id::from_known("heal"),
))),
target: Some(context.mon_handle()),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"fail".to_owned(),
activation,
)
}
pub fn immune(context: &mut MonContext, effect: Option<EffectHandle>) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.mon_handle()),
ignore_active_move_source_effect: true,
source_effect: effect,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"immune".to_owned(),
activation,
)
}
fn move_event_on_target(context: &mut MonContext, event: &str) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.mon_handle()),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
event.to_owned(),
activation,
)
}
pub fn miss(context: &mut MonContext) -> Result<()> {
move_event_on_target(context, "miss")
}
pub fn super_effective(context: &mut MonContext) -> Result<()> {
move_event_on_target(context, "supereffective")
}
pub fn resisted(context: &mut MonContext) -> Result<()> {
move_event_on_target(context, "resisted")
}
pub fn critical_hit(context: &mut MonContext) -> Result<()> {
move_event_on_target(context, "crit")
}
pub fn ohko(context: &mut MonContext) -> Result<()> {
move_event_on_target(context, "ohko")
}
pub fn faint(context: &mut MonContext) -> Result<()> {
move_event_on_target(context, "faint")
}
pub fn waiting(context: &mut MonContext, target: MonHandle) -> Result<()> {
let event = battle_log_entry!(
"waiting",
("mon", Mon::position_details(context)?),
(
"on",
Mon::position_details(&context.as_battle_context_mut().mon_context(target)?)?
)
);
context.battle_mut().log(event);
Ok(())
}
pub fn damage(
context: &mut MonContext,
effect: Option<EffectHandle>,
source: Option<MonHandle>,
) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.mon_handle()),
ignore_active_move_source_effect: true,
source_effect: effect,
source,
..Default::default()
};
let mut private_event = effect_activation_internal(
context.as_battle_context_mut(),
"damage".to_owned(),
activation,
)?;
let mut public_event = private_event.clone();
private_event.set("health", Mon::secret_health_string(context));
public_event.set("health", Mon::public_health_string(context));
let side = context.mon().side;
context
.battle_mut()
.log_private_public(side, private_event, public_event);
Ok(())
}
pub fn heal(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
..Default::default()
};
let mut private_event = effect_activation_internal(
context.as_battle_context_mut(),
"heal".to_owned(),
activation,
)?;
let mut public_event = private_event.clone();
let context = &mut context.target_context()?;
private_event.set("health", Mon::secret_health_string(&context));
public_event.set("health", Mon::public_health_string(&context));
let side = context.mon().side;
context
.battle_mut()
.log_private_public(side, private_event, public_event);
Ok(())
}
pub fn set_hp(
context: &mut MonContext,
effect: Option<EffectHandle>,
source: Option<MonHandle>,
) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.mon_handle()),
ignore_active_move_source_effect: true,
source_effect: effect,
source,
..Default::default()
};
let mut private_event = effect_activation_internal(
context.as_battle_context_mut(),
"sethp".to_owned(),
activation,
)?;
let mut public_event = private_event.clone();
private_event.set("health", Mon::secret_health_string(context));
public_event.set("health", Mon::public_health_string(context));
let side = context.mon().side;
context
.battle_mut()
.log_private_public(side, private_event, public_event);
Ok(())
}
pub fn revive(
context: &mut MonContext,
effect: Option<EffectHandle>,
source: Option<MonHandle>,
) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.mon_handle()),
ignore_active_move_source_effect: true,
source_effect: effect,
source,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"revive".to_owned(),
activation,
)
}
pub fn cure_status(
context: &mut ApplyingEffectContext,
status: &Id,
log_effect: bool,
) -> Result<()> {
let status = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &status)?
.name()
.to_owned();
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: if log_effect {
Some(context.effect_handle().clone())
} else {
None
},
source: context.source_handle(),
additional: Vec::from_iter([format!("status:{status}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"curestatus".to_owned(),
activation,
)
}
pub fn add_volatile(context: &mut ApplyingEffectContext, volatile: &Id) -> Result<()> {
if !context.battle().engine_options.log_volatile_statuses {
return Ok(());
}
let volatile = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &volatile)?
.name()
.to_owned();
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("volatile:{volatile}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"addvolatile".to_owned(),
activation,
)
}
pub fn remove_volatile(context: &mut ApplyingEffectContext, volatile: &Id) -> Result<()> {
if !context.battle().engine_options.log_volatile_statuses {
return Ok(());
}
let volatile = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &volatile)?
.name()
.to_owned();
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("volatile:{volatile}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"removevolatile".to_owned(),
activation,
)
}
pub fn add_side_condition(context: &mut SideEffectContext, condition: &Id) -> Result<()> {
if !context.battle().engine_options.log_side_conditions {
return Ok(());
}
let condition = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &condition)?
.name()
.to_owned();
let activation = EffectActivationContext {
side: Some(context.side().index),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("condition:{condition}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"addsidecondition".to_owned(),
activation,
)
}
pub fn remove_side_condition(context: &mut SideEffectContext, condition: &Id) -> Result<()> {
if !context.battle().engine_options.log_side_conditions {
return Ok(());
}
let condition = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &condition)?
.name()
.to_owned();
let activation = EffectActivationContext {
side: Some(context.side().index),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("condition:{condition}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"removesidecondition".to_owned(),
activation,
)
}
pub fn swap_side_conditions(context: &mut SideEffectContext, source_side: usize) -> Result<()> {
let activation = EffectActivationContext {
side: Some(context.side().index),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("with:{source_side}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"swapsideconditions".to_owned(),
activation,
)
}
pub fn swap_side_condition(
context: &mut SideEffectContext,
condition: &Id,
source_side: usize,
) -> Result<()> {
let condition = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &condition)?
.name()
.to_owned();
let activation = EffectActivationContext {
side: Some(context.side().index),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([
format!("condition:{condition}"),
format!("source:{source_side}"),
]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"swapsidecondition".to_owned(),
activation,
)
}
pub fn add_slot_condition(
context: &mut SideEffectContext,
slot: usize,
condition: &Id,
) -> Result<()> {
if !context.battle().engine_options.log_slot_conditions {
return Ok(());
}
let condition = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &condition)?
.name()
.to_owned();
let activation = EffectActivationContext {
side: Some(context.side().index),
slot: Some(slot),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("condition:{condition}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"addslotcondition".to_owned(),
activation,
)
}
pub fn remove_slot_condition(
context: &mut SideEffectContext,
slot: usize,
condition: &Id,
) -> Result<()> {
if !context.battle().engine_options.log_slot_conditions {
return Ok(());
}
let condition = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &condition)?
.name()
.to_owned();
let activation = EffectActivationContext {
side: Some(context.side().index),
slot: Some(slot),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("condition:{condition}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"removeslotcondition".to_owned(),
activation,
)
}
pub fn add_pseudo_weather(context: &mut FieldEffectContext, condition: &Id) -> Result<()> {
if !context.battle().engine_options.log_pseudo_weathers {
return Ok(());
}
let condition = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &condition)?
.name()
.to_owned();
let activation = EffectActivationContext {
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("condition:{condition}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"addpseudoweather".to_owned(),
activation,
)
}
pub fn remove_pseudo_weather(context: &mut FieldEffectContext, condition: &Id) -> Result<()> {
if !context.battle().engine_options.log_pseudo_weathers {
return Ok(());
}
let condition = CoreBattle::get_effect_by_id(context.as_battle_context_mut(), &condition)?
.name()
.to_owned();
let activation = EffectActivationContext {
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("condition:{condition}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"removepseudoweather".to_owned(),
activation,
)
}
pub fn type_change(context: &mut ApplyingEffectContext, types: &[Type]) -> Result<()> {
let types = types.iter().map(|typ| typ.to_string()).join("/");
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("types:{types}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"typechange".to_owned(),
activation,
)
}
pub fn reset_type_change(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"resettypechange".to_owned(),
activation,
)
}
pub fn added_type(
context: &mut MonContext,
typ: Type,
effect: Option<EffectHandle>,
source: Option<MonHandle>,
) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.mon_handle()),
ignore_active_move_source_effect: true,
source_effect: effect,
source,
additional: Vec::from_iter([format!("type:{typ}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"addedtype".to_owned(),
activation,
)
}
pub fn transform(context: &mut ApplyingEffectContext, target: MonHandle) -> Result<()> {
let species = context
.battle()
.dex
.species
.get_by_id(&context.target().volatile_state.species)?
.data
.name
.clone();
let into = Mon::position_details(&context.as_battle_context_mut().mon_context(target)?)?;
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("into:{into}"), format!("species:{species}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"transform".to_owned(),
activation,
)
}
pub fn ability(context: &mut ApplyingEffectContext) -> Result<()> {
let ability = context
.battle()
.dex
.abilities
.get_by_id(&context.target().volatile_state.ability_slot.ability.id)?
.data
.name
.clone();
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("ability:{ability}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"ability".to_owned(),
activation,
)
}
pub fn ability_end(context: &mut ApplyingEffectContext) -> Result<()> {
let ability = context
.battle()
.dex
.abilities
.get_by_id(&context.target().volatile_state.ability_slot.ability.id)?
.data
.name
.clone();
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("ability:{ability}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"abilityend".to_owned(),
activation,
)
}
pub fn item(context: &mut ApplyingEffectContext) -> Result<()> {
let item = match context.target().item.clone() {
Some(item) => item,
None => return Err(general_error("target has no item")),
};
let item = context
.battle()
.dex
.items
.get_by_id(&item)?
.data
.name
.clone();
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("item:{item}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"item".to_owned(),
activation,
)
}
pub fn item_end(
context: &mut ApplyingEffectContext,
no_source: bool,
silent: bool,
eat: bool,
) -> Result<()> {
let item = match context.target().item.clone() {
Some(item) => item,
None => return Err(general_error("target has no item")),
};
let item = context
.battle()
.dex
.items
.get_by_id(&item)?
.data
.name
.clone();
let mut additional = Vec::from_iter([format!("item:{item}")]);
if silent {
additional.push("silent".to_owned());
}
if eat {
additional.push("eat".to_owned());
}
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: if no_source {
None
} else {
Some(context.effect_handle().clone())
},
source: if no_source {
None
} else {
context.source_handle()
},
additional,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"itemend".to_owned(),
activation,
)
}
pub fn use_move(
context: &mut ActiveMoveContext,
target: Option<MonHandle>,
animate_only: bool,
) -> Result<()> {
let title = if animate_only { "animatemove" } else { "move" };
let mut event = battle_log_entry!(
title,
("mon", Mon::position_details(context.as_mon_context())?),
("name", &context.active_move().data.name)
);
if let Some(target) = target {
event.extend(&(
"target",
Mon::position_details(&context.as_battle_context_mut().mon_context(target)?)?,
));
}
if !animate_only {
if let Some(from) = context.source_effect_handle() {
add_effect_to_log_entry(
context.as_battle_context_mut(),
&mut event,
&from,
Some("from"),
)?
}
}
context.active_move_mut().last_move_log = Some(context.battle_mut().log(event));
Ok(())
}
pub fn add_attribute_to_last_move(context: &mut ActiveMoveContext, attribute: &str) {
if let Some(index) = context.active_move().last_move_log {
context.battle_mut().add_attribute_to_log(index, attribute);
}
}
pub fn add_attribute_value_to_last_move(
context: &mut ActiveMoveContext,
attribute: &str,
value: String,
) {
if let Some(index) = context.active_move().last_move_log {
context
.battle_mut()
.add_attribute_value_to_log(index, attribute, value);
}
}
fn remove_attribute_from_last_move(context: &mut ActiveMoveContext, attribute: &str) {
if let Some(index) = context.active_move().last_move_log {
context
.battle_mut()
.remove_attribute_from_log(index, attribute);
}
}
pub fn last_move_had_no_target(context: &mut ActiveMoveContext) {
add_attribute_to_last_move(context, "notarget");
}
pub fn do_not_animate_last_move(context: &mut ActiveMoveContext) {
add_attribute_to_last_move(context, "noanim");
remove_attribute_from_last_move(context, "target");
remove_attribute_from_last_move(context, "spread");
}
pub fn last_move_spread_targets<I>(context: &mut ActiveMoveContext, targets: I) -> Result<()>
where
I: IntoIterator<Item = MonHandle>,
{
let mut target_positions = Vec::new();
for target in targets {
target_positions.push(format!(
"{}",
Mon::position_details_or_previous(
&context.as_battle_context_mut().mon_context(target)?
)?
));
}
add_attribute_value_to_last_move(context, "spread", target_positions.into_iter().join(";"));
remove_attribute_from_last_move(context, "target");
Ok(())
}
pub fn last_move_target(context: &mut ActiveMoveContext, target: MonHandle) -> Result<()> {
let details = Mon::position_details(&context.as_battle_context_mut().mon_context(target)?)?;
add_attribute_value_to_last_move(context, "target", details.to_string());
Ok(())
}
fn add_attribute_to_last_item(context: &mut Context, attribute: &str) {
if let Some(index) = context.battle().last_item_log() {
context.battle_mut().add_attribute_to_log(index, attribute);
}
}
fn remove_attribute_from_last_item(context: &mut Context, attribute: &str) {
if let Some(index) = context.battle().last_item_log() {
context
.battle_mut()
.remove_attribute_from_log(index, attribute);
}
}
pub fn last_item_had_no_target(context: &mut Context) {
add_attribute_to_last_item(context, "notarget");
}
pub fn do_not_animate_last_item(context: &mut Context) {
add_attribute_to_last_item(context, "noanim");
remove_attribute_from_last_item(context, "target");
}
pub fn hit_count(context: &mut Context, hits: u8) -> Result<()> {
let event = battle_log_entry!("hitcount", ("hits", hits));
context.battle_mut().log(event);
Ok(())
}
pub fn boost(
context: &mut ApplyingEffectContext,
boost: Boost,
delta: i8,
original_delta: i8,
) -> Result<()> {
let (delta, message) = if original_delta >= 0 {
(delta as u8, "boost")
} else {
(-delta as u8, "unboost")
};
let mut additional = Vec::from_iter([format!("stat:{boost}"), format!("by:{delta}")]);
if original_delta >= 12 {
additional.push("max".to_owned());
} else if original_delta <= -12 {
additional.push("min".to_owned());
}
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
ignore_active_move_source_effect: true,
additional,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
message.to_owned(),
activation,
)
}
pub fn swap_boosts(context: &mut ApplyingEffectContext, boosts: &[Boost]) -> Result<()> {
let mut additional = Vec::default();
if !boosts.is_empty() {
additional.push(format!("stats:{}", boosts.iter().join(",")));
}
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"swapboosts".to_owned(),
activation,
)
}
pub fn invert_boosts(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"invertboosts".to_owned(),
activation,
)
}
pub fn copy_boosts(context: &mut ApplyingEffectContext, target: MonHandle) -> Result<()> {
let source = Mon::position_details(&context.as_battle_context_mut().mon_context(target)?)?;
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("source:{source}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"copyboosts".to_owned(),
activation,
)
}
pub fn debug_event_failure(
context: &mut Context,
event: fxlang::BattleEvent,
effect_name: &str,
error: &str,
) {
let log_event = battle_log_entry!(
"debug",
("event", event),
("effect", effect_name),
("error", error)
);
context.battle_mut().log(log_event);
}
pub fn debug_full_event_failure(context: &mut Context, event: fxlang::BattleEvent, error: &str) {
let log_event = battle_log_entry!("debug", ("event", event), ("error", error));
context.battle_mut().log(log_event);
}
pub fn experience(context: &mut MonContext, exp: u32) -> Result<()> {
let event = battle_log_entry!(
"exp",
("mon", Mon::position_details(context)?),
("exp", exp)
);
context.battle_mut().log(event);
Ok(())
}
pub fn level_up(context: &mut MonContext) -> Result<()> {
let event = battle_log_entry!(
"levelup",
("mon", Mon::position_details(context)?),
("level", context.mon().level),
("hp", context.mon().base_max_hp),
("atk", context.mon().volatile_state.stats.get(Stat::Atk)),
("def", context.mon().volatile_state.stats.get(Stat::Def)),
("spa", context.mon().volatile_state.stats.get(Stat::SpAtk)),
("spd", context.mon().volatile_state.stats.get(Stat::SpDef)),
("spe", context.mon().volatile_state.stats.get(Stat::Spe)),
);
context.battle_mut().log(event);
Ok(())
}
pub fn cannot_escape(context: &mut PlayerContext) -> Result<()> {
let event = battle_log_entry!("cannotescape", ("player", &context.player().id));
context.battle_mut().log(event);
Ok(())
}
pub fn escaped(context: &mut PlayerContext) -> Result<()> {
let event = battle_log_entry!("escaped", ("player", &context.player().id));
context.battle_mut().log(event);
Ok(())
}
pub fn forfeited(context: &mut PlayerContext) -> Result<()> {
let event = battle_log_entry!("forfeited", ("player", &context.player().id));
context.battle_mut().log(event);
Ok(())
}
pub fn use_item(context: &mut PlayerContext, item: &Id, target: Option<MonHandle>) -> Result<()> {
let item = context.battle().dex.items.get_by_id(item)?;
let mut event = battle_log_entry!(
"useitem",
("player", &context.player().id),
("name", item.data.name.clone())
);
if let Some(target) = target {
event.extend(&(
"target",
Mon::position_details(&context.as_battle_context_mut().mon_context(target)?)?,
));
}
let index = context.battle_mut().log(event);
context.battle_mut().set_last_item_log(index);
Ok(())
}
pub fn fail_use_item(
context: &mut PlayerContext,
item: &Id,
from: Option<EffectHandle>,
) -> Result<()> {
let activation = EffectActivationContext {
effect_flag_name: Some("what".to_owned()),
effect: Some(EffectHandle::Item(item.clone())),
player: Some(context.player().index),
source_effect: from,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"fail".to_owned(),
activation,
)
}
pub fn deduct_pp(context: &mut ApplyingEffectContext, move_id: &Id, delta: u8) -> Result<()> {
let activation = EffectActivationContext {
effect: Some(EffectHandle::InactiveMove(move_id.clone())),
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("by:{delta}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"deductpp".to_owned(),
activation,
)
}
pub fn restore_pp(context: &mut ApplyingEffectContext, move_id: &Id, delta: u8) -> Result<()> {
let activation = EffectActivationContext {
effect: Some(EffectHandle::InactiveMove(move_id.clone())),
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("by:{delta}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"restorepp".to_owned(),
activation,
)
}
pub fn set_pp(context: &mut ApplyingEffectContext, move_id: &Id, pp: u8) -> Result<()> {
let activation = EffectActivationContext {
effect: Some(EffectHandle::InactiveMove(move_id.clone())),
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("to:{pp}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"setpp".to_owned(),
activation,
)
}
pub fn clear_boosts(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"clearboosts".to_owned(),
activation,
)
}
pub fn clear_negative_boosts(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"clearnegativeboosts".to_owned(),
activation,
)
}
pub fn clear_positive_boosts(context: &mut ApplyingEffectContext) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
ignore_active_move_source_effect: true,
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"clearpositiveboosts".to_owned(),
activation,
)
}
pub fn uncatchable(context: &mut PlayerContext, target: MonHandle, wild: bool) -> Result<()> {
let mut additional = Vec::new();
if !wild {
additional.push("thief".to_owned());
}
let activation = EffectActivationContext {
player: Some(context.player().index),
target: Some(target),
additional,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"uncatchable".to_owned(),
activation,
)
}
pub fn catch_failed(
context: &mut PlayerContext,
target: MonHandle,
item: &Id,
shakes: u8,
critical: bool,
) -> Result<()> {
let mut additional = Vec::from_iter([format!("shakes:{shakes}")]);
if critical {
additional.push("critical".to_owned());
}
let activation = EffectActivationContext {
player: Some(context.player().index),
effect: Some(EffectHandle::Item(item.clone())),
target: Some(target),
additional,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"catchfailed".to_owned(),
activation,
)
}
pub fn catch(
context: &mut PlayerContext,
target: MonHandle,
item: &Id,
shakes: u8,
critical: bool,
) -> Result<()> {
let mut additional = Vec::from_iter([format!("shakes:{shakes}")]);
if critical {
additional.push("critical".to_owned());
}
let activation = EffectActivationContext {
player: Some(context.player().index),
effect: Some(EffectHandle::Item(item.clone())),
target: Some(target),
additional,
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"catch".to_owned(),
activation,
)
}
pub fn swap(context: &mut ApplyingEffectContext, position: usize) -> Result<()> {
let activation = EffectActivationContext {
target: Some(context.target_handle()),
source_effect: Some(context.effect_handle().clone()),
source: context.source_handle(),
additional: Vec::from_iter([format!("position:{position}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"swap".to_owned(),
activation,
)
}
pub fn swap_player(context: &mut PlayerContext, position: usize) -> Result<()> {
let activation = EffectActivationContext {
player: Some(context.player().index),
additional: Vec::from_iter([format!("position:{position}")]),
..Default::default()
};
effect_activation(
context.as_battle_context_mut(),
"swapplayer".to_owned(),
activation,
)
}