use std::cmp::Ordering;
use crate::battle::rng::BattleRng;
use crate::battle::BattlerRef;
use super::ctx::{BattleCtx, EffectProvider};
use super::event::{Effect, EffectId, Event, HandlerFn, HandlerResult, RelayVar};
pub struct CollectedHandler<P: EffectProvider + ?Sized> {
pub order: u32,
pub priority: i32,
pub speed: u32,
pub sub_order: u8,
pub effect_order: u64,
pub target: BattlerRef,
pub source: BattlerRef,
pub source_effect: EffectId,
pub call: HandlerFn<P>,
}
impl<P: EffectProvider + ?Sized> Clone for CollectedHandler<P> {
fn clone(&self) -> Self {
Self {
order: self.order,
priority: self.priority,
speed: self.speed,
sub_order: self.sub_order,
effect_order: self.effect_order,
target: self.target,
source: self.source,
source_effect: self.source_effect,
call: self.call,
}
}
}
pub fn compare<P: EffectProvider + ?Sized>(
a: &CollectedHandler<P>,
b: &CollectedHandler<P>,
) -> Ordering {
a.order
.cmp(&b.order)
.then(b.priority.cmp(&a.priority))
.then(b.speed.cmp(&a.speed))
.then(a.sub_order.cmp(&b.sub_order))
.then(a.effect_order.cmp(&b.effect_order))
}
pub fn collect_from_effect<P: EffectProvider + ?Sized>(
ctx: &BattleCtx<'_, P>,
eff: &'static Effect<P>,
ev: Event,
target: BattlerRef,
source: BattlerRef,
out: &mut Vec<CollectedHandler<P>>,
) {
push_matching(ctx, eff, ev, target, source, out);
}
fn push_matching<P: EffectProvider + ?Sized>(
ctx: &BattleCtx<'_, P>,
eff: &'static Effect<P>,
ev: Event,
target: BattlerRef,
source: BattlerRef,
out: &mut Vec<CollectedHandler<P>>,
) {
let speed = 0;
let effect_order = ctx
.effect(eff.id)
.map(|s| s.effect_order)
.unwrap_or(eff.id.0 as u64);
for hook in eff.hooks {
if hook.event != ev {
continue;
}
out.push(CollectedHandler {
order: hook.order,
priority: hook.priority,
speed,
sub_order: hook.sub_order.unwrap_or_else(|| eff.kind.sub_order()),
effect_order,
target,
source,
source_effect: eff.id,
call: hook.call,
});
}
}
pub fn collect_handlers<P: EffectProvider>(
ctx: &BattleCtx<'_, P>,
provider: &P,
src_eff: Option<&'static Effect<P>>,
ev: Event,
target: BattlerRef,
source: BattlerRef,
out: &mut Vec<CollectedHandler<P>>,
) {
if let Some(eff) = src_eff {
push_matching(ctx, eff, ev, target, source, out);
}
for e in ctx.effects.iter() {
if e.host != target && e.host != source {
continue;
}
if let Some(eff) = provider.effect_for_volatile(&e.kind) {
push_matching(ctx, eff, ev, target, source, out);
}
}
let battlers: &[BattlerRef] = if target == source {
std::slice::from_ref(&source)
} else {
&[target, source]
};
for &who in battlers {
let b = ctx.battler(who);
if let Some(eff) = provider.effect_for_ability(b) {
push_matching(ctx, eff, ev, who, source, out);
}
if let Some(eff) = provider.effect_for_item(b) {
push_matching(ctx, eff, ev, who, source, out);
}
}
for &eff in provider.side_effects(ctx, target.side) {
push_matching(ctx, eff, ev, target, source, out);
}
if source.side != target.side {
for &eff in provider.side_effects(ctx, source.side) {
push_matching(ctx, eff, ev, target, source, out);
}
}
for &eff in provider.field_effects(ctx) {
push_matching(ctx, eff, ev, target, source, out);
}
}
fn is_alive<P: EffectProvider + ?Sized>(ctx: &BattleCtx<'_, P>, who: BattlerRef) -> bool {
ctx.battler(who).hp > 0
}
pub fn speed_sort_tiebreak<P: EffectProvider + ?Sized>(
hs: &mut [CollectedHandler<P>],
rng: &mut dyn BattleRng,
) {
let mut i = 0;
while i < hs.len() {
let mut j = i + 1;
while j < hs.len() && compare(&hs[i], &hs[j]) == Ordering::Equal {
j += 1;
}
if j - i >= 2 {
for k in i..(j - 1) {
if rng.next_u8() >= 128 {
hs.swap(k, k + 1);
}
}
}
i = j;
}
}
pub fn run_event<P: EffectProvider + ?Sized>(
ctx: &mut BattleCtx<'_, P>,
mut hs: Vec<CollectedHandler<P>>,
mut relay: RelayVar,
fast_exit: bool,
) -> RelayVar {
hs.sort_by(compare);
speed_sort_tiebreak(&mut hs, ctx.rng);
for h in hs {
match (h.call)(ctx, relay, h.target, h.source, h.source_effect) {
HandlerResult::Unchanged => {}
HandlerResult::Set(v) => {
relay = v;
if fast_exit {
return relay;
}
}
HandlerResult::Fail => return RelayVar::Bool(false),
HandlerResult::FailSilent => return RelayVar::Unit,
}
}
relay
}
pub fn run_event_checked<P: EffectProvider + ?Sized>(
ctx: &mut BattleCtx<'_, P>,
mut hs: Vec<CollectedHandler<P>>,
mut relay: RelayVar,
fast_exit: bool,
) -> RelayVar {
hs.sort_by(compare);
speed_sort_tiebreak(&mut hs, ctx.rng);
for h in hs {
if !is_alive(ctx, h.target) {
continue;
}
match (h.call)(ctx, relay, h.target, h.source, h.source_effect) {
HandlerResult::Unchanged => {}
HandlerResult::Set(v) => {
relay = v;
if fast_exit {
return relay;
}
}
HandlerResult::Fail => return RelayVar::Bool(false),
HandlerResult::FailSilent => return RelayVar::Unit,
}
}
relay
}