Skip to main content

weasel/
character.rs

1//! Character rules.
2
3use crate::battle::{Battle, BattleRules, BattleState};
4use crate::entity::{transmute_entity, Entities, Entity, EntityId, Transmutation};
5use crate::entropy::Entropy;
6use crate::error::{WeaselError, WeaselResult};
7use crate::event::{Event, EventKind, EventProcessor, EventQueue, EventTrigger, Prioritized};
8use crate::metric::WriteMetrics;
9use crate::status::{AppliedStatus, Potency, Status, StatusId};
10use crate::util::Id;
11#[cfg(feature = "serialization")]
12use serde::{Deserialize, Serialize};
13use std::any::Any;
14use std::fmt::{Debug, Formatter, Result};
15use std::hash::Hash;
16
17/// Rules to define the structure and the behavior of characters.
18pub trait CharacterRules<R: BattleRules> {
19    #[cfg(not(feature = "serialization"))]
20    /// See [CreatureId](../creature/type.CreatureId.html).
21    type CreatureId: Hash + Eq + Clone + Debug + Send;
22    #[cfg(feature = "serialization")]
23    /// See [CreatureId](../creature/type.CreatureId.html).
24    type CreatureId: Hash + Eq + Clone + Debug + Send + Serialize + for<'a> Deserialize<'a>;
25
26    #[cfg(not(feature = "serialization"))]
27    /// See [ObjectId](../object/type.ObjectId.html).
28    type ObjectId: Hash + Eq + Clone + Debug + Send;
29    #[cfg(feature = "serialization")]
30    /// See [ObjectId](../object/type.ObjectId.html).
31    type ObjectId: Hash + Eq + Clone + Debug + Send + Serialize + for<'a> Deserialize<'a>;
32
33    /// See [Statistic](type.Statistic.html).
34    type Statistic: Id + 'static;
35
36    #[cfg(not(feature = "serialization"))]
37    /// See [StatisticsSeed](type.StatisticsSeed.html).
38    type StatisticsSeed: Clone + Debug + Send;
39    #[cfg(feature = "serialization")]
40    /// See [StatisticsSeed](type.StatisticsSeed.html).
41    type StatisticsSeed: Clone + Debug + Send + Serialize + for<'a> Deserialize<'a>;
42
43    #[cfg(not(feature = "serialization"))]
44    /// See [StatisticsAlteration](type.StatisticsAlteration.html).
45    type StatisticsAlteration: Clone + Debug + Send;
46    #[cfg(feature = "serialization")]
47    /// See [StatisticsAlteration](type.StatisticsAlteration.html).
48    type StatisticsAlteration: Clone + Debug + Send + Serialize + for<'a> Deserialize<'a>;
49
50    /// See [Status](../status/type.Status.html).
51    type Status: Id + 'static;
52
53    #[cfg(not(feature = "serialization"))]
54    /// See [StatusesAlteration](../status/type.StatusesAlteration.html).
55    type StatusesAlteration: Clone + Debug + Send;
56    #[cfg(feature = "serialization")]
57    /// See [StatusesAlteration](../status/type.StatusesAlteration.html).
58    type StatusesAlteration: Clone + Debug + Send + Serialize + for<'a> Deserialize<'a>;
59
60    /// Generates all statistics of a character.
61    /// Statistics should have unique ids, otherwise only the last entry will be persisted.
62    ///
63    /// The provided implementation generates an empty set of statistics.
64    fn generate_statistics(
65        &self,
66        _seed: &Option<Self::StatisticsSeed>,
67        _entropy: &mut Entropy<R>,
68        _metrics: &mut WriteMetrics<R>,
69    ) -> Box<dyn Iterator<Item = Self::Statistic>> {
70        Box::new(std::iter::empty())
71    }
72
73    /// Alters one or more statistics starting from the given alteration object.\
74    /// Returns an optional `Transmutation` to be applied to the character as result of
75    /// this alteration.
76    ///
77    /// The provided implementation does nothing.
78    fn alter_statistics(
79        &self,
80        _character: &mut dyn Character<R>,
81        _alteration: &Self::StatisticsAlteration,
82        _entropy: &mut Entropy<R>,
83        _metrics: &mut WriteMetrics<R>,
84    ) -> Option<Transmutation> {
85        None
86    }
87
88    /// Generates a status to be applied to the given character.\
89    /// Returns the new status or nothing if no status should be added. Existing status with
90    /// the same id will be replaced.
91    ///
92    /// The provided implementation returns `None`.
93    fn generate_status(
94        &self,
95        _character: &dyn Character<R>,
96        _status_id: &StatusId<R>,
97        _potency: &Option<Potency<R>>,
98        _entropy: &mut Entropy<R>,
99        _metrics: &mut WriteMetrics<R>,
100    ) -> Option<Status<R>> {
101        None
102    }
103
104    /// Alters one or more statuses starting from the given alteration object.
105    ///
106    /// The provided implementation does nothing.
107    fn alter_statuses(
108        &self,
109        _character: &mut dyn Character<R>,
110        _alteration: &Self::StatusesAlteration,
111        _entropy: &mut Entropy<R>,
112        _metrics: &mut WriteMetrics<R>,
113    ) {
114    }
115
116    /// Invoked when a character is added to the battle.
117    ///
118    /// The provided implementation does nothing.
119    fn on_character_added(
120        &self,
121        _state: &BattleState<R>,
122        _character: &dyn Character<R>,
123        _event_queue: &mut Option<EventQueue<R>>,
124        _entropy: &mut Entropy<R>,
125        _metrics: &mut WriteMetrics<R>,
126    ) {
127    }
128
129    /// Invoked when a character is transmuted during the battle.
130    ///
131    /// The provided implementation does nothing.    
132    fn on_character_transmuted(
133        &self,
134        _state: &BattleState<R>,
135        _character: &dyn Character<R>,
136        _transmutation: Transmutation,
137        _event_queue: &mut Option<EventQueue<R>>,
138        _entropy: &mut Entropy<R>,
139        _metrics: &mut WriteMetrics<R>,
140    ) {
141    }
142}
143
144/// Type to represent an individual statistic.
145///
146/// Statistics are the primary way to describe attributes of characters. For instance,
147/// the *health points* of a creature are a prime example of statistic.
148pub type Statistic<R> = <<R as BattleRules>::CR as CharacterRules<R>>::Statistic;
149
150/// Alias for `Statistic<R>::Id`.
151pub type StatisticId<R> = <Statistic<R> as Id>::Id;
152
153/// Type to drive the generation of all statistics of a character.
154pub type StatisticsSeed<R> = <<R as BattleRules>::CR as CharacterRules<R>>::StatisticsSeed;
155
156/// Encapsulates the data used to describe an alteration of one or more statistics.
157pub type StatisticsAlteration<R> =
158    <<R as BattleRules>::CR as CharacterRules<R>>::StatisticsAlteration;
159
160/// A trait for objects which possess statistics.
161pub trait Character<R: BattleRules>: Entity<R> {
162    /// Returns an iterator over statistics.
163    fn statistics<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Statistic<R>> + 'a>;
164
165    /// Returns a mutable iterator over statistics.
166    fn statistics_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut Statistic<R>> + 'a>;
167
168    /// Returns the statistic with the given id.
169    fn statistic(&self, id: &StatisticId<R>) -> Option<&Statistic<R>>;
170
171    /// Returns a mutable reference to the statistic with the given id.
172    fn statistic_mut(&mut self, id: &StatisticId<R>) -> Option<&mut Statistic<R>>;
173
174    /// Adds a new statistic. Replaces an existing statistic with the same id.
175    /// Returns the replaced statistic, if present.
176    fn add_statistic(&mut self, statistic: Statistic<R>) -> Option<Statistic<R>>;
177
178    /// Removes a statistic.
179    /// Returns the removed statistic, if present.
180    fn remove_statistic(&mut self, id: &StatisticId<R>) -> Option<Statistic<R>>;
181
182    /// Returns an iterator over statuses.
183    fn statuses<'a>(&'a self) -> Box<dyn Iterator<Item = &'a AppliedStatus<R>> + 'a>;
184
185    /// Returns a mutable iterator over statuses.
186    fn statuses_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut AppliedStatus<R>> + 'a>;
187
188    /// Returns the status with the given id.
189    fn status(&self, id: &StatusId<R>) -> Option<&AppliedStatus<R>>;
190
191    /// Returns a mutable reference to the status with the given id.
192    fn status_mut(&mut self, id: &StatusId<R>) -> Option<&mut AppliedStatus<R>>;
193
194    /// Adds a new status. Replaces an existing status with the same id.
195    /// Returns the replaced status, if present.
196    fn add_status(&mut self, status: AppliedStatus<R>) -> Option<AppliedStatus<R>>;
197
198    /// Removes a status.
199    /// Returns the removed status, if present.
200    fn remove_status(&mut self, id: &StatusId<R>) -> Option<AppliedStatus<R>>;
201}
202
203/// An event to alter the statistics of a character.
204///
205/// # Examples
206/// ```
207/// use weasel::{
208///     battle_rules, rules::empty::*, AlterStatistics, Battle, BattleController, BattleRules,
209///     CreateCreature, CreateTeam, EntityId, EventKind, EventTrigger, Server,
210/// };
211///
212/// battle_rules! {}
213///
214/// let battle = Battle::builder(CustomRules::new()).build();
215/// let mut server = Server::builder(battle).build();
216///
217/// let team_id = 1;
218/// CreateTeam::trigger(&mut server, team_id).fire().unwrap();
219/// let creature_id = 1;
220/// let position = ();
221/// CreateCreature::trigger(&mut server, creature_id, team_id, position)
222///     .fire()
223///     .unwrap();
224///
225/// let alteration = ();
226/// AlterStatistics::trigger(&mut server, EntityId::Creature(creature_id), alteration)
227///     .fire()
228///     .unwrap();
229/// assert_eq!(
230///     server.battle().history().events().iter().last().unwrap().kind(),
231///     EventKind::AlterStatistics
232/// );
233/// ```
234#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
235pub struct AlterStatistics<R: BattleRules> {
236    #[cfg_attr(
237        feature = "serialization",
238        serde(bound(
239            serialize = "EntityId<R>: Serialize",
240            deserialize = "EntityId<R>: Deserialize<'de>"
241        ))
242    )]
243    id: EntityId<R>,
244
245    #[cfg_attr(
246        feature = "serialization",
247        serde(bound(
248            serialize = "StatisticsAlteration<R>: Serialize",
249            deserialize = "StatisticsAlteration<R>: Deserialize<'de>"
250        ))
251    )]
252    alteration: StatisticsAlteration<R>,
253}
254
255impl<R: BattleRules> AlterStatistics<R> {
256    /// Returns a trigger for this event.
257    pub fn trigger<'a, P: EventProcessor<R>>(
258        processor: &'a mut P,
259        id: EntityId<R>,
260        alteration: StatisticsAlteration<R>,
261    ) -> AlterStatisticsTrigger<'a, R, P> {
262        AlterStatisticsTrigger {
263            processor,
264            id,
265            alteration,
266        }
267    }
268
269    /// Returns the character's entity id.
270    pub fn id(&self) -> &EntityId<R> {
271        &self.id
272    }
273
274    /// Returns the definition of the changes to the character's statistics.
275    pub fn alteration(&self) -> &StatisticsAlteration<R> {
276        &self.alteration
277    }
278}
279
280impl<R: BattleRules> Debug for AlterStatistics<R> {
281    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
282        write!(
283            f,
284            "AlterStatistics {{ id: {:?}, alteration: {:?} }}",
285            self.id, self.alteration
286        )
287    }
288}
289
290impl<R: BattleRules> Clone for AlterStatistics<R> {
291    fn clone(&self) -> Self {
292        Self {
293            id: self.id.clone(),
294            alteration: self.alteration.clone(),
295        }
296    }
297}
298
299impl<R: BattleRules + 'static> Event<R> for AlterStatistics<R> {
300    fn verify(&self, battle: &Battle<R>) -> WeaselResult<(), R> {
301        verify_get_character(battle.entities(), &self.id).map(|_| ())
302    }
303
304    fn apply(&self, battle: &mut Battle<R>, event_queue: &mut Option<EventQueue<R>>) {
305        // Retrieve the character.
306        let character = battle
307            .state
308            .entities
309            .character_mut(&self.id)
310            .unwrap_or_else(|| panic!("constraint violated: character {:?} not found", self.id));
311        // Alter the character.
312        let transmutation = battle.rules.character_rules().alter_statistics(
313            character,
314            &self.alteration,
315            &mut battle.entropy,
316            &mut battle.metrics.write_handle(),
317        );
318        // Change the character's existence if needed.
319        if let Some(transmutation) = transmutation {
320            transmute_entity(
321                &self.id,
322                transmutation,
323                &mut event_queue.as_mut().map(|queue| Prioritized::new(queue)),
324            );
325        }
326    }
327
328    fn kind(&self) -> EventKind {
329        EventKind::AlterStatistics
330    }
331
332    fn box_clone(&self) -> Box<dyn Event<R> + Send> {
333        Box::new(self.clone())
334    }
335
336    fn as_any(&self) -> &dyn Any {
337        self
338    }
339}
340
341/// Trigger to build and fire an `AlterStatistics` event.
342pub struct AlterStatisticsTrigger<'a, R, P>
343where
344    R: BattleRules,
345    P: EventProcessor<R>,
346{
347    processor: &'a mut P,
348    id: EntityId<R>,
349    alteration: StatisticsAlteration<R>,
350}
351
352impl<'a, R, P> EventTrigger<'a, R, P> for AlterStatisticsTrigger<'a, R, P>
353where
354    R: BattleRules + 'static,
355    P: EventProcessor<R>,
356{
357    fn processor(&'a mut self) -> &'a mut P {
358        self.processor
359    }
360
361    /// Returns an `AlterStatistics` event.
362    fn event(&self) -> Box<dyn Event<R> + Send> {
363        Box::new(AlterStatistics {
364            id: self.id.clone(),
365            alteration: self.alteration.clone(),
366        })
367    }
368}
369
370/// An event to regenerate the statistics of a character.
371///
372/// A new set of statistics is created from a seed.\
373/// - Statistics already present in the character won't be modified.
374/// - Statistics that the character didn't have before will be added.
375/// - Current character's statistics that are not present in the new set will be removed
376///   from the character.
377///
378/// # Examples
379/// ```
380/// use weasel::{
381///     battle_rules, rules::empty::*, Battle, BattleController, BattleRules, CreateCreature,
382///     CreateTeam, EntityId, EventKind, EventTrigger, RegenerateStatistics, Server,
383/// };
384///
385/// battle_rules! {}
386///
387/// let battle = Battle::builder(CustomRules::new()).build();
388/// let mut server = Server::builder(battle).build();
389///
390/// let team_id = 1;
391/// CreateTeam::trigger(&mut server, team_id).fire().unwrap();
392/// let creature_id = 1;
393/// let position = ();
394/// CreateCreature::trigger(&mut server, creature_id, team_id, position)
395///     .fire()
396///     .unwrap();
397///
398/// RegenerateStatistics::trigger(&mut server, EntityId::Creature(creature_id))
399///     .fire()
400///     .unwrap();
401/// assert_eq!(
402///     server.battle().history().events().iter().last().unwrap().kind(),
403///     EventKind::RegenerateStatistics
404/// );
405/// ```
406#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
407pub struct RegenerateStatistics<R: BattleRules> {
408    #[cfg_attr(
409        feature = "serialization",
410        serde(bound(
411            serialize = "EntityId<R>: Serialize",
412            deserialize = "EntityId<R>: Deserialize<'de>"
413        ))
414    )]
415    id: EntityId<R>,
416
417    #[cfg_attr(
418        feature = "serialization",
419        serde(bound(
420            serialize = "Option<StatisticsSeed<R>>: Serialize",
421            deserialize = "Option<StatisticsSeed<R>>: Deserialize<'de>"
422        ))
423    )]
424    seed: Option<StatisticsSeed<R>>,
425}
426
427impl<R: BattleRules> RegenerateStatistics<R> {
428    /// Returns a trigger for this event.
429    pub fn trigger<P: EventProcessor<R>>(
430        processor: &'_ mut P,
431        id: EntityId<R>,
432    ) -> RegenerateStatisticsTrigger<'_, R, P> {
433        RegenerateStatisticsTrigger {
434            processor,
435            id,
436            seed: None,
437        }
438    }
439
440    /// Returns the character's entity id.
441    pub fn id(&self) -> &EntityId<R> {
442        &self.id
443    }
444
445    /// Returns the seed to regenerate the character's statistics.
446    pub fn seed(&self) -> &Option<StatisticsSeed<R>> {
447        &self.seed
448    }
449}
450
451impl<R: BattleRules> Debug for RegenerateStatistics<R> {
452    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
453        write!(
454            f,
455            "RegenerateStatistics {{ id: {:?}, seed: {:?} }}",
456            self.id, self.seed
457        )
458    }
459}
460
461impl<R: BattleRules> Clone for RegenerateStatistics<R> {
462    fn clone(&self) -> Self {
463        Self {
464            id: self.id.clone(),
465            seed: self.seed.clone(),
466        }
467    }
468}
469
470impl<R: BattleRules + 'static> Event<R> for RegenerateStatistics<R> {
471    fn verify(&self, battle: &Battle<R>) -> WeaselResult<(), R> {
472        verify_get_character(battle.entities(), &self.id).map(|_| ())
473    }
474
475    fn apply(&self, battle: &mut Battle<R>, _: &mut Option<EventQueue<R>>) {
476        // Retrieve the character.
477        let character = battle
478            .state
479            .entities
480            .character_mut(&self.id)
481            .unwrap_or_else(|| panic!("constraint violated: character {:?} not found", self.id));
482        // Generate a new set of statistics.
483        let statistics: Vec<_> = battle
484            .rules
485            .character_rules()
486            .generate_statistics(
487                &self.seed,
488                &mut battle.entropy,
489                &mut battle.metrics.write_handle(),
490            )
491            .collect();
492        let mut to_remove = Vec::new();
493        // Remove all character's statistics not present in the new set.
494        for statistic in character.statistics() {
495            if statistics
496                .iter()
497                .find(|e| e.id() == statistic.id())
498                .is_none()
499            {
500                to_remove.push(statistic.id().clone());
501            }
502        }
503        for statistic_id in to_remove {
504            character.remove_statistic(&statistic_id);
505        }
506        // Add all statistics present in the new set but not in the character.
507        for statistic in statistics {
508            if character.statistic(statistic.id()).is_none() {
509                character.add_statistic(statistic);
510            }
511        }
512    }
513
514    fn kind(&self) -> EventKind {
515        EventKind::RegenerateStatistics
516    }
517
518    fn box_clone(&self) -> Box<dyn Event<R> + Send> {
519        Box::new(self.clone())
520    }
521
522    fn as_any(&self) -> &dyn Any {
523        self
524    }
525}
526
527/// Trigger to build and fire a `RegenerateStatistics` event.
528pub struct RegenerateStatisticsTrigger<'a, R, P>
529where
530    R: BattleRules,
531    P: EventProcessor<R>,
532{
533    processor: &'a mut P,
534    id: EntityId<R>,
535    seed: Option<StatisticsSeed<R>>,
536}
537
538impl<'a, R, P> RegenerateStatisticsTrigger<'a, R, P>
539where
540    R: BattleRules + 'static,
541    P: EventProcessor<R>,
542{
543    /// Adds a seed to drive the regeneration of this character's statistics.
544    pub fn seed(&'a mut self, seed: StatisticsSeed<R>) -> &'a mut Self {
545        self.seed = Some(seed);
546        self
547    }
548}
549
550impl<'a, R, P> EventTrigger<'a, R, P> for RegenerateStatisticsTrigger<'a, R, P>
551where
552    R: BattleRules + 'static,
553    P: EventProcessor<R>,
554{
555    fn processor(&'a mut self) -> &'a mut P {
556        self.processor
557    }
558
559    /// Returns a `RegenerateStatistics` event.
560    fn event(&self) -> Box<dyn Event<R> + Send> {
561        Box::new(RegenerateStatistics {
562            id: self.id.clone(),
563            seed: self.seed.clone(),
564        })
565    }
566}
567
568/// Checks if an entity exists and is a character.
569/// Returns the character if successful;
570pub(crate) fn verify_get_character<'a, R>(
571    entities: &'a Entities<R>,
572    id: &EntityId<R>,
573) -> WeaselResult<&'a dyn Character<R>, R>
574where
575    R: BattleRules,
576{
577    // Check if this entity claims to be a character.
578    if !id.is_character() {
579        return Err(WeaselError::NotACharacter(id.clone()));
580    }
581    // Check if the character exists.
582    let character = entities
583        .character(id)
584        .ok_or_else(|| WeaselError::EntityNotFound(id.clone()))?;
585    Ok(character)
586}