Added dimensional door ability for mindflayer

This commit is contained in:
Sam 2021-03-20 23:28:13 -04:00
parent f1156c9ce5
commit 30da614e89
19 changed files with 265 additions and 55 deletions

View File

@ -1 +1,5 @@
BasicBlock
Blink(
buildup_duration: 0.5,
recover_duration: 0.25,
max_range: 100.0,
)

View File

@ -999,11 +999,17 @@ impl Client {
}
}
pub fn handle_input(&mut self, input: InputKind, pressed: bool, select_pos: Option<Vec3<f32>>) {
pub fn handle_input(
&mut self,
input: InputKind,
pressed: bool,
select_pos: Option<Vec3<f32>>,
target_entity: Option<EcsEntity>,
) {
if pressed {
self.control_action(ControlAction::StartInput {
input,
target: None,
target_entity: target_entity.and_then(|e| self.state.read_component_copied(e)),
select_pos,
});
} else {
@ -1638,6 +1644,15 @@ impl Client {
impulse,
});
},
ServerGeneral::PositionUpdate(pos) => {
self.state
.ecs()
.read_resource::<EventBus<LocalEvent>>()
.emit_now(LocalEvent::PositionUpdate {
entity: self.entity(),
pos,
});
},
ServerGeneral::UpdatePendingTrade(id, trade, pricing) => {
tracing::trace!("UpdatePendingTrade {:?} {:?}", id, trade);
self.pending_trade = Some((id, trade, pricing));

View File

@ -104,6 +104,7 @@ pub enum ServerGeneral {
SetViewDistance(u32),
Outcomes(Vec<Outcome>),
Knockback(Vec3<f32>),
PositionUpdate(comp::Pos),
// Ingame related AND terrain stream
TerrainChunkUpdate {
key: Vec2<i32>,
@ -235,6 +236,7 @@ impl ServerMsg {
| ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_)
| ServerGeneral::Knockback(_)
| ServerGeneral::PositionUpdate(_)
| ServerGeneral::UpdatePendingTrade(_, _, _)
| ServerGeneral::FinishedTrade(_)
| ServerGeneral::SiteEconomy(_) => {

View File

@ -1,6 +1,6 @@
use crate::{
assets::{self, Asset},
combat::{self, CombatEffect},
combat::{self, CombatEffect, Knockback},
comp::{
aura, beam, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills,
Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate,
@ -10,7 +10,6 @@ use crate::{
utils::{AbilityInfo, StageSection},
*,
},
Knockback,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
@ -252,6 +251,11 @@ pub enum CharacterAbility {
energy_cost: f32,
specifier: beam::FrontendSpecifier,
},
Blink {
buildup_duration: f32,
recover_duration: f32,
max_range: f32,
},
}
impl Default for CharacterAbility {
@ -532,6 +536,14 @@ impl CharacterAbility {
*heal *= power;
*tick_rate *= speed;
},
Blink {
ref mut buildup_duration,
ref mut recover_duration,
..
} => {
*buildup_duration /= speed;
*recover_duration /= speed;
},
}
self
}
@ -558,7 +570,7 @@ impl CharacterAbility {
0
}
},
BasicBlock | Boost { .. } | ComboMelee { .. } => 0,
BasicBlock | Boost { .. } | ComboMelee { .. } | Blink { .. } => 0,
}
}
@ -1560,6 +1572,20 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
CharacterAbility::Blink {
buildup_duration,
recover_duration,
max_range,
} => CharacterState::Blink(blink::Data {
static_data: blink::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
max_range: *max_range,
ability_info,
},
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
}
}
}

View File

