veloren_common/states/
transform.rs

1use std::time::Duration;
2
3use common_assets::AssetExt;
4use rand::thread_rng;
5use serde::{Deserialize, Serialize};
6use tracing::error;
7
8use crate::{
9    comp::{CharacterState, StateUpdate, item::Reagent},
10    event::TransformEvent,
11    generation::{EntityConfig, EntityInfo},
12    states::utils::{end_ability, tick_attack_or_default},
13};
14
15use super::{
16    behavior::CharacterBehavior,
17    utils::{AbilityInfo, StageSection},
18};
19
20#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
21pub enum FrontendSpecifier {
22    Evolve,
23    Cursekeeper,
24}
25
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27pub struct StaticData {
28    /// How long until state has until transformation
29    pub buildup_duration: Duration,
30    /// How long the state has until exiting
31    pub recover_duration: Duration,
32    /// The entity configuration you will be transformed into
33    pub target: String,
34    pub ability_info: AbilityInfo,
35    /// Whether players are allowed to transform
36    pub allow_players: bool,
37    /// Used to specify the transformation to the frontend
38    pub specifier: Option<FrontendSpecifier>,
39}
40
41#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
42pub struct Data {
43    /// Struct containing data that does not change over the course of the
44    /// character state
45    pub static_data: StaticData,
46    /// Timer for each stage
47    pub timer: Duration,
48    /// What section the character stage is in
49    pub stage_section: StageSection,
50}
51
52impl CharacterBehavior for Data {
53    fn behavior(
54        &self,
55        data: &super::behavior::JoinData,
56        output_events: &mut crate::comp::character_state::OutputEvents,
57    ) -> crate::comp::StateUpdate {
58        let mut update = StateUpdate::from(data);
59        match self.stage_section {
60            StageSection::Buildup => {
61                // Tick the timer as long as buildup hasn't finihsed
62                if self.timer < self.static_data.buildup_duration {
63                    update.character = CharacterState::Transform(Data {
64                        static_data: self.static_data.clone(),
65                        timer: tick_attack_or_default(data, self.timer, None),
66                        ..*self
67                    });
68                // Buildup finished, start transformation
69                } else {
70                    let Ok(entity_config) = EntityConfig::load(&self.static_data.target) else {
71                        error!(?self.static_data.target, "Failed to load entity configuration");
72                        end_ability(data, &mut update);
73                        return update;
74                    };
75
76                    let entity_info = EntityInfo::at(data.pos.0).with_entity_config(
77                        entity_config.read().clone(),
78                        Some(&self.static_data.target),
79                        &mut thread_rng(),
80                        None,
81                    );
82
83                    // Handle frontend events
84                    if let Some(specifier) = self.static_data.specifier {
85                        match specifier {
86                            FrontendSpecifier::Evolve => {
87                                output_events.emit_local(crate::event::LocalEvent::CreateOutcome(
88                                    crate::outcome::Outcome::Explosion {
89                                        pos: data.pos.0,
90                                        power: 5.0,
91                                        radius: 2.0,
92                                        is_attack: false,
93                                        reagent: Some(Reagent::White),
94                                    },
95                                ))
96                            },
97                            FrontendSpecifier::Cursekeeper => {
98                                output_events.emit_local(crate::event::LocalEvent::CreateOutcome(
99                                    crate::outcome::Outcome::Explosion {
100                                        pos: data.pos.0,
101                                        power: 5.0,
102                                        radius: 2.0,
103                                        is_attack: false,
104                                        reagent: Some(Reagent::Purple),
105                                    },
106                                ))
107                            },
108                        }
109                    }
110
111                    output_events.emit_server(TransformEvent {
112                        target_entity: *data.uid,
113                        entity_info,
114                        allow_players: self.static_data.allow_players,
115                        delete_on_failure: false,
116                    });
117                    update.character = CharacterState::Transform(Data {
118                        static_data: self.static_data.clone(),
119                        timer: Duration::default(),
120                        stage_section: StageSection::Recover,
121                    });
122                }
123            },
124            StageSection::Recover => {
125                // Wait for recovery period to finish
126                if self.timer < self.static_data.recover_duration {
127                    update.character = CharacterState::Transform(Data {
128                        static_data: self.static_data.clone(),
129                        timer: tick_attack_or_default(
130                            data,
131                            self.timer,
132                            Some(data.stats.recovery_speed_modifier),
133                        ),
134                        ..*self
135                    });
136                } else {
137                    // End the ability after recovery is done
138                    end_ability(data, &mut update);
139                }
140            },
141            _ => {
142                // If we somehow ended up in an incorrect character state, end the ability
143                end_ability(data, &mut update);
144            },
145        }
146
147        update
148    }
149}