veloren/common/src/states/basic_summon.rs

255 lines
10 KiB
Rust
Raw Normal View History

use crate::{
comp::{
self,
2021-06-06 22:54:15 +00:00
inventory::loadout_builder::{self, LoadoutBuilder},
2021-06-03 22:42:50 +00:00
Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate,
},
2021-03-27 15:47:56 +00:00
event::{LocalEvent, ServerEvent},
outcome::Outcome,
skillset_builder::{self, SkillSetBuilder},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
},
2021-06-03 22:42:50 +00:00
terrain::Block,
vol::ReadVol,
};
2021-06-03 22:42:50 +00:00
use rand::Rng;
use serde::{Deserialize, Serialize};
2021-06-03 22:42:50 +00:00
use std::{f32::consts::PI, time::Duration};
use vek::*;
/// 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,
2021-06-03 22:42:50 +00:00
/// Range of the summons relative to the summonner
pub summon_distance: (f32, f32),
/// Information about the summoned creature
pub summon_info: SummonInfo,
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo,
2021-06-03 22:42:50 +00:00
/// Duration of the summoned entity
pub duration: Option<Duration>,
}
#[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 {
2021-05-30 19:39:30 +00:00
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 SummonInfo {
body,
loadout_config,
skillset_config,
..
} = self.static_data.summon_info;
Split LodoutBuilder::build_loadout LoadoutBuilder::build_loadout is a function which has four parameters and 3 of them are Option<>, and although fourth (body) isn't Option<>, it's optional too because it is used only in some combinations of another arguments. Because these combinations produces quirky code flow, it will be better to split it to different methods. So we did following changes to remove it and rewrite code that was using it to use better methods. * Introduce LoadoutPreset as new LoadoutConfig, currently it's only used in Summon ability, because SummonInfo uses Copy and we can't specify String for specifying asset path for loadout. Everything else is rewritten to use asset path to create loadouts. * More builder methods for LoadoutBuilder. Namely: - from_default which is used in server/src/cmd.rs in "/spawn" command. - with_default_equipment, with_default_maintool to use default loadout for specific body - with_preset to use LoadoutPreset * Add new make_loadout field with `fn (loadout_builder, trading_info) -> loadout_builder` to EntityInfo which allows to lazily construct loadout without modifying LoadoutBuilder code * Fix Merchants not having trade site We had heuristic that if something has Merchant LoadoutConfig - it's merchant, which can be false, especially if we create Merchant loadout lazily As side note, we do same check for Guards and it fails too. Too fix it, we introduce new agent::Mark, which explicitly specifies kind of agent for entity * `LoadoutBuilder::build_loadout` was written in a such way that depending on main_tool you will have different loadout. Turns out it was this way only for Adlets though and this behaviour is reproduced by specifying different loadouts directly in world code.
2021-06-05 18:05:31 +00:00
let loadout = {
let loadout_builder =
LoadoutBuilder::new().with_default_maintool(&body);
// If preset is none, use default equipment
if let Some(preset) = loadout_config {
loadout_builder.with_preset(preset).build()
} else {
loadout_builder.with_default_equipment(&body).build()
}
};
Split LodoutBuilder::build_loadout LoadoutBuilder::build_loadout is a function which has four parameters and 3 of them are Option<>, and although fourth (body) isn't Option<>, it's optional too because it is used only in some combinations of another arguments. Because these combinations produces quirky code flow, it will be better to split it to different methods. So we did following changes to remove it and rewrite code that was using it to use better methods. * Introduce LoadoutPreset as new LoadoutConfig, currently it's only used in Summon ability, because SummonInfo uses Copy and we can't specify String for specifying asset path for loadout. Everything else is rewritten to use asset path to create loadouts. * More builder methods for LoadoutBuilder. Namely: - from_default which is used in server/src/cmd.rs in "/spawn" command. - with_default_equipment, with_default_maintool to use default loadout for specific body - with_preset to use LoadoutPreset * Add new make_loadout field with `fn (loadout_builder, trading_info) -> loadout_builder` to EntityInfo which allows to lazily construct loadout without modifying LoadoutBuilder code * Fix Merchants not having trade site We had heuristic that if something has Merchant LoadoutConfig - it's merchant, which can be false, especially if we create Merchant loadout lazily As side note, we do same check for Guards and it fails too. Too fix it, we introduce new agent::Mark, which explicitly specifies kind of agent for entity * `LoadoutBuilder::build_loadout` was written in a such way that depending on main_tool you will have different loadout. Turns out it was this way only for Adlets though and this behaviour is reproduced by specifying different loadouts directly in world code.
2021-06-05 18:05:31 +00:00
let skill_set = {
let skillset_builder = SkillSetBuilder::default();
if let Some(preset) = skillset_config {
skillset_builder.with_preset(preset).build()
} else {
skillset_builder.build()
}
};
Split LodoutBuilder::build_loadout LoadoutBuilder::build_loadout is a function which has four parameters and 3 of them are Option<>, and although fourth (body) isn't Option<>, it's optional too because it is used only in some combinations of another arguments. Because these combinations produces quirky code flow, it will be better to split it to different methods. So we did following changes to remove it and rewrite code that was using it to use better methods. * Introduce LoadoutPreset as new LoadoutConfig, currently it's only used in Summon ability, because SummonInfo uses Copy and we can't specify String for specifying asset path for loadout. Everything else is rewritten to use asset path to create loadouts. * More builder methods for LoadoutBuilder. Namely: - from_default which is used in server/src/cmd.rs in "/spawn" command. - with_default_equipment, with_default_maintool to use default loadout for specific body - with_preset to use LoadoutPreset * Add new make_loadout field with `fn (loadout_builder, trading_info) -> loadout_builder` to EntityInfo which allows to lazily construct loadout without modifying LoadoutBuilder code * Fix Merchants not having trade site We had heuristic that if something has Merchant LoadoutConfig - it's merchant, which can be false, especially if we create Merchant loadout lazily As side note, we do same check for Guards and it fails too. Too fix it, we introduce new agent::Mark, which explicitly specifies kind of agent for entity * `LoadoutBuilder::build_loadout` was written in a such way that depending on main_tool you will have different loadout. Turns out it was this way only for Adlets though and this behaviour is reproduced by specifying different loadouts directly in world code.
2021-06-05 18:05:31 +00:00
let stats = comp::Stats::new("Summon".to_string());
2021-06-03 22:42:50 +00:00
let health_scaling = self
.static_data
.summon_info
.health_scaling
.map(|health_scaling| comp::Health::new(body, health_scaling));
// Ray cast to check where summon should happen
let summon_frac =
self.summon_count as f32 / self.static_data.summon_amount as f32;
let length = rand::thread_rng().gen_range(
2021-06-11 01:15:58 +00:00
self.static_data.summon_distance.0..=self.static_data.summon_distance.1,
2021-06-03 22:42:50 +00:00
);
// Summon in a clockwise fashion
let ray_vector = Vec3::new(
(summon_frac * 2.0 * PI).sin() * length,
(summon_frac * 2.0 * PI).cos() * length,
data.body.eye_height(),
);
// Check for collision on the xy plane
let obstacle_xy = data
.terrain
.ray(data.pos.0, data.pos.0 + length * ray_vector)
.until(Block::is_solid)
.cast()
.0;
let collision_vector = Vec3::new(
data.pos.0.x + (summon_frac * 2.0 * PI).sin() * obstacle_xy,
data.pos.0.y + (summon_frac * 2.0 * PI).cos() * obstacle_xy,
data.pos.0.z,
);
// Check for collision in z up to 50 blocks
let obstacle_z = data
.terrain
.ray(collision_vector, collision_vector - Vec3::unit_z() * 50.0)
.until(Block::is_solid)
.cast()
.0;
// If a duration is specified, create a projectile componenent for the npc
let projectile = self.static_data.duration.map(|duration| Projectile {
hit_solid: Vec::new(),
hit_entity: Vec::new(),
time_left: duration,
owner: Some(*data.uid),
ignore_group: true,
is_sticky: false,
is_point: false,
});
2021-03-27 15:47:56 +00:00
// Send server event to create npc
update.server_events.push_front(ServerEvent::CreateNpc {
2021-06-03 22:42:50 +00:00
pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z),
stats,
skill_set,
2021-06-03 22:42:50 +00:00
health: 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,
2021-06-03 22:42:50 +00:00
projectile,
});
2021-03-27 15:47:56 +00:00
// 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 {
2021-05-30 19:39:30 +00:00
timer: tick_attack_or_default(data, self.timer, None),
summon_count: self.summon_count + 1,
..*self
});
} else {
// Cast
update.character = CharacterState::BasicSummon(Data {
2021-05-30 19:39:30 +00:00
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 {
2021-05-30 19:39:30 +00:00
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<comp::Scale>,
2021-06-03 22:42:50 +00:00
health_scaling: Option<u16>,
2021-06-06 22:54:15 +00:00
// TODO: use assets for specifying skills and loadout?
loadout_config: Option<loadout_builder::Preset>,
skillset_config: Option<skillset_builder::Preset>,
}