@ -93,6 +93,8 @@ pub enum CharacterState {
/// specifically for the healing beam. There was also functionality present
/// on basic beam which was unnecessary for the healing beam.
HealingBeam(healing_beam::Data),
/// A short teleport that targets either a position or entity
Blink(blink::Data),
}
impl CharacterState {

View File

@ -110,7 +110,7 @@ pub enum ControlAction {
Talk,
StartInput {
input: InputKind,
target: Option<Uid>,
target_entity: Option<Uid>,
// Some inputs need a selected position, such as mining
select_pos: Option<Vec3<f32>>,
},
@ -121,7 +121,7 @@ impl ControlAction {
pub fn basic_input(input: InputKind) -> Self {
ControlAction::StartInput {
input,
target: None,
target_entity: None,
select_pos: None,
}
}
@ -144,9 +144,10 @@ impl InputKind {
}
}
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct InputAttr {
pub select_pos: Option<Vec3<f32>>,
pub target_entity: Option<Uid>,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]

View File

@ -28,6 +28,8 @@ pub enum LocalEvent {
},
/// Applies `vel` velocity to `entity`
Boost { entity: EcsEntity, vel: Vec3<f32> },
/// Updates the position of the entity
PositionUpdate { entity: EcsEntity, pos: Pos },
}
#[allow(clippy::large_enum_variant)] // TODO: Pending review in #587
@ -161,6 +163,11 @@ pub enum ServerEvent {
pos: Vec3<i32>,
tool: Option<comp::tool::ToolKind>,
},
TeleportTo {
entity: EcsEntity,
target: Uid,
max_range: Option<f32>,
},
}
pub struct EventBus<E> {

View File

@ -34,11 +34,14 @@ pub trait CharacterBehavior {
&self,
data: &JoinData,
input: InputKind,
_target: Option<Uid>,
target_entity: Option<Uid>,
select_pos: Option<Vec3<f32>>,
) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.queued_inputs.insert(input, InputAttr { select_pos });
update.queued_inputs.insert(input, InputAttr {
select_pos,
target_entity,
});
update
}
fn cancel_input(&self, data: &JoinData, input: InputKind) -> StateUpdate {
@ -60,9 +63,9 @@ pub trait CharacterBehavior {
ControlAction::Talk => self.talk(data),
ControlAction::StartInput {
input,
target,
target_entity,
select_pos,
} => self.start_input(data, input, target, select_pos),
} => self.start_input(data, input, target_entity, select_pos),
ControlAction::CancelInput(input) => self.cancel_input(data, input),
}
}

View File

@ -0,0 +1,94 @@
use crate::{
comp::{CharacterState, StateUpdate},
event::ServerEvent,
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 {
pub buildup_duration: Duration,
pub recover_duration: Duration,
pub max_range: f32,
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,
/// 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::Blink(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Blinks to target location, defaults to 25 meters in front if no target
// provided
if let Some(input_attr) = self.static_data.ability_info.input_attr {
if let Some(target) = input_attr.target_entity {
update.server_events.push_front(ServerEvent::TeleportTo {
entity: data.entity,
target,
max_range: Some(self.static_data.max_range),
});
} else if let Some(pos) = input_attr.select_pos {
update.pos.0 = pos;
} else {
update.pos.0 += *data.inputs.look_dir * 25.0;
}
}
// Transitions to recover section of stage
update.character = CharacterState::Blink(Data {
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
});
}
},
StageSection::Recover => {
if self.timer < self.static_data.recover_duration {
// Recovery
update.character = CharacterState::Blink(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Done
update.character = CharacterState::Wielding;
}
},
_ => {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding;
},
}
update
}
}

View File

@ -4,6 +4,7 @@ pub mod basic_block;
pub mod basic_melee;
pub mod basic_ranged;
pub mod behavior;
pub mod blink;
pub mod boost;
pub mod charged_melee;
pub mod charged_ranged;

View File

@ -5,7 +5,8 @@ use crate::{
item::{Hands, ItemKind, Tool, ToolKind},
quadruped_low, quadruped_medium, quadruped_small, ship,
skills::{Skill, SwimSkill},
theropod, Body, CharacterAbility, CharacterState, InputKind, InventoryAction, StateUpdate,
theropod, Body, CharacterAbility, CharacterState, InputAttr, InputKind, InventoryAction,
StateUpdate,
},
consts::{FRIC_GROUND, GRAVITY},
event::{LocalEvent, ServerEvent},
@ -523,11 +524,10 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
})
.filter(|ability| ability.requirements_paid(data, update))
{
update.character = (
update.character = CharacterState::from((
&ability,
AbilityInfo::from_input(data, matches!(equip_slot, EquipSlot::Offhand), input),
)
.into();
));
}
}
}
@ -578,40 +578,23 @@ pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) {
})
.filter(|ability| ability.requirements_paid(data, update))
{
update.character = CharacterState::from((
&ability,
AbilityInfo::from_input(data, false, InputKind::Roll),
));
if let CharacterState::ComboMelee(c) = data.character {
update.character = (
&ability,
AbilityInfo::from_input(data, false, InputKind::Roll),
)
.into();
if let CharacterState::Roll(roll) = &mut update.character {
roll.was_combo = Some((c.static_data.ability_info.input, c.stage));
roll.was_wielded = true;
}
} else if data.character.is_wield() {
update.character = (
&ability,
AbilityInfo::from_input(data, false, InputKind::Roll),
)
.into();
if let CharacterState::Roll(roll) = &mut update.character {
roll.was_wielded = true;
}
} else if data.character.is_stealthy() {
update.character = (
&ability,
AbilityInfo::from_input(data, false, InputKind::Roll),
)
.into();
if let CharacterState::Roll(roll) = &mut update.character {
roll.was_sneak = true;
}
} else {
update.character = (
&ability,
AbilityInfo::from_input(data, false, InputKind::Roll),
)
.into();
}
}
}
@ -707,7 +690,7 @@ pub struct AbilityInfo {
pub tool: Option<ToolKind>,
pub hand: Option<HandInfo>,
pub input: InputKind,
pub select_pos: Option<Vec3<f32>>,
pub input_attr: Option<InputAttr>,
}
impl AbilityInfo {
@ -730,13 +713,7 @@ impl AbilityInfo {
tool,
hand,
input,
select_pos: data
.controller
.queued_inputs
.get(&input)
.cloned()
.unwrap_or_default()
.select_pos,
input_attr: data.controller.queued_inputs.get(&input).copied(),
}
}
}

