use std::collections::HashMap;
use dotzuki_engine::battle::stack::{Effect, EffectId, EffectProvider, EffectType, Event, EventHook};
use crate::bindings::RuleBindings;
use crate::interp::interpret;
use crate::model::{EffectKind, LoadError, Op, Ruleset};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolverKind {
Move,
Status,
Ability,
Item,
Weather,
}
impl ResolverKind {
pub fn from_kind(kind: EffectKind) -> Self {
match kind {
EffectKind::Move => ResolverKind::Move,
EffectKind::Status => ResolverKind::Status,
EffectKind::Ability => ResolverKind::Ability,
EffectKind::Item => ResolverKind::Item,
EffectKind::Weather => ResolverKind::Weather,
}
}
}
#[derive(Debug, Clone)]
pub struct CompiledHook {
pub id: EffectId,
pub event: Event,
pub order: u32,
pub priority: i32,
pub chance: Option<(u32, u32)>,
pub ops: Vec<Op>,
pub move_type_index: Option<usize>,
pub effect_type: EffectType,
pub source_id: String,
pub resolver: ResolverKind,
}
#[derive(Debug, Clone)]
pub struct CompiledRuleset {
pub hooks: HashMap<EffectId, CompiledHook>,
pub types: Vec<String>,
pub stats: Vec<String>,
pub resources: Vec<String>,
pub move_costs: HashMap<String, Vec<(usize, u16)>>,
pub statuses: HashMap<String, usize>,
pub type_chart: HashMap<(usize, usize), (u32, u32)>,
}
impl CompiledRuleset {
pub fn status_index(&self, name: &str) -> Option<usize> {
self.statuses.get(name).copied()
}
pub fn chart_mult(&self, atk_type_index: usize, def_type_index: usize) -> (u32, u32) {
self.type_chart
.get(&(atk_type_index, def_type_index))
.copied()
.unwrap_or((1, 1))
}
}
impl CompiledRuleset {
pub fn compile<P, B>(
ruleset: &Ruleset,
id_base: u32,
bindings: &B,
status_index_of: impl Fn(&str) -> Option<usize>,
) -> Result<Self, LoadError>
where
P: EffectProvider,
B: RuleBindings<P>,
{
let _ = bindings; let mut hooks = HashMap::new();
let mut statuses: HashMap<String, usize> = HashMap::new();
let mut move_costs: HashMap<String, Vec<(usize, u16)>> = HashMap::new();
let mut next_id = id_base;
let mut type_chart: HashMap<(usize, usize), (u32, u32)> = HashMap::new();
for edge in &ruleset.type_chart {
let a = ruleset
.type_index(&edge.atk)
.ok_or_else(|| LoadError::UnknownType(edge.atk.clone()))?;
let d = ruleset
.type_index(&edge.def)
.ok_or_else(|| LoadError::UnknownType(edge.def.clone()))?;
type_chart.insert((a, d), (edge.mult.num, edge.mult.den));
}
for rec in &ruleset.effects {
let effect_type = crate::model::parse_kind(rec.kind);
let resolver = ResolverKind::from_kind(rec.kind);
let move_type_index = match &rec.mtype {
Some(name) => Some(
ruleset
.type_index(name)
.ok_or_else(|| LoadError::UnknownType(name.clone()))?,
),
None => None,
};
if !rec.cost.is_empty() {
let mut costs = Vec::with_capacity(rec.cost.len());
for c in &rec.cost {
let idx = ruleset
.resource_index(&c.resource)
.ok_or_else(|| LoadError::UnknownResource(c.resource.clone()))?;
costs.push((idx, c.amount));
}
move_costs.insert(rec.id.clone(), costs);
}
for hook in &rec.hooks {
let event = crate::model::parse_event(&hook.on)?;
let chance = match hook.chance {
Some(r) => {
if r.den == 0 {
return Err(LoadError::BadChance(r.num, r.den));
}
Some((r.num, r.den))
}
None => None,
};
for op in &hook.ops {
validate_op::<P, B>(op, ruleset, &status_index_of)?;
if let Op::InflictStatus { status, .. } = op {
let idx = status_index_of(status)
.ok_or_else(|| LoadError::UnknownStatus(status.clone()))?;
statuses.insert(status.clone(), idx);
}
for pred in op_predicates(op) {
let status_name = match pred {
crate::model::Predicate::TargetHasStatus(s)
| crate::model::Predicate::SourceHasStatus(s) => Some(s),
_ => None,
};
if let Some(s) = status_name {
let idx = status_index_of(s)
.ok_or_else(|| LoadError::UnknownStatus(s.clone()))?;
statuses.insert(s.clone(), idx);
}
}
}
let id = EffectId(next_id);
next_id += 1;
hooks.insert(
id,
CompiledHook {
id,
event,
order: hook.order,
priority: hook.priority,
chance,
ops: hook.ops.clone(),
move_type_index,
effect_type,
source_id: rec.id.clone(),
resolver,
},
);
}
}
Ok(CompiledRuleset {
hooks,
types: ruleset.types.clone(),
stats: ruleset.stats.clone(),
resources: ruleset.resources.clone(),
move_costs,
statuses,
type_chart,
})
}
pub fn hook(&self, id: EffectId) -> Option<&CompiledHook> {
self.hooks.get(&id)
}
pub fn move_cost(&self, source_id: &str) -> &[(usize, u16)] {
self.move_costs
.get(source_id)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn build_effects<P>(&self) -> Vec<&'static Effect<P>>
where
P: RulesProvider,
{
let mut out = Vec::with_capacity(self.hooks.len());
for h in self.hooks.values() {
let hook: EventHook<P> = EventHook {
event: h.event,
call: interpret::<P>,
order: h.order,
priority: h.priority,
sub_order: None,
};
let leaked_hooks: &'static [EventHook<P>] = Box::leak(vec![hook].into_boxed_slice());
let eff: &'static Effect<P> = Box::leak(Box::new(Effect {
id: h.id,
kind: h.effect_type,
hooks: leaked_hooks,
}));
out.push(eff);
}
out
}
}
fn op_predicates(op: &Op) -> Vec<&crate::model::Predicate> {
match op {
Op::DamageFraction { unless, .. } | Op::HealFraction { unless, .. } => {
unless.iter().collect()
}
Op::ScaleRelay { when, .. } | Op::SetHp { when, .. } => when.iter().collect(),
Op::VetoIf { cond, .. } => vec![cond],
Op::RepeatHits {
final_hit: crate::model::FinalHitRider::OnFinal { ops, .. },
..
} => ops.iter().flat_map(op_predicates).collect(),
_ => Vec::new(),
}
}
fn validate_op<P, B>(
op: &Op,
ruleset: &Ruleset,
status_index_of: &impl Fn(&str) -> Option<usize>,
) -> Result<(), LoadError>
where
P: EffectProvider,
B: RuleBindings<P>,
{
use crate::model::Predicate;
let check_type = |name: &str| -> Result<(), LoadError> {
ruleset
.type_index(name)
.map(|_| ())
.ok_or_else(|| LoadError::UnknownType(name.to_string()))
};
let check_stat = |name: &str| -> Result<(), LoadError> {
ruleset
.stat_index(name)
.map(|_| ())
.ok_or_else(|| LoadError::UnknownStat(name.to_string()))
};
let check_resource = |name: &str| -> Result<(), LoadError> {
ruleset
.resource_index(name)
.map(|_| ())
.ok_or_else(|| LoadError::UnknownResource(name.to_string()))
};
let check_status = |name: &str| -> Result<(), LoadError> {
status_index_of(name)
.map(|_| ())
.ok_or_else(|| LoadError::UnknownStatus(name.to_string()))
};
let check_pred = |p: &Predicate| -> Result<(), LoadError> {
match p {
Predicate::HasType(t) => check_type(t),
Predicate::StatIs(s) => check_stat(s),
Predicate::RelayIntLt(_) => Ok(()),
Predicate::HasVolatile(_) | Predicate::MoveTypeIsDefenderType => Ok(()),
Predicate::TargetHasStatus(s) => check_status(s),
Predicate::LevelGE => Ok(()),
Predicate::SelfHpBelow { .. } => Ok(()),
Predicate::SourceHasStatus(s) => check_status(s),
Predicate::Not(inner) => match inner.as_ref() {
Predicate::HasType(t) => check_type(t),
Predicate::StatIs(s) => check_stat(s),
Predicate::TargetHasStatus(s) | Predicate::SourceHasStatus(s) => check_status(s),
_ => Ok(()),
},
Predicate::TargetHasAnyStatus => Ok(()),
}
};
match op {
Op::DealMoveDamage | Op::ApplyTypeChart | Op::SetRelay(_) | Op::AddRelay(_) => Ok(()),
Op::ClampRelay { .. } => Ok(()),
Op::DamageFraction { unless, .. } | Op::HealFraction { unless, .. } => {
if let Some(p) = unless {
check_pred(p)?;
}
Ok(())
}
Op::InflictStatus { status, .. } => status_index_of(status)
.map(|_| ())
.ok_or_else(|| LoadError::UnknownStatus(status.clone())),
Op::InflictVolatile { .. } => Ok(()),
Op::Boost { stat, .. } => check_stat(stat),
Op::ScaleRelay { when, .. } => {
for p in when {
check_pred(p)?;
}
Ok(())
}
Op::VetoIf { cond, .. } => check_pred(cond),
Op::PayResource { resource, .. } => check_resource(resource),
Op::SetHp { when, .. } => {
for p in when {
check_pred(p)?;
}
Ok(())
}
Op::SetDamage { .. } | Op::DamageCurrentHpFraction { .. } | Op::RemoveStatus { .. } => {
Ok(())
}
Op::RepeatHits { final_hit, .. } => {
if let crate::model::FinalHitRider::OnFinal { ops, .. } = final_hit {
for op in ops {
validate_op::<P, B>(op, ruleset, status_index_of)?;
}
}
Ok(())
}
}
}
pub trait RulesProvider: EffectProvider {
type Bindings: RuleBindings<Self>;
fn compiled(&self) -> &CompiledRuleset;
fn bindings(&self) -> &Self::Bindings;
fn rules_host() -> Option<&'static RulesHost<Self>>
where
Self: Sized;
}
pub struct RulesHost<P: RulesProvider> {
pub compiled: CompiledRuleset,
pub bindings: P::Bindings,
}
impl<P: RulesProvider> RulesHost<P> {
pub fn new(compiled: CompiledRuleset, bindings: P::Bindings) -> Self {
Self { compiled, bindings }
}
pub fn hook(&self, id: EffectId) -> Option<&CompiledHook> {
self.compiled.hook(id)
}
}