1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! Module to handle combat.

use crate::battle::{Battle, BattleRules, BattleState};
use crate::character::Character;
use crate::entropy::Entropy;
use crate::error::WeaselResult;
use crate::event::{Event, EventKind, EventProcessor, EventQueue, EventTrigger, LinkedQueue};
use crate::metric::WriteMetrics;
use crate::status::{Application, AppliedStatus};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::fmt::Debug;

/// Rules to determine how combat works. They manage the damage dealt,
/// accuracy of attacks and, more in general, how to apply consequences of abilities.
pub trait FightRules<R: BattleRules> {
    #[cfg(not(feature = "serialization"))]
    /// See [Impact](type.Impact.html).
    type Impact: Clone + Debug + Send;
    #[cfg(feature = "serialization")]
    /// See [Impact](type.Impact.html).
    type Impact: Clone + Debug + Send + Serialize + for<'a> Deserialize<'a>;

    #[cfg(not(feature = "serialization"))]
    /// See [Potency](../status/type.Potency.html).
    type Potency: Clone + Debug + Send;
    #[cfg(feature = "serialization")]
    /// See [Potency](../status/type.Potency.html).
    type Potency: Clone + Debug + Send + Serialize + for<'a> Deserialize<'a>;

    /// Takes an impact and generates one or more events to change the state of creatures or
    /// other objects.
    ///
    /// The provided implementation does nothing.
    fn apply_impact(
        &self,
        _state: &BattleState<R>,
        _impact: &Self::Impact,
        _event_queue: &mut Option<EventQueue<R>>,
        _entropy: &mut Entropy<R>,
        _metrics: &mut WriteMetrics<R>,
    ) {
    }

    /// Applies the side effects of a status when it's inflicted upon a character.
    /// `application` contains the context in which the status was created.
    ///
    /// The status is automatically added to the character before the call to this method.
    ///
    /// The provided implementation does nothing.
    fn apply_status(
        &self,
        _state: &BattleState<R>,
        _character: &dyn Character<R>,
        _application: Application<R>,
        _event_queue: &mut Option<EventQueue<R>>,
        _entropy: &mut Entropy<R>,
        _metrics: &mut WriteMetrics<R>,
    ) {
    }

    /// Applies the periodic side effects of a status.
    /// Returns `true` if the status should end after this update.
    ///
    /// For actors: status updates happen at the start of their turn.\
    /// For non-actor characters: status updates happen when the event `EnvironmentTurn` is fired.
    ///
    /// The provided implementation does nothing and it never ends any status.
    fn update_status(
        &self,
        _state: &BattleState<R>,
        _character: &dyn Character<R>,
        _status: &AppliedStatus<R>,
        _linked_queue: &mut Option<LinkedQueue<R>>,
        _entropy: &mut Entropy<R>,
        _metrics: &mut WriteMetrics<R>,
    ) -> bool {
        false
    }

    /// Removes the side effects of a status when the latter is removed from a character.
    ///
    /// The character is guaranteed to be affected by `status`.
    /// The status will be automatically dropped immediately after this method.
    ///
    /// The provided implementation does nothing.
    fn delete_status(
        &self,
        _state: &BattleState<R>,
        _character: &dyn Character<R>,
        _status: &AppliedStatus<R>,
        _event_queue: &mut Option<EventQueue<R>>,
        _entropy: &mut Entropy<R>,
        _metrics: &mut WriteMetrics<R>,
    ) {
    }
}

/// Impacts encapsulate information about which creatures or areas are affected
/// and what force is applied to them.
///
/// More specifically, an impact should contain
/// the necessary data to generate altering events on creatures or other objects.\
/// It's important to understand that an impact is an indirection between an ability's output
/// and its effect on the world. For instance, throwing a bomb could be considered the
/// ability while the bomb's explosion would be the impact; the explosion might then
/// cause damage to one or more creatures.
pub type Impact<R> = <<R as BattleRules>::FR as FightRules<R>>::Impact;

/// An event to apply an impact on the game world.
///
/// # Examples
/// ```
/// use weasel::{
///     battle_rules, rules::empty::*, ApplyImpact, Battle, BattleController, BattleRules,
///     EventKind, EventTrigger, Server,
/// };
///
/// battle_rules! {}
///
/// let battle = Battle::builder(CustomRules::new()).build();
/// let mut server = Server::builder(battle).build();
///
/// let impact = ();
/// ApplyImpact::trigger(&mut server, impact).fire().unwrap();
/// assert_eq!(
///     server.battle().history().events()[0].kind(),
///     EventKind::ApplyImpact
/// );
/// ```
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct ApplyImpact<R: BattleRules> {
    #[cfg_attr(
        feature = "serialization",
        serde(bound(
            serialize = "Impact<R>: Serialize",
            deserialize = "Impact<R>: Deserialize<'de>"
        ))
    )]
    impact: Impact<R>,
}

impl<R: BattleRules> ApplyImpact<R> {
    /// Returns a trigger for this event.
    pub fn trigger<'a, P: EventProcessor<R>>(
        processor: &'a mut P,
        impact: Impact<R>,
    ) -> ApplyImpactTrigger<'a, R, P> {
        ApplyImpactTrigger { processor, impact }
    }

    /// Returns the impact inside this event.
    pub fn impact(&self) -> &Impact<R> {
        &self.impact
    }
}

impl<R: BattleRules> std::fmt::Debug for ApplyImpact<R> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "ApplyImpact {{ impact: {:?} }}", self.impact)
    }
}

impl<R: BattleRules> Clone for ApplyImpact<R> {
    fn clone(&self) -> Self {
        Self {
            impact: self.impact.clone(),
        }
    }
}

impl<R: BattleRules + 'static> Event<R> for ApplyImpact<R> {
    fn verify(&self, _: &Battle<R>) -> WeaselResult<(), R> {
        // For simplicity, don't verify an impact.
        // Trust the server to generate *processable* impacts.
        // `apply` should take care of generating correct events in all cases.
        Ok(())
    }

    fn apply(&self, battle: &mut Battle<R>, event_queue: &mut Option<EventQueue<R>>) {
        battle.rules.fight_rules().apply_impact(
            &battle.state,
            &self.impact,
            event_queue,
            &mut battle.entropy,
            &mut battle.metrics.write_handle(),
        );
    }

    fn kind(&self) -> EventKind {
        EventKind::ApplyImpact
    }

    fn box_clone(&self) -> Box<dyn Event<R> + Send> {
        Box::new(self.clone())
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

/// Trigger to build and fire an `ApplyImpact` event.
pub struct ApplyImpactTrigger<'a, R, P>
where
    R: BattleRules,
    P: EventProcessor<R>,
{
    processor: &'a mut P,
    impact: Impact<R>,
}

impl<'a, R, P> EventTrigger<'a, R, P> for ApplyImpactTrigger<'a, R, P>
where
    R: BattleRules + 'static,
    P: EventProcessor<R>,
{
    fn processor(&'a mut self) -> &'a mut P {
        self.processor
    }

    /// Returns an `ApplyImpact` event.
    fn event(&self) -> Box<dyn Event<R> + Send> {
        Box::new(ApplyImpact {
            impact: self.impact.clone(),
        })
    }
}