View File

@ -302,6 +302,7 @@ impl<'a> System<'a> for Sys {
CharacterState::BasicBeam(data) => data.handle_event(&j, action),
CharacterState::BasicAura(data) => data.handle_event(&j, action),
CharacterState::HealingBeam(data) => data.handle_event(&j, action),
CharacterState::Blink(data) => data.handle_event(&j, action),
};
local_emitter.append(&mut state_update.local_events);
server_emitter.append(&mut state_update.server_events);
@ -354,6 +355,7 @@ impl<'a> System<'a> for Sys {
CharacterState::BasicBeam(data) => data.behavior(&j),
CharacterState::BasicAura(data) => data.behavior(&j),
CharacterState::HealingBeam(data) => data.behavior(&j),
CharacterState::Blink(data) => data.behavior(&j),
};
local_emitter.append(&mut state_update.local_events);

View File

@ -489,6 +489,7 @@ impl State {
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
for event in events {
let mut velocities = self.ecs.write_storage::<comp::Vel>();
let mut positions = self.ecs.write_storage::<comp::Pos>();
let physics = self.ecs.read_storage::<comp::PhysicsState>();
match event {
LocalEvent::Jump(entity, impulse) => {
@ -509,6 +510,11 @@ impl State {
vel.0 += extra_vel;
}
},
LocalEvent::PositionUpdate { entity, pos } => {
if let Some(position) = positions.get_mut(entity) {
*position = pos;
}
},
}
}
drop(guard);

View File

@ -246,7 +246,8 @@ impl<'a> System<'a> for Sys {
| CharacterState::Shockwave { .. }
| CharacterState::BasicBeam { .. }
| CharacterState::BasicAura { .. }
| CharacterState::HealingBeam { .. } => {
| CharacterState::HealingBeam { .. }
| CharacterState::Blink { .. } => {
if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0
}

View File

@ -110,6 +110,7 @@ impl Client {
| ServerGeneral::SiteEconomy(_)
| ServerGeneral::Outcomes(_)
| ServerGeneral::Knockback(_)
| ServerGeneral::PositionUpdate(_)
| ServerGeneral::UpdatePendingTrade(_, _, _)
| ServerGeneral::FinishedTrade(_) => {
self.in_game_stream.lock().unwrap().send(g)
@ -180,6 +181,7 @@ impl Client {
| ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_)
| ServerGeneral::Knockback(_)
| ServerGeneral::PositionUpdate(_)
| ServerGeneral::SiteEconomy(_)
| ServerGeneral::UpdatePendingTrade(_, _, _)
| ServerGeneral::FinishedTrade(_) => {

View File

@ -941,3 +941,25 @@ pub fn handle_combo_change(server: &Server, entity: EcsEntity, change: i32) {
}
}
}
pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_range: Option<f32>) {
let ecs = &server.state.ecs();
let mut positions = ecs.write_storage::<Pos>();
let clients = ecs.read_storage::<Client>();
let target_pos = server
.state
.ecs()
.entity_from_uid(target.into())
.and_then(|e| positions.get(e))
.copied();
if let (Some(pos), Some(target_pos)) = (positions.get_mut(entity), target_pos) {
if max_range.map_or(true, |r| pos.0.distance_squared(target_pos.0) < r.powi(2)) {
*pos = target_pos;
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::PositionUpdate(target_pos));
}
}
}
}

View File

@ -8,7 +8,7 @@ use entity_creation::{
use entity_manipulation::{
handle_aura, handle_buff, handle_combo_change, handle_damage, handle_delete, handle_destroy,
handle_energy_change, handle_explosion, handle_knockback, handle_land_on_ground, handle_poise,
handle_respawn,
handle_respawn, handle_teleport_to,
};
use group_manip::handle_group;
use information::handle_site_info;
@ -199,6 +199,11 @@ impl Server {
},
ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(&self, entity, id),
ServerEvent::MineBlock { pos, tool } => handle_mine_block(self, pos, tool),
ServerEvent::TeleportTo {
entity,
target,
max_range,
} => handle_teleport_to(&self, entity, target, max_range),
}
}

