firecore_battle/default_engine/default_scripting/
mod.rs1use alloc::{boxed::Box, string::String, vec::Vec};
2use core::{fmt::Debug, hash::Hash};
3
4use 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("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 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}