veloren_common/states/
transform.rs

1use std::time::Duration;
2
3use common_assets::{AssetExt, Ron};
4use rand::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) = Ron::<EntityConfig>::load(&self.static_data.target)
71                    else {
72                        error!(?self.static_data.target, "Failed to load entity configuration");
73                        end_ability(data, &mut update);
74                        return update;
75                    };
76
77                    let entity_info = EntityInfo::at(data.pos.0).with_entity_config(
78                        entity_config.read().clone().into_inner(),
79                        Some(&self.static_data.target),
80                        &mut rng(),
81                        None,
82                    );
83
84                    // Handle frontend events
85                    if let Some(specifier) = self.static_data.specifier {
86                        match specifier {
87                            FrontendSpecifier::Evolve => {
88                                output_events.emit_local(crate::event::LocalEvent::CreateOutcome(
89                                    crate::outcome::Outcome::Explosion {
90                                        pos: data.pos.0,
91                                        power: 5.0,
92                                        radius: 2.0,
93                                        is_attack: false,
94                                        reagent: Some(Reagent::White),
95                                    },
96                                ))
97                            },
98                            FrontendSpecifier::Cursekeeper => {
99                                output_events.emit_local(crate::event::LocalEvent::CreateOutcome(
100                                    crate::outcome::Outcome::Explosion {
101                                        pos: data.pos.0,
102                                        power: 5.0,
103                                        radius: 2.0,
104                                        is_attack: false,
105                                        reagent: Some(Reagent::Purple),
106                                    },
107                                ))
108                            },
109                        }
110                    }
111
112                    output_events.emit_server(TransformEvent {
113                        target_entity: *data.uid,
114                        entity_info,
115                        allow_players: self.static_data.allow_players,
116                        delete_on_failure: false,
117                    });
118                    update.character = CharacterState::Transform(Data {
119                        static_data: self.static_data.clone(),
120                        timer: Duration::default(),
121                        stage_section: StageSection::Recover,
122                    });
123                }
124            },
125            StageSection::Recover => {
126                // Wait for recovery period to finish
127                if self.timer < self.static_data.recover_duration {
128                    update.character = CharacterState::Transform(Data {
129                        static_data: self.static_data.clone(),
130                        timer: tick_attack_or_default(
131                            data,
132                            self.timer,
133                            Some(data.stats.recovery_speed_modifier),
134                        ),
135                        ..*self
136                    });
137                } else {
138                    // End the ability after recovery is done
139                    end_ability(data, &mut update);
140                }
141            },
142            _ => {
143                // If we somehow ended up in an incorrect character state, end the ability
144                end_ability(data, &mut update);
145            },
146        }
147
148        update
149    }
150}