hexroll3_scroll/
commands.rs

1/*
2// Copyright (C) 2020-2025 Pen, Dice & Paper
3//
4// This program is dual-licensed under the following terms:
5//
6// Option 1: (Non-Commercial) GNU Affero General Public License (AGPL)
7// This program is free software: you can redistribute it and/or modify
8// it under the terms of the GNU Affero General Public License as
9// published by the Free Software Foundation, either version 3 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15// GNU Affero General Public License for more details.
16//
17// You should have received a copy of the GNU Affero General Public License
18// along with this program. If not, see <http://www.gnu.org/licenses/>.
19//
20// Option 2: Commercial License
21// For commercial use, you are required to obtain a separate commercial
22// license. Please contact ithai at pendicepaper.com
23// for more information about commercial licensing terms.
24*/
25use std::collections::HashSet;
26use std::sync::Arc;
27
28use anyhow::{anyhow, Result};
29
30use crate::frame::*;
31use crate::generators::*;
32use crate::instance::*;
33use crate::renderer::render_entity;
34use crate::repository::*;
35use crate::semantics::*;
36
37/// A trait for creating commands that assign primitive values to
38/// an attribute
39pub trait ValueAssigner {
40    fn new(name: String, value: serde_json::Value) -> Self;
41}
42
43/// A trait for creating commands related to entity assignments.
44/// Useful for operations such as rolling single entities, handling
45/// entity arrays, or choosing from collected entities.
46pub trait EntityAssigner {
47    /// Constructs a new instance implementing the `EntityAssigner` trait.
48    ///
49    /// # Parameters
50    ///
51    /// * `name`: The attribute name to which the entities will be assigned.
52    /// * `class_names`: Specifies the classes from which entities will be rolled or selected.
53    /// * `min`: Specifies the minimum number of entities to be rolled or selected.
54    /// * `max`: Specifies the maximum number of entities to be rolled or selected.
55    /// * `injectors`: A set of injectors to enhance entities with additional attributes or
56    ///                to override existing attributes.
57    fn new(
58        name: String,
59        class_names: ClassNamesToRoll,
60        min: CardinalityValue,
61        max: CardinalityValue,
62        injectors: Injectors,
63    ) -> Self;
64}
65
66pub trait RefInjectCommand {
67    fn make(name: String, path: Vec<String>) -> Arc<dyn InjectCommand + Send + Sync>;
68}
69
70#[derive(Clone)]
71pub enum CardinalityValue {
72    Number(i32),
73    Variable(String),
74    Undefined,
75}
76
77#[derive(Clone)]
78pub enum ClassNamesToRoll {
79    List(Vec<String>),
80    Indirect(String),
81    Unset(),
82}
83
84/// An attribute command used when assigning simple values to attributes:
85///
86/// ```text
87/// Cat {
88///     name = "Garfield"
89/// }
90/// ```
91#[derive(Clone)]
92pub struct AttrCommandAssigner {
93    pub name: String,
94    pub value: serde_json::Value,
95}
96
97impl ValueAssigner for AttrCommandAssigner {
98    fn new(name: String, value: serde_json::Value) -> Self {
99        AttrCommandAssigner { name, value }
100    }
101}
102
103impl AttrCommand for AttrCommandAssigner {
104    fn apply(
105        &self,
106        _ctx: &mut Context,
107        _builder: &SandboxBuilder,
108        tx: &mut ReadWriteTransaction,
109        euid: &str,
110    ) -> Result<()> {
111        let entity = tx.load(euid)?;
112        if entity.as_object().unwrap().contains_key(&self.name) && self.value.is_boolean() {
113            log::warn!(
114                "Entity class {} has an override with value {} for attribute {} already set to {}",
115                entity["class"],
116                self.value,
117                self.name,
118                entity[&self.name]
119            );
120        }
121        entity[&self.name] = self.value.to_owned();
122        Ok(())
123    }
124
125    fn revert(
126        &self,
127        _ctx: &mut Context,
128        _builder: &SandboxBuilder,
129        tx: &mut ReadWriteTransaction,
130        euid: &str,
131    ) -> Result<()> {
132        let entity = tx.load(euid)?;
133        entity.clear(&self.name);
134        Ok(())
135    }
136    fn value(&self) -> Option<String> {
137        Some(self.value.as_str().unwrap().to_string())
138    }
139}
140
141/// An attribute command for assigning values and templates
142/// that are weakly linked with entities.
143///
144/// These weakly linked values are not stored in the data file,
145/// but are instead read from the parsed model at runtime.
146///
147/// ```text
148/// Cat {
149///     name ~ "Garfield"
150/// }
151/// ```
152#[derive(Clone)]
153pub struct AttrCommandWeakAssigner {
154    pub name: String,
155    pub value: serde_json::Value,
156}
157
158impl AttrCommand for AttrCommandWeakAssigner {
159    fn apply(
160        &self,
161        _ctx: &mut Context,
162        _builder: &SandboxBuilder,
163        tx: &mut ReadWriteTransaction,
164        euid: &str,
165    ) -> Result<()> {
166        let entity = tx.load(euid)?;
167        entity[&self.name] = serde_json::Value::Null;
168        Ok(())
169    }
170
171    fn revert(
172        &self,
173        _ctx: &mut Context,
174        _builder: &SandboxBuilder,
175        tx: &mut ReadWriteTransaction,
176        euid: &str,
177    ) -> Result<()> {
178        let entity = tx.load(euid)?;
179        entity.clear(&self.name);
180        Ok(())
181    }
182    fn value(&self) -> Option<String> {
183        Some(self.value.as_str().unwrap().to_string())
184    }
185}
186
187/// An attribute command for rolling dice values to attributes.
188///
189/// ```text
190/// Dog {
191///     age @ 1d6+2
192/// }
193/// ```
194#[derive(Clone)]
195pub struct AttrCommandDice {
196    pub name: String,
197    pub number_of_dice: i32,
198    pub dice_type: i32,
199    pub dice_modifier: i32,
200}
201
202impl AttrCommand for AttrCommandDice {
203    fn apply(
204        &self,
205        _ctx: &mut Context,
206        builder: &SandboxBuilder,
207        tx: &mut ReadWriteTransaction,
208        euid: &str,
209    ) -> Result<()> {
210        let mut total: i32 = 0;
211        for _ in 0..self.number_of_dice {
212            total += builder.randomizer.in_range(1, self.dice_type);
213        }
214        total += self.dice_modifier;
215        let entity = tx.load(euid)?;
216        entity[&self.name] = serde_json::to_value(total).unwrap();
217        Ok(())
218    }
219
220    fn revert(
221        &self,
222        _ctx: &mut Context,
223        _builder: &SandboxBuilder,
224        tx: &mut ReadWriteTransaction,
225        euid: &str,
226    ) -> Result<()> {
227        let entity = tx.load(euid)?;
228        entity.clear(&self.name);
229        Ok(())
230    }
231}
232
233/// Pre-rendered attribute values command.
234///
235/// Used to pre-render jinja expressions during generation:
236///
237/// ```text
238/// attribute = `{%if True %}{{"some value}}{% endif %}`
239/// ```
240#[derive(Clone)]
241pub struct AttrCommandPrerenderedAssigner {
242    pub name: String,
243    pub value: serde_json::Value,
244}
245
246impl AttrCommand for AttrCommandPrerenderedAssigner {
247    fn apply(
248        &self,
249        _ctx: &mut Context,
250        builder: &SandboxBuilder,
251        tx: &mut ReadWriteTransaction,
252        euid: &str,
253    ) -> Result<()> {
254        let ro_entity = tx.retrieve(euid)?;
255        let rendered = render_entity(builder.sandbox, tx, &ro_entity.value, true)?;
256        let prerendered = builder
257            .templating_env
258            .render_str(self.value.as_str().unwrap(), &rendered)?;
259        let entity = tx.load(euid)?;
260        entity[&self.name] = serde_json::Value::String(prerendered);
261        Ok(())
262    }
263
264    fn revert(
265        &self,
266        _ctx: &mut Context,
267        _builder: &SandboxBuilder,
268        tx: &mut ReadWriteTransaction,
269        euid: &str,
270    ) -> Result<()> {
271        let entity = tx.load(euid)?;
272        entity.clear(&self.name);
273        Ok(())
274    }
275    fn value(&self) -> Option<String> {
276        Some(self.value.as_str().unwrap().to_string())
277    }
278}
279
280/// An attribute command for rolling entities into attributes.
281///
282/// ```text
283/// Wolf {
284/// }
285///
286/// Forest {
287///     [1..10 wolves] @ Wolf
288/// }
289///
290/// ```
291#[derive(Clone)]
292pub struct AttrCommandRollEntity {
293    pub name: String,
294    pub class_names: ClassNamesToRoll,
295    pub min: CardinalityValue,
296    pub max: CardinalityValue,
297    pub injectors: Injectors,
298}
299
300impl EntityAssigner for AttrCommandRollEntity {
301    fn new(
302        name: String,
303        class_names: ClassNamesToRoll,
304        min: CardinalityValue,
305        max: CardinalityValue,
306        injectors: Injectors,
307    ) -> Self {
308        AttrCommandRollEntity {
309            name,
310            class_names,
311            min,
312            max,
313            injectors,
314        }
315    }
316}
317
318impl AttrCommand for AttrCommandRollEntity {
319    fn apply(
320        &self,
321        ctx: &mut Context,
322        builder: &SandboxBuilder,
323        tx: &mut ReadWriteTransaction,
324        euid: &str,
325    ) -> Result<()> {
326        let (min, max) = match ctx {
327            Context::Appending(_) => (1, 1),
328            Context::Rerolling(_) => (1, 1),
329            Context::Rolling => (
330                resolve_value(builder, &self.min),
331                resolve_value(builder, &self.max),
332            ),
333            _ => return Err(anyhow!("Invalid context when applying roll: {:#?}", ctx)),
334        };
335        let class_names = match &self.class_names {
336            ClassNamesToRoll::List(v) => v.clone(),
337            ClassNamesToRoll::Indirect(s) => {
338                let entity = tx.load(euid)?;
339                vec![entity[s].as_str().unwrap().to_string()]
340            }
341            ClassNamesToRoll::Unset() => unreachable!(),
342        };
343        let uid = {
344            let entity = tx.load(euid)?;
345            entity["uid"].as_str().unwrap().to_string()
346        };
347        let seq =
348            if max == 0 && min == 0 {
349                serde_json::json!([])
350            } else {
351                let n = if max - min > 0 {
352                    builder.randomizer.in_range(min, max)
353                } else {
354                    min
355                };
356                let mut ret: serde_json::Value = {
357                    let entity = tx.load(euid)?;
358                    match ctx {
359                        Context::Appending(_) => entity[&self.name].clone(),
360                        Context::Rerolling(_) => entity[&self.name].clone(),
361                        Context::Rolling => serde_json::json!([]),
362                        _ => return Err(anyhow!("Invalid context when applying roll: {:#?}", ctx)),
363                    }
364                };
365                for _ in 0..n {
366                    let actual_class_name = builder.randomizer.choose::<String>(&class_names);
367                    let generated_uid =
368                        roll(builder, tx, actual_class_name, &uid, Some(&self.injectors))?;
369                    {
370                        let entity = tx.load(&generated_uid)?;
371                        entity["$parent"] = serde_json::json!({
372                            "uid": &uid,
373                            "attr": self.name,
374                        });
375                        tx.save(&generated_uid)?;
376                    }
377
378                    if let Context::Rerolling(payload) = ctx {
379                        if let Some(index) = ret.as_array_mut().unwrap().iter().position(|value| {
380                            value.as_str().unwrap() == payload.existing_uid.as_str()
381                        }) {
382                            ret.as_array_mut().unwrap()[index] =
383                                serde_json::Value::from(generated_uid.clone());
384                            payload.new_uid = Some(generated_uid.clone());
385                        } else {
386                        }
387                    } else {
388                        ret.as_array_mut()
389                            .unwrap()
390                            .push(serde_json::Value::from(generated_uid.clone()));
391                    }
392
393                    if let Context::Appending(payload) = ctx {
394                        payload.appended_uid = Some(generated_uid.clone());
395                    }
396                    collect(builder, tx, &uid, generated_uid.as_str(), actual_class_name)?;
397                }
398                serde_json::json!(ret)
399            };
400        {
401            let entity = tx.load(euid)?;
402            entity[&self.name] = seq;
403        }
404        Ok(())
405    }
406
407    fn revert(
408        &self,
409        _ctx: &mut Context,
410        builder: &SandboxBuilder,
411        tx: &mut ReadWriteTransaction,
412        euid: &str,
413    ) -> Result<()> {
414        let entity = tx.load(euid)?;
415        // Checking if there is an actual array is only needed when dealing
416        // with the legacy reference injection. For example:
417        //
418        // ```text
419        // Ruler {
420        //      Weapon @ Weapon
421        //      Necromancer {
422        //          Weapon = &Weapon
423        //      }
424        // }
425        // ```
426        //
427        // In this case, when unrolling, the first weapon will be cleared,
428        // but Necromancer as no way of differentiating between its
429        // Weapon attribute and a regular attribute, so it will try
430        // to revert it, and will find nothing.
431        if let Some(arr) = entity[&self.name].as_array() {
432            for euid in arr.clone() {
433                unroll(builder, tx, euid.as_str().unwrap(), Some(&self.injectors))?;
434            }
435        }
436        // entity.clear(&self.name);
437        Ok(())
438    }
439}
440
441///
442/// Roll an attribute value from list.
443///
444/// ```text
445/// Cat {
446///     color @ [
447///         * Black
448///         * Ginger
449///         * Gray
450///     ]
451/// }
452/// ```
453#[derive(Clone)]
454pub struct AttrCommandRollFromList {
455    pub name: String,
456    pub list: Vec<serde_json::Value>,
457}
458
459impl AttrCommand for AttrCommandRollFromList {
460    fn apply(
461        &self,
462        _ctx: &mut Context,
463        builder: &SandboxBuilder,
464        tx: &mut ReadWriteTransaction,
465        euid: &str,
466    ) -> Result<()> {
467        let entity = tx.load(euid)?;
468        entity[&self.name] = builder.randomizer.choose(&self.list).to_owned();
469        Ok(())
470    }
471
472    fn revert(
473        &self,
474        _ctx: &mut Context,
475        _builder: &SandboxBuilder,
476        tx: &mut ReadWriteTransaction,
477        euid: &str,
478    ) -> Result<()> {
479        let entity = tx.load(euid)?;
480        entity.clear(&self.name);
481        Ok(())
482    }
483}
484
485/// Copy the attribute value of an ancestor.
486/// The ancestor attribute is specified using a class name followed
487/// by an attribute name:
488///
489/// ```text
490/// attribute = :ClassName.attribute_name
491/// ```
492#[derive(Clone)]
493pub struct AttrCommandContext {
494    pub name: String,
495    pub context_parent: String,
496    pub context_attr: String,
497}
498
499impl AttrCommand for AttrCommandContext {
500    fn apply(
501        &self,
502        _ctx: &mut Context,
503        _builder: &SandboxBuilder,
504        tx: &mut ReadWriteTransaction,
505        euid: &str,
506    ) -> Result<()> {
507        let entity = tx.load(euid)?;
508        entity[&self.name] = serde_json::json!({
509            "type" : "context",
510            "spec" : {
511            "parent" : self.context_parent,
512            "attr" : self.context_attr
513        }
514        });
515        Ok(())
516    }
517
518    fn revert(
519        &self,
520        _ctx: &mut Context,
521        _builder: &SandboxBuilder,
522        tx: &mut ReadWriteTransaction,
523        euid: &str,
524    ) -> Result<()> {
525        let entity = tx.load(euid)?;
526        entity.clear(&self.name);
527        Ok(())
528    }
529}
530
531/// Roll an attribute value indirectly using a variable.
532///
533/// ```text
534/// pets = [
535///     * dog
536///     * cat
537///     * fox
538/// ]
539///
540/// attribute @ $pets
541///
542/// ```
543#[derive(Clone)]
544pub struct AttrCommandRollFromVariable {
545    pub name: String,
546    pub var: String,
547}
548
549impl AttrCommand for AttrCommandRollFromVariable {
550    fn apply(
551        &self,
552        _ctx: &mut Context,
553        builder: &SandboxBuilder,
554        tx: &mut ReadWriteTransaction,
555        euid: &str,
556    ) -> Result<()> {
557        let value = builder.sandbox.globals[&self.var]
558            .as_array()
559            .ok_or(anyhow!("Unable to find {}", self.var))?;
560        let entity = tx.load(euid)?;
561        entity[&self.name] = builder.randomizer.choose(value).to_owned();
562        Ok(())
563    }
564
565    fn revert(
566        &self,
567        _ctx: &mut Context,
568        _builder: &SandboxBuilder,
569        tx: &mut ReadWriteTransaction,
570        euid: &str,
571    ) -> Result<()> {
572        let entity = tx.load(euid)?;
573        entity.clear(&self.name);
574        Ok(())
575    }
576}
577
578/// Use a collected entity by class name:
579///
580/// ```text
581/// attribute ? ClassName
582/// ```
583/// or
584///
585/// ```text
586/// [1..3 attribute] ? ClassName
587/// ```
588///
589/// * The class name can be a concrete or a base class
590/// * The used entity will be made unavailable for others until it gets recycled
591///
592/// Any selected entity can be 'injected' with new attributes or attribute overrides:
593///
594/// ```text
595/// attribute ? ClassName {
596///     new_attribute = "some value"
597/// }
598/// ```
599#[derive(Clone)]
600pub struct AttrCommandUseEntity {
601    pub name: String,
602    pub class_names: ClassNamesToRoll,
603    pub min: CardinalityValue,
604    pub max: CardinalityValue,
605    injectors: Injectors,
606}
607
608impl EntityAssigner for AttrCommandUseEntity {
609    fn new(
610        name: String,
611        class_names: ClassNamesToRoll,
612        min: CardinalityValue,
613        max: CardinalityValue,
614        injectors: Injectors,
615    ) -> Self {
616        AttrCommandUseEntity {
617            name,
618            class_names,
619            min,
620            max,
621            injectors,
622        }
623    }
624}
625
626impl AttrCommand for AttrCommandUseEntity {
627    fn apply(
628        &self,
629        ctx: &mut Context,
630        builder: &SandboxBuilder,
631        tx: &mut ReadWriteTransaction,
632        euid: &str,
633    ) -> Result<()> {
634        let entity = tx.load(euid)?;
635        if entity.is_missing(&self.name) {
636            entity[&self.name] =
637                serde_json::to_value(Vec::new() as Vec<serde_json::Value>).unwrap();
638        }
639        let (min, max) = match ctx {
640            Context::Appending(_) => (1, 1),
641            Context::Rolling => (
642                resolve_value(builder, &self.min),
643                resolve_value(builder, &self.max),
644            ),
645            _ => return Err(anyhow!("Invalid context when applying use: {:#?}", ctx)),
646        };
647        let class_names = match &self.class_names {
648            ClassNamesToRoll::List(l) => l,
649            _ => unreachable!(),
650        };
651        for _ in 0..builder.randomizer.in_range(min, max) {
652            let cls = builder.randomizer.choose::<String>(class_names);
653            if let Ok(Some(selected_uid)) = use_collected(builder, tx, euid, cls) {
654                for injector in self.injectors.appenders.as_slice() {
655                    injector.inject(builder, tx, &selected_uid, euid)?;
656                }
657                add_user_to_entity(tx, &selected_uid, euid, &self.name)?;
658                {
659                    let entity = tx.load(euid)?;
660                    let list = entity[&self.name].as_array_mut().unwrap();
661                    list.push(serde_json::to_value(selected_uid)?);
662                }
663            }
664        }
665        //
666        Ok(())
667    }
668
669    fn revert(
670        &self,
671        ctx: &mut Context,
672        builder: &SandboxBuilder,
673        tx: &mut ReadWriteTransaction,
674        euid: &str,
675    ) -> Result<()> {
676        if *ctx != Context::Unrolling {
677            return Err(anyhow!(
678                "Reverting a use entity command is only allowed with Context::Unrolling"
679            ));
680        }
681        let entity = tx.load(euid)?;
682        let uids_in_use = entity[&self.name].as_array().unwrap();
683        for uid_in_use in uids_in_use.clone() {
684            let uid_in_use = uid_in_use.as_str().unwrap();
685            for injector in self.injectors.appenders.as_slice() {
686                injector.eject(builder, tx, uid_in_use, euid)?;
687            }
688            if let Ok(entity_in_use) = tx.load(uid_in_use) {
689                let entity_in_use_class_name = entity_in_use["class"].as_str().unwrap().to_string();
690                entity_in_use["$users"]
691                    .as_array_mut()
692                    .unwrap()
693                    .retain(|user| !(user["uid"] == euid && user["attr"] == self.name));
694                tx.save(uid_in_use)?;
695                recycle(tx, euid, uid_in_use, &entity_in_use_class_name)?;
696            }
697        }
698        // entity.clear(&self.name); // This is likely redundant, but kept for clarity
699        Ok(())
700    }
701}
702
703/// Pick a collected entity by class name:
704///
705/// ```text
706/// attribute % ClassName
707/// ```
708/// or
709///
710/// ```text
711/// [1..3 attribute] % ClassName
712/// ```
713///
714/// * The class name can be a concrete or a base class
715/// * Picked entities can be selected again, unlike used entities.
716/// * If multiple entities are picked for an attribute, uniqueness is maintained.
717///
718/// Any selected entity can be 'injected' with new attributes or attribute overrides:
719///
720/// ```text
721/// attribute ? ClassName {
722///     new_attribute = "some value"
723/// }
724/// ```
725#[derive(Clone)]
726pub struct AttrCommandPickEntity {
727    pub name: String,
728    pub class_names: ClassNamesToRoll,
729    pub min: CardinalityValue,
730    pub max: CardinalityValue,
731    injectors: Injectors,
732}
733
734impl EntityAssigner for AttrCommandPickEntity {
735    fn new(
736        name: String,
737        class_names: ClassNamesToRoll,
738        min: CardinalityValue,
739        max: CardinalityValue,
740        injectors: Injectors,
741    ) -> Self {
742        AttrCommandPickEntity {
743            name,
744            class_names,
745            min,
746            max,
747            injectors,
748        }
749    }
750}
751
752impl AttrCommand for AttrCommandPickEntity {
753    fn apply(
754        &self,
755        ctx: &mut Context,
756        builder: &SandboxBuilder,
757        tx: &mut ReadWriteTransaction,
758        euid: &str,
759    ) -> Result<()> {
760        let entity = tx.load(euid)?;
761        if entity.is_missing(&self.name) {
762            entity[&self.name] =
763                serde_json::to_value(Vec::new() as Vec<serde_json::Value>).unwrap();
764        }
765        let (min, max) = match ctx {
766            Context::Appending(_) => (1, 1),
767            Context::Rolling => (
768                resolve_value(builder, &self.min),
769                resolve_value(builder, &self.max),
770            ),
771            _ => return Err(anyhow!("Invalid context when applying pick: {:#?}", ctx)),
772        };
773
774        let class_names = match &self.class_names {
775            ClassNamesToRoll::List(l) => l,
776            _ => unreachable!(),
777        };
778        let mut uniqueness_check_set: HashSet<String> = HashSet::new();
779        for _ in 0..builder.randomizer.in_range(min, max) {
780            let cls = builder.randomizer.choose::<String>(class_names);
781            if let Ok(Some(selected_uid)) = pick_collected(builder, tx, euid, cls) {
782                if !uniqueness_check_set.insert(selected_uid.clone()) {
783                    continue;
784                }
785                for injector in self.injectors.appenders.as_slice() {
786                    injector.inject(builder, tx, &selected_uid, euid)?;
787                }
788
789                add_user_to_entity(tx, &selected_uid, euid, &self.name)?;
790                let entity = tx.load(euid)?;
791                let list = entity[&self.name].as_array_mut().unwrap();
792                list.push(serde_json::to_value(selected_uid)?);
793            }
794        }
795        Ok(())
796    }
797
798    fn revert(
799        &self,
800        ctx: &mut Context,
801        builder: &SandboxBuilder,
802        tx: &mut ReadWriteTransaction,
803        euid: &str,
804    ) -> Result<()> {
805        if *ctx != Context::Unrolling {
806            return Err(anyhow!(
807                "Reverting a pick entity command is only allowed with Context::Unrolling"
808            ));
809        }
810        let entity = tx.load(euid)?;
811        let uids_in_use = entity[&self.name].as_array().unwrap();
812        for uid_in_use in uids_in_use.clone() {
813            let uid_in_use = uid_in_use.as_str().unwrap();
814            for injector in self.injectors.appenders.as_slice() {
815                injector.eject(builder, tx, uid_in_use, euid)?;
816            }
817            if let Ok(entity_in_use) = tx.load(uid_in_use) {
818                entity_in_use["$users"]
819                    .as_array_mut()
820                    .unwrap()
821                    .retain(|user| !(user["uid"] == euid && user["attr"] == self.name));
822                tx.save(uid_in_use)?;
823            }
824        }
825        Ok(())
826    }
827}
828
829/// An attribute injection command that sets a simple value:
830///
831/// ```text
832/// attribute ? ClassName {
833///     new_attribute = "Just a string"
834/// }
835/// ```
836#[derive(Clone)]
837pub struct InjectCommandSetValue {
838    pub name: String,
839    pub value: serde_json::Value,
840}
841
842impl ValueAssigner for InjectCommandSetValue {
843    fn new(name: String, value: serde_json::Value) -> Self {
844        InjectCommandSetValue { name, value }
845    }
846}
847
848impl InjectCommand for InjectCommandSetValue {
849    fn inject(
850        &self,
851        _builder: &SandboxBuilder,
852        tx: &mut ReadWriteTransaction,
853        euid: &str,
854        _caller: &str,
855    ) -> Result<()> {
856        let entity = tx.load(euid)?;
857        entity[&self.name] = self.value.clone();
858        Ok(())
859    }
860    fn eject(
861        &self,
862        _builder: &SandboxBuilder,
863        tx: &mut ReadWriteTransaction,
864        euid: &str,
865        _caller: &str,
866    ) -> Result<()> {
867        let entity = tx.load(euid)?;
868        entity[&self.name] = serde_json::Value::from(false);
869        Ok(())
870    }
871}
872
873/// An attribute injection command that sets a dice roll value:
874///
875/// ```text
876/// attribute ? ClassName {
877///     new_attribute @ 2d20
878/// }
879/// ```
880#[derive(Clone)]
881pub struct InjectCommandDiceRoll {
882    pub name: String,
883    pub number_of_dice: i32,
884    pub dice_type: i32,
885    pub dice_modifier: i32,
886}
887
888impl InjectCommand for InjectCommandDiceRoll {
889    fn inject(
890        &self,
891        builder: &SandboxBuilder,
892        tx: &mut ReadWriteTransaction,
893        euid: &str,
894        _caller: &str,
895    ) -> Result<()> {
896        let entity = tx.load(euid)?;
897        let mut total: i32 = 0;
898        for _ in 0..self.number_of_dice {
899            total += builder.randomizer.in_range(1, self.dice_type);
900        }
901        total += self.dice_modifier;
902        entity[&self.name] = serde_json::to_value(total).unwrap();
903        Ok(())
904    }
905    fn eject(
906        &self,
907        _builder: &SandboxBuilder,
908        tx: &mut ReadWriteTransaction,
909        euid: &str,
910        _caller: &str,
911    ) -> Result<()> {
912        let entity = tx.load(euid)?;
913        entity[&self.name] = serde_json::Value::from(false);
914        Ok(())
915    }
916}
917
918/// An attribute injection command that rolls a value from a list:
919///
920/// ```text
921/// attribute % ClassName {
922///     new_attribute @ [
923///         * foo
924///         * bar
925///     ]
926/// }
927/// ```
928#[derive(Clone)]
929pub struct InjectCommandRollFromList {
930    pub name: String,
931    pub list: Vec<serde_json::Value>,
932}
933
934impl InjectCommand for InjectCommandRollFromList {
935    fn inject(
936        &self,
937        builder: &SandboxBuilder,
938        tx: &mut ReadWriteTransaction,
939        euid: &str,
940        _caller: &str,
941    ) -> Result<()> {
942        let entity = tx.load(euid)?;
943        entity[&self.name] = builder.randomizer.choose(&self.list).to_owned();
944        Ok(())
945    }
946    fn eject(
947        &self,
948        _builder: &SandboxBuilder,
949        tx: &mut ReadWriteTransaction,
950        euid: &str,
951        _caller: &str,
952    ) -> Result<()> {
953        let entity = tx.load(euid)?;
954        entity.clear(&self.name);
955        Ok(())
956    }
957}
958
959/// An attribute injection command that copies a value from the parent entity:
960///
961/// ```text
962/// MainClass {
963///     main_attribute = "Value"
964///     attribute % AnotherClass {
965///         # new_attribute will be set to "Value"
966///         new_attribute = &main_attribute
967///     }
968/// }
969///```
970#[derive(Clone)]
971pub struct InjectCommandCopyValue {
972    pub name: String,
973    pub path: Vec<String>,
974}
975
976impl RefInjectCommand for InjectCommandCopyValue {
977    fn make(name: String, path: Vec<String>) -> Arc<dyn InjectCommand + Send + Sync> {
978        Arc::new(InjectCommandCopyValue { name, path })
979    }
980}
981
982impl InjectCommand for InjectCommandCopyValue {
983    fn inject(
984        &self,
985        _builder: &SandboxBuilder,
986        tx: &mut ReadWriteTransaction,
987        euid: &str,
988        caller: &str,
989    ) -> Result<()> {
990        let value = {
991            if let Some((pointer_uid, pointer_attr_name)) = walk_path(caller, &self.path, tx)? {
992                let src = tx.load(pointer_uid.as_str().unwrap())?;
993                src[pointer_attr_name.as_str().unwrap()].clone()
994            } else {
995                serde_json::Value::from(false)
996            }
997        };
998        let entity = tx.load(euid)?;
999        entity[&self.name] = value;
1000        Ok(())
1001    }
1002    fn eject(
1003        &self,
1004        _builder: &SandboxBuilder,
1005        tx: &mut ReadWriteTransaction,
1006        euid: &str,
1007        _caller: &str,
1008    ) -> Result<()> {
1009        let entity = tx.load(euid)?;
1010        entity[&self.name] = serde_json::Value::from(false);
1011        Ok(())
1012    }
1013}
1014
1015/// An attribute injection command that points to a value in the parent entity.
1016/// The actual value is resolved during rendering.
1017///
1018/// ```text
1019/// MainClass {
1020///     main_attribute = "Value"
1021///     attribute % AnotherClass {
1022///         # new_attribute will not be statically set and can change
1023///         # if main_attribute's value is modified.
1024///         new_attribute = *main_attribute
1025///     }
1026/// }
1027///```
1028pub struct InjectCommandPtr {
1029    pub name: String,
1030    pub path: Vec<String>,
1031}
1032
1033impl RefInjectCommand for InjectCommandPtr {
1034    fn make(name: String, path: Vec<String>) -> Arc<dyn InjectCommand + Send + Sync> {
1035        Arc::new(InjectCommandPtr { name, path })
1036    }
1037}
1038
1039impl InjectCommand for InjectCommandPtr {
1040    fn inject(
1041        &self,
1042        _builder: &SandboxBuilder,
1043        tx: &mut ReadWriteTransaction,
1044        euid: &str,
1045        caller: &str,
1046    ) -> Result<()> {
1047        if let Some((pointer_uid, pointer_attr_name)) = walk_path(caller, &self.path, tx)? {
1048            let value = serde_json::json!({
1049                "type": "pointer",
1050                "spec": {
1051                    "uid": pointer_uid,
1052                    "attr": pointer_attr_name
1053                }
1054            });
1055            let entity = tx.load(euid)?;
1056            entity[&self.name] = value;
1057            add_user_to_entity(tx, pointer_uid.as_str().unwrap(), euid, &self.name)?;
1058            Ok(())
1059        } else {
1060            Err(anyhow!(
1061                "walking an attribute path failed for {}",
1062                self.name
1063            ))
1064        }
1065    }
1066    fn eject(
1067        &self,
1068        _builder: &SandboxBuilder,
1069        tx: &mut ReadWriteTransaction,
1070        euid: &str,
1071        _caller: &str,
1072    ) -> Result<()> {
1073        let entity = tx.load(euid)?;
1074        entity[&self.name] = serde_json::Value::from(false);
1075        Ok(())
1076    }
1077}
1078
1079/// Adds a user to a used, picked or pointed-to entity.
1080/// The user spec is added to a `$users` array attribute and is used to
1081/// inform any users in case the entity in use is unrolled.
1082/// # Parameters
1083///
1084/// - `tx`: A read/write transaction
1085/// - `uid`: Uid of the entity being used, picked or pointed-to.
1086/// - `user_uid`: Uid of the user
1087/// - `user_attr`: The attribute in the user entity storing this entity's uid.
1088///
1089/// # Returns
1090///
1091/// - `Result<()>`: Returns `Ok(())` if the operation is successful or an error if it fails.
1092fn add_user_to_entity(
1093    tx: &mut ReadWriteTransaction,
1094    uid: &str,
1095    user_uid: &str,
1096    user_attr: &str,
1097) -> Result<()> {
1098    let selected_entity = tx.load(uid)?;
1099    if selected_entity.is_missing("$users") {
1100        selected_entity["$users"] = serde_json::json!([]);
1101    }
1102    selected_entity["$users"]
1103        .as_array_mut()
1104        .unwrap()
1105        .push(serde_json::json!({
1106            "uid": user_uid,
1107            "attr": user_attr,
1108        }));
1109    tx.save(uid)?;
1110    Ok(())
1111}
1112
1113/// Resolves pointer or reference attribute path.
1114///
1115/// When injecting pointers or references, the specified attribute
1116/// can be navigated to using dot-notation path:
1117///
1118/// ```text
1119/// attribute = *parent_attr.its_attribute
1120/// ```
1121fn walk_path(
1122    starting_uid: &str,
1123    path: &[String],
1124    tx: &mut ReadWriteTransaction,
1125) -> Result<Option<(serde_json::Value, serde_json::Value)>> {
1126    let mut pointer_uid: serde_json::Value = serde_json::json!(starting_uid);
1127    let mut pointer_attr_name = serde_json::to_value(path.first()).unwrap();
1128    for (index, path_part) in path.iter().enumerate() {
1129        let src = tx.load(pointer_uid.as_str().unwrap())?;
1130        let pointed_value = &src[path_part];
1131        if index == path.len() - 1 {
1132            pointer_attr_name = serde_json::to_value(path_part).unwrap();
1133        } else if let Some(value_as_array) = pointed_value.as_array() {
1134            if value_as_array.is_empty() {
1135                return Ok(None);
1136            }
1137            pointer_uid = value_as_array.first().unwrap().clone();
1138        } else {
1139            pointer_attr_name = serde_json::to_value(path_part).unwrap();
1140        }
1141    }
1142    Ok(Some((pointer_uid, pointer_attr_name)))
1143}
1144
1145/// Resolve the actual cardinality value when specifying it in array attributes.
1146/// Values can be set explicitly as integers, or indirectly from variables.
1147/// When no cardinality is specified, then we assume a single entity
1148/// is being generated.
1149fn resolve_value(builder: &SandboxBuilder, cv: &CardinalityValue) -> i32 {
1150    match cv {
1151        CardinalityValue::Number(n) => *n,
1152        CardinalityValue::Variable(v) => {
1153            log::info!("{}", v);
1154            builder.sandbox.globals.get(v).unwrap().as_i64().unwrap() as i32
1155        }
1156        CardinalityValue::Undefined => 1,
1157    }
1158}