firecore_battle/default_engine/default_scripting/
mod.rs

1use alloc::{boxed::Box, string::String, vec::Vec};
2use core::{fmt::Debug, hash::Hash};
3
4// use std::error::Error;
5
6use hashbrown::HashMap;
7use rand::Rng;
8use rhai::{
9    packages::{BasicArrayPackage, Package},
10    Engine, EvalAltResult, ParseError, Scope,
11};
12
13use pokedex::{
14    item::{Item, ItemId},
15    moves::{Move, MoveId},
16};
17
18use crate::{
19    engine::{BattlePokemon, ItemResult, MoveResult, Players},
20    pokemon::{Indexed, PokemonIdentifier},
21    prelude::BattleData,
22};
23
24use super::scripting::ScriptingEngine;
25
26type Scripts<ID> = HashMap<ID, String>;
27
28pub type MoveScripts = Scripts<MoveId>;
29pub type ItemScripts = Scripts<ItemId>;
30
31mod moves;
32pub use moves::*;
33
34pub struct RhaiScriptingEngine {
35    pub engine: Engine,
36    pub scope: Scope<'static>,
37    pub moves: MoveScripts,
38    pub items: ItemScripts,
39}
40
41impl RhaiScriptingEngine {
42    pub fn new<ID: Clone + 'static, R: Rng + Clone + 'static>() -> Self {
43        let mut engine = Engine::new();
44
45        engine
46            .register_global_module(BasicArrayPackage::new().as_shared_module())
47            .register_type_with_name::<ScriptRandom<R>>("Random")
48            .register_type_with_name::<ScriptDamage>("Damage")
49            .register_fn("damage", ScriptDamage::with_damage)
50            .register_set("damage", ScriptDamage::set_damage)
51            .register_get("damage", ScriptDamage::get_damage)
52            .register_get("effective", ScriptDamage::effective)
53            .register_type_with_name::<ScriptAilmentEffect>("AilmentEffect")
54            .register_type_with_name::<LiveScriptAilment>("LiveAilment")
55            .register_fn("ailment", ScriptAilmentEffect::ailment)
56            .register_fn("init", ScriptAilmentEffect::init::<R>)
57            .register_fn("clear_ailment", LiveScriptAilment::clear_ailment)
58            .register_type_with_name::<ScriptPokemon<ID>>("Pokemon")
59            .register_iterator::<Vec<ScriptPokemon<ID>>>()
60            .register_fn("throw_move", ScriptPokemon::<ID>::throw_move::<R>)
61            // .register_fn("ailment_affects", ScriptPokemon::<ID>::ailment_affects)
62            .register_fn("damage", ScriptPokemon::<ID>::get_damage::<R>)
63            .register_get("hp", ScriptPokemon::<ID>::hp)
64            .register_iterator::<Vec<ScriptPokemon<ID>>>()
65            .register_type_with_name::<ScriptMove>("Move")
66            .register_get("category", ScriptMove::get_category)
67            .register_get("type", ScriptMove::get_type)
68            .register_get("crit_rate", ScriptMove::get_crit_rate)
69            .register_type_with_name::<MoveCategory>("Category")
70            .register_type_with_name::<PokemonType>("Type")
71            .register_type::<MoveResult>()
72            .register_type_with_name::<ScriptMoveResult<ID>>("Result")
73            .register_fn("Miss", ScriptMoveResult::<ID>::miss)
74            .register_fn("Damage", ScriptMoveResult::<ID>::damage)
75            .register_fn("Ailment", ScriptMoveResult::<ID>::ailment)
76            .register_fn("Drain", ScriptMoveResult::<ID>::heal);
77
78        engine.set_max_expr_depths(0, 0);
79        // engine.set_optimization_level(rhai::OptimizationLevel::Full);
80
81        let mut scope = Scope::new();
82
83        scope.push_constant("CLEAR", LiveScriptAilment::clear_ailment());
84
85        Self {
86            items: Default::default(),
87            moves: Default::default(),
88            engine,
89            scope,
90        }
91    }
92}
93
94impl ScriptingEngine for RhaiScriptingEngine {
95    type Error = RhaiScriptError;
96
97    fn execute_move<
98        ID: Eq + Hash + Clone + 'static + Debug,
99        R: Rng + Clone + 'static,
100        PLR: Players<ID>,
101    >(
102        &self,
103        random: &mut R,
104        m: &Move,
105        user: Indexed<ID, &BattlePokemon>,
106        targets: Vec<PokemonIdentifier<ID>>,
107        players: &PLR,
108    ) -> Result<Vec<Indexed<ID, MoveResult>>, Self::Error> {
109        match self.moves.get(&m.id) {
110            Some(script) => {
111                use rhai::*;
112
113                let mut scope = self.scope.clone();
114
115                scope.push("random", ScriptRandom::new(random));
116
117                let ast = self
118                    .engine
119                    .compile_with_scope(&mut scope, script)
120                    .map_err(|err| RhaiScriptError::Parse(m.id, err))?;
121
122                let targets = targets
123                    .into_iter()
124                    .flat_map(|id| (players.get(&id).map(|r| Indexed(id, r))))
125                    .map(ScriptPokemon::new)
126                    .collect::<Vec<ScriptPokemon<ID>>>();
127
128                let result: Array = self.engine.call_fn(
129                    &mut scope,
130                    &ast,
131                    "use_move",
132                    (ScriptMove::new(m), ScriptPokemon::<ID>::new(user), targets),
133                )?;
134
135                let result = result
136                    .into_iter()
137                    .flat_map(Dynamic::try_cast::<ScriptMoveResult<ID>>)
138                    .map(|r| r.0)
139                    .collect::<Vec<Indexed<ID, MoveResult>>>();
140
141                Ok(result)
142            }
143            None => Err(RhaiScriptError::Missing(m.id)),
144        }
145    }
146
147    fn execute_item<ID: PartialEq, R: Rng, PLR: Players<ID>>(
148        &self,
149        _battle: &BattleData,
150        _random: &mut R,
151        _item: &Item,
152        _user: &ID,
153        _target: PokemonIdentifier<ID>,
154        _players: &mut PLR,
155    ) -> Result<Vec<ItemResult>, Self::Error> {
156        log::debug!("to - do: item scripting");
157        Err(RhaiScriptError::Unimplemented)
158    }
159}
160
161#[derive(Debug)]
162pub enum RhaiScriptError {
163    Parse(MoveId, ParseError),
164    Evaluate(Box<EvalAltResult>),
165    Missing(MoveId),
166    Unimplemented,
167}
168
169impl std::error::Error for RhaiScriptError {}
170
171impl From<Box<EvalAltResult>> for RhaiScriptError {
172    fn from(r: Box<EvalAltResult>) -> Self {
173        Self::Evaluate(r)
174    }
175}
176
177impl core::fmt::Display for RhaiScriptError {
178    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179        match self {
180            #[cfg(feature = "default_engine_scripting")]
181            Self::Parse(id, err) => write!(
182                f,
183                "Cannot parse move script for {} with error {}",
184                &id.0, err
185            ),
186            #[cfg(feature = "default_engine_scripting")]
187            Self::Evaluate(err) => core::fmt::Display::fmt(err, f),
188            Self::Missing(id) => write!(f, "Could not find move script with id {}", &id.0),
189            Self::Unimplemented => write!(f, "Unimplemented feature!"),
190        }
191    }
192}