View File

@ -363,6 +363,7 @@ impl<'a> System<'a> for Sys {
tgt_pos,
read_data.bodies.get(attacker),
&read_data.dt,
&read_data,
);
}
}
@ -443,6 +444,7 @@ impl<'a> System<'a> for Sys {
tgt_pos,
read_data.bodies.get(attacker),
&read_data.dt,
&read_data,
);
// Remember this encounter if an RtSim entity
if let Some(tgt_stats) =
@ -604,6 +606,7 @@ impl<'a> AgentData<'a> {
tgt_pos,
read_data.bodies.get(target),
&read_data.dt,
&read_data,
);
} else {
agent.target = None;
@ -1177,6 +1180,7 @@ impl<'a> AgentData<'a> {
}
}
#[allow(clippy::too_many_arguments)]
fn attack(
&self,
agent: &mut Agent,
@ -1185,6 +1189,7 @@ impl<'a> AgentData<'a> {
tgt_pos: &Pos,
tgt_body: Option<&Body>,
dt: &DeltaTime,
_read_data: &ReadData,
) {
let min_attack_dist = self.body.map_or(3.0, |b| b.radius() * self.scale + 2.0);
let tactic = match self

View File

@ -390,7 +390,12 @@ impl PlayState for SessionState {
client.remove_block(select_pos.map(|e| e.floor() as i32));
}
} else {
client.handle_input(InputKind::Primary, state, select_pos);
client.handle_input(
InputKind::Primary,
state,
select_pos,
target_entity.map(|t| t.0),
);
}
},
GameInput::Secondary => {
@ -404,7 +409,12 @@ impl PlayState for SessionState {
);
}
} else {
client.handle_input(InputKind::Secondary, state, select_pos);
client.handle_input(
InputKind::Secondary,
state,
select_pos,
target_entity.map(|t| t.0),
);
}
},
GameInput::Roll => {
@ -423,7 +433,12 @@ impl PlayState for SessionState {
}
}
} else {
client.handle_input(InputKind::Roll, state, select_pos);
client.handle_input(
InputKind::Roll,
state,
select_pos,
target_entity.map(|t| t.0),
);
}
},
GameInput::Respawn => {
@ -434,7 +449,12 @@ impl PlayState for SessionState {
},
GameInput::Jump => {
let mut client = self.client.borrow_mut();
client.handle_input(InputKind::Jump, state, select_pos);
client.handle_input(
InputKind::Jump,
state,
select_pos,
target_entity.map(|t| t.0),
);
},
GameInput::SwimUp => {
self.key_state.swim_up = state;
@ -495,7 +515,12 @@ impl PlayState for SessionState {
// controller change
self.key_state.fly ^= state;
let mut client = self.client.borrow_mut();
client.handle_input(InputKind::Fly, self.key_state.fly, select_pos);
client.handle_input(
InputKind::Fly,
self.key_state.fly,
select_pos,
target_entity.map(|t| t.0),
);
},
GameInput::Climb => {
self.key_state.climb_up = state;
@ -1254,11 +1279,21 @@ impl PlayState for SessionState {
},
HudEvent::Ability3(state) => {
let mut client = self.client.borrow_mut();
client.handle_input(InputKind::Ability(0), state, select_pos);
client.handle_input(
InputKind::Ability(0),
state,
select_pos,
target_entity.map(|t| t.0),
);
},
HudEvent::Ability4(state) => {
let mut client = self.client.borrow_mut();
client.handle_input(InputKind::Ability(1), state, select_pos);
client.handle_input(
InputKind::Ability(1),
state,
select_pos,
target_entity.map(|t| t.0),
);
},
HudEvent::ChangeFOV(new_fov) => {
global_state.settings.graphics.fov = new_fov;