use crate::{ comp::{ self, inventory::loadout_builder::{LoadoutBuilder, LoadoutPreset}, Behavior, BehaviorCapability, CharacterState, StateUpdate, }, event::{LocalEvent, ServerEvent}, outcome::Outcome, skillset_builder::{SkillSetBuilder, SkillSetConfig}, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, }, }; use serde::{Deserialize, Serialize}; use std::time::Duration; /// Separated out to condense update portions of character state #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct StaticData { /// How long the state builds up for pub buildup_duration: Duration, /// How long the state is casting for pub cast_duration: Duration, /// How long the state recovers for pub recover_duration: Duration, /// How many creatures the state should summon pub summon_amount: u32, /// Information about the summoned creature pub summon_info: SummonInfo, /// Miscellaneous information about the ability pub ability_info: AbilityInfo, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { /// Struct containing data that does not change over the course of the /// character state pub static_data: StaticData, /// How many creatures have been summoned pub summon_count: u32, /// Timer for each stage pub timer: Duration, /// What section the character stage is in pub stage_section: StageSection, } impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); match self.stage_section { StageSection::Buildup => { if self.timer < self.static_data.buildup_duration { // Build up update.character = CharacterState::BasicSummon(Data { timer: tick_attack_or_default(data, self.timer, None), ..*self }); } else { // Transitions to recover section of stage update.character = CharacterState::BasicSummon(Data { timer: Duration::default(), stage_section: StageSection::Cast, ..*self }); } }, StageSection::Cast => { if self.timer < self.static_data.cast_duration || self.summon_count < self.static_data.summon_amount { if self.timer > self.static_data.cast_duration * self.summon_count / self.static_data.summon_amount { let body = self.static_data.summon_info.body; let mut loadout_builder = LoadoutBuilder::new().with_default_maintool(&body); if let Some(preset) = self.static_data.summon_info.loadout_preset { loadout_builder = loadout_builder.with_preset(preset); } let loadout = loadout_builder.build(); let stats = comp::Stats::new("Summon".to_string()); let skill_set = SkillSetBuilder::build_skillset( &None, self.static_data.summon_info.skillset_preset, ) .build(); // Send server event to create npc update.server_events.push_front(ServerEvent::CreateNpc { pos: *data.pos, stats, skill_set, health: comp::Health::new( body, self.static_data.summon_info.health_scaling, ), poise: comp::Poise::new(body), loadout, body, agent: Some(comp::Agent::new( None, &body, Behavior::from(BehaviorCapability::SPEAK), true, )), alignment: comp::Alignment::Owned(*data.uid), scale: self .static_data .summon_info .scale .unwrap_or(comp::Scale(1.0)), home_chunk: None, drop_item: None, rtsim_entity: None, }); // Send local event used for frontend shenanigans update.local_events.push_front(LocalEvent::CreateOutcome( Outcome::SummonedCreature { pos: data.pos.0, body, }, )); update.character = CharacterState::BasicSummon(Data { timer: tick_attack_or_default(data, self.timer, None), summon_count: self.summon_count + 1, ..*self }); } else { // Cast update.character = CharacterState::BasicSummon(Data { timer: tick_attack_or_default(data, self.timer, None), ..*self }); } } else { // Transitions to recover section of stage update.character = CharacterState::BasicSummon(Data { timer: Duration::default(), stage_section: StageSection::Recover, ..*self }); } }, StageSection::Recover => { if self.timer < self.static_data.recover_duration { // Recovery update.character = CharacterState::BasicSummon(Data { timer: tick_attack_or_default(data, self.timer, None), ..*self }); } else { // Done update.character = CharacterState::Wielding; } }, _ => { // If it somehow ends up in an incorrect stage section update.character = CharacterState::Wielding; }, } update } } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SummonInfo { body: comp::Body, scale: Option, health_scaling: u16, // TODO: use assets for specifying skills and loadouts? loadout_preset: Option, skillset_preset: Option, }