Primary input now activated from control actions.

Moved a lot of key_state to a HashSet so that it is handled automatically.
This commit is contained in:
Sam 2021-03-05 01:09:56 -05:00
parent b0a41704da
commit c6d8daaae3
12 changed files with 488 additions and 367 deletions

View File

@ -26,7 +26,7 @@ use common::{
invite::{InviteKind, InviteResponse},
skills::Skill,
slot::Slot,
ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputKind,
InventoryAction, InventoryEvent, InventoryUpdateEvent,
},
event::{EventBus, LocalEvent},
@ -60,7 +60,7 @@ use num::traits::FloatConst;
use rayon::prelude::*;
use specs::Component;
use std::{
collections::VecDeque,
collections::{BTreeSet, VecDeque},
sync::Arc,
time::{Duration, Instant},
};
@ -991,6 +991,17 @@ impl Client {
}
}
pub fn handle_input(&mut self, input: InputKind, pressed: bool) {
if pressed {
self.control_action(ControlAction::StartInput {
ability: input,
target: None,
});
} else {
self.control_action(ControlAction::CancelInput);
}
}
fn control_action(&mut self, control_action: ControlAction) {
if let Some(controller) = self
.state
@ -1133,6 +1144,7 @@ impl Client {
entry
.or_insert_with(|| Controller {
inputs: inputs.clone(),
queued_inputs: BTreeSet::new(),
events: Vec::new(),
actions: Vec::new(),
})

View File

@ -1,13 +1,13 @@
use crate::{
combat::Attack,
comp::{Energy, Ori, Pos, Vel},
comp::{Energy, InputKind, Ori, Pos, Vel},
event::{LocalEvent, ServerEvent},
states::{behavior::JoinData, *},
};
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, VecStorage};
use specs_idvs::IdvStorage;
use std::collections::VecDeque;
use std::collections::{BTreeSet, VecDeque};
/// Data returned from character behavior fn's to Character Behavior System.
pub struct StateUpdate {
@ -17,6 +17,7 @@ pub struct StateUpdate {
pub ori: Ori,
pub energy: Energy,
pub swap_equipped_weapons: bool,
pub queued_inputs: BTreeSet<InputKind>,
pub local_events: VecDeque<LocalEvent>,
pub server_events: VecDeque<ServerEvent>,
}
@ -30,6 +31,7 @@ impl From<&JoinData<'_>> for StateUpdate {
energy: *data.energy,
swap_equipped_weapons: false,
character: data.character.clone(),
queued_inputs: BTreeSet::new(),
local_events: VecDeque::new(),
server_events: VecDeque::new(),
}

View File

@ -11,7 +11,7 @@ use crate::{
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::time::Duration;
use std::{collections::BTreeSet, time::Duration};
use vek::*;
/// Default duration before an input is considered 'held'.
@ -111,6 +111,24 @@ pub enum ControlAction {
Sneak,
Stand,
Talk,
StartInput {
ability: InputKind,
target: Option<Uid>,
},
CancelInput,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Ord, PartialOrd)]
#[repr(u32)]
pub enum InputKind {
Primary = 0,
/* Secondary = 1,
* Ability(usize) = 2,
* Jump = 3,
* Roll = 4,
* Glide = 5,
* Fly = 6,
* WallLeap = 7, */
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -225,7 +243,7 @@ pub enum Climb {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ControllerInputs {
pub primary: Input,
//pub primary: Input,
pub secondary: Input,
pub ability3: Input,
pub ability4: Input,
@ -245,6 +263,7 @@ pub struct ControllerInputs {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Controller {
pub inputs: ControllerInputs,
pub queued_inputs: BTreeSet<InputKind>,
// TODO: consider SmallVec
pub events: Vec<ControlEvent>,
pub actions: Vec<ControlAction>,
@ -253,7 +272,7 @@ pub struct Controller {
impl ControllerInputs {
/// Updates all inputs, accounting for delta time
pub fn tick(&mut self, dt: Duration) {
self.primary.tick(dt);
//self.primary.tick(dt);
self.secondary.tick(dt);
self.ability3.tick(dt);
self.ability4.tick(dt);
@ -266,7 +285,7 @@ impl ControllerInputs {
}
pub fn tick_freshness(&mut self) {
self.primary.tick_freshness();
//self.primary.tick_freshness();
self.secondary.tick_freshness();
self.ability3.tick_freshness();
self.ability4.tick_freshness();
@ -280,7 +299,7 @@ impl ControllerInputs {
/// Updates Controller inputs with new version received from the client
pub fn update_with_new(&mut self, new: Self) {
self.primary.update_with_new(new.primary);
//self.primary.update_with_new(new.primary);
self.secondary.update_with_new(new.secondary);
self.ability3.update_with_new(new.ability3);
self.ability4.update_with_new(new.ability4);
@ -297,10 +316,8 @@ impl ControllerInputs {
}
pub fn holding_ability_key(&self) -> bool {
self.primary.is_pressed()
|| self.secondary.is_pressed()
|| self.ability3.is_pressed()
|| self.ability4.is_pressed()
//self.primary.is_pressed() ||
self.secondary.is_pressed() || self.ability3.is_pressed() || self.ability4.is_pressed()
}
}

View File

@ -62,7 +62,7 @@ pub use self::{
combo::Combo,
controller::{
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting,
InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting,
},
energy::{Energy, EnergyChange, EnergySource},
group::Group,

View File

@ -17,9 +17,7 @@ impl CharacterBehavior for Data {
handle_move(&data, &mut update, 0.4);
if !data.physics.on_ground
|| !(data.inputs.secondary.is_pressed() || data.inputs.primary.is_pressed())
{
if !data.physics.on_ground || !data.inputs.secondary.is_pressed() {
attempt_wield(data, &mut update);
}
update

View File

@ -1,8 +1,8 @@
use crate::{
comp::{
item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction, Controller,
ControllerInputs, Energy, Health, Inventory, InventoryAction, Melee, Ori, PhysicsState,
Pos, StateUpdate, Stats, Vel,
ControllerInputs, Energy, Health, InputKind, Inventory, InventoryAction, Melee, Ori,
PhysicsState, Pos, StateUpdate, Stats, Vel,
},
resources::DeltaTime,
uid::Uid,
@ -29,6 +29,12 @@ pub trait CharacterBehavior {
fn sneak(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
fn stand(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
fn talk(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
fn handle_input(&self, data: &JoinData, input: InputKind, _target: Option<Uid>) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.queued_inputs.insert(input);
update
}
fn cancel_input(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
fn handle_event(&self, data: &JoinData, event: ControlAction) -> StateUpdate {
match event {
ControlAction::SwapEquippedWeapons => self.swap_equipped_weapons(data),
@ -41,6 +47,10 @@ pub trait CharacterBehavior {
ControlAction::Sneak => self.sneak(data),
ControlAction::Stand => self.stand(data),
ControlAction::Talk => self.talk(data),
ControlAction::StartInput { ability, target } => {
self.handle_input(data, ability, target)
},
ControlAction::CancelInput => self.cancel_input(data),
}
}
// fn init(data: &JoinData) -> CharacterState;

View File

@ -5,7 +5,7 @@ use crate::{
item::{Hands, ItemKind, Tool, ToolKind},
quadruped_low, quadruped_medium, quadruped_small,
skills::Skill,
theropod, Body, CharacterAbility, CharacterState, InventoryAction, StateUpdate,
theropod, Body, CharacterAbility, CharacterState, InputKind, InventoryAction, StateUpdate,
},
consts::{FRIC_GROUND, GRAVITY},
event::{LocalEvent, ServerEvent},
@ -314,8 +314,10 @@ fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
/// First checks whether `primary`, `secondary`, `ability3`, or `ability4` input
/// is pressed, then attempts to go into Equipping state, otherwise Idle
pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.primary.is_pressed()
|| data.inputs.secondary.is_pressed()
if
/*data.inputs.primary.is_pressed()
|| */
data.inputs.secondary.is_pressed()
|| data.inputs.ability3.is_pressed()
|| data.inputs.ability4.is_pressed()
{
@ -489,11 +491,17 @@ fn handle_ability_pressed(data: &JoinData, update: &mut StateUpdate, ability_key
}
}
pub fn handle_ability1_input(data: &JoinData, update: &mut StateUpdate) {
pub fn handle_input(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
match input {
InputKind::Primary => handle_ability_pressed(data, update, AbilityKey::Mouse1),
}
}
/*pub fn handle_ability1_input(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.primary.is_pressed() {
handle_ability_pressed(data, update, AbilityKey::Mouse1);
}
}
}*/
pub fn handle_ability2_input(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.secondary.is_pressed() {
@ -595,7 +603,7 @@ pub fn get_crit_data(data: &JoinData, ai: AbilityInfo) -> (f32, f32) {
pub fn handle_interrupt(data: &JoinData, update: &mut StateUpdate, attacks_interrupt: bool) {
if attacks_interrupt {
handle_ability1_input(data, update);
//handle_ability1_input(data, update);
handle_ability2_input(data, update);
handle_ability3_input(data, update);
handle_ability4_input(data, update);
@ -605,7 +613,11 @@ pub fn handle_interrupt(data: &JoinData, update: &mut StateUpdate, attacks_inter
pub fn ability_key_is_pressed(data: &JoinData, ability_key: AbilityKey) -> bool {
match ability_key {
AbilityKey::Mouse1 => data.inputs.primary.is_pressed(),
AbilityKey::Mouse1 =>
/* data.inputs.primary.is_pressed() */
{
false
},
AbilityKey::Mouse2 => data.inputs.secondary.is_pressed(),
AbilityKey::Skill1 => data.inputs.ability3.is_pressed(),
AbilityKey::Skill2 => data.inputs.ability4.is_pressed(),

View File

@ -2,9 +2,10 @@ use super::utils::*;
use crate::{
comp::{
slot::{EquipSlot, Slot},
CharacterState, InventoryAction, StateUpdate,
CharacterState, InputKind, InventoryAction, StateUpdate,
},
states::behavior::{CharacterBehavior, JoinData},
uid::Uid,
};
pub struct Data;
@ -16,7 +17,7 @@ impl CharacterBehavior for Data {
handle_move(&data, &mut update, 1.0);
handle_jump(&data, &mut update);
handle_climb(&data, &mut update);
handle_ability1_input(&data, &mut update);
//handle_ability1_input(&data, &mut update);
handle_ability2_input(&data, &mut update);
handle_ability3_input(&data, &mut update);
handle_ability4_input(&data, &mut update);
@ -25,6 +26,18 @@ impl CharacterBehavior for Data {
update
}
fn handle_input(
&self,
data: &JoinData,
ability: InputKind,
_target: Option<Uid>,
) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_input(&data, &mut update, ability);
update
}
fn sit(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_sit(data, &mut update);

View File

@ -350,6 +350,10 @@ impl<'a> System<'a> for Sys {
local_emitter.append(&mut state_update.local_events);
server_emitter.append(&mut state_update.server_events);
join_struct
.controller
.queued_inputs
.append(&mut state_update.queued_inputs);
incorporate_update(&mut join_struct, state_update);
}
}

View File

@ -11,8 +11,8 @@ use common::{
},
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy,
Health, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Stats,
UnresolvedChatMsg, Vel,
Health, InputKind, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale,
Stats, UnresolvedChatMsg, Vel,
},
event::{Emitter, EventBus, ServerEvent},
path::TraversalConfig,
@ -1081,7 +1081,11 @@ impl<'a> AgentData<'a> {
match tactic {
Tactic::Melee => {
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
controller.inputs.move_dir = Vec2::zero();
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1129,7 +1133,11 @@ impl<'a> AgentData<'a> {
controller.inputs.ability3.set_state(true);
agent.action_timer += dt.0;
} else {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -1177,7 +1185,11 @@ impl<'a> AgentData<'a> {
controller.inputs.ability3.set_state(true);
agent.action_timer += dt.0;
} else {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -1237,7 +1249,11 @@ impl<'a> AgentData<'a> {
} else if agent.action_timer > 2.0 {
agent.action_timer = 0.0;
} else {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -1318,7 +1334,11 @@ impl<'a> AgentData<'a> {
agent.action_timer += dt.0;
} else {
controller.inputs.secondary.set_state(false);
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
}
} else {
@ -1388,7 +1408,11 @@ impl<'a> AgentData<'a> {
} else if self.energy.current() > 10 {
controller.inputs.secondary.set_state(true);
} else {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1408,7 +1432,11 @@ impl<'a> AgentData<'a> {
.try_normalized()
.unwrap_or_else(Vec2::zero)
* speed;
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
@ -1447,7 +1475,11 @@ impl<'a> AgentData<'a> {
// 2.0 is temporary correction factor to allow them to melee with their
// large hitbox
controller.inputs.move_dir = Vec2::zero();
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if self.vel.0.is_approx_zero() {
controller.inputs.ability3.set_state(true);
@ -1489,7 +1521,11 @@ impl<'a> AgentData<'a> {
if dist_sqrd < (min_attack_dist * self.scale).powi(2) && thread_rng().gen_bool(0.5)
{
controller.inputs.move_dir = Vec2::zero();
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else if dist_sqrd < (radius as f32 * min_attack_dist * self.scale).powi(2) {
controller.inputs.move_dir = (self.pos.0 - tgt_pos.0)
.xy()
@ -1547,7 +1583,11 @@ impl<'a> AgentData<'a> {
.xy()
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
@ -1598,10 +1638,15 @@ impl<'a> AgentData<'a> {
Tactic::TailSlap => {
if dist_sqrd < (1.5 * min_attack_dist * self.scale).powi(2) {
if agent.action_timer > 4.0 {
controller.inputs.primary.set_state(false);
controller.actions.push(ControlAction::CancelInput);
//controller.inputs.primary.set_state(false);
agent.action_timer = 0.0;
} else if agent.action_timer > 1.0 {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
} else {
controller.inputs.secondary.set_state(true);
@ -1639,7 +1684,11 @@ impl<'a> AgentData<'a> {
} else if dist_sqrd < (3.0 * min_attack_dist * self.scale).powi(2)
&& dist_sqrd > (2.0 * min_attack_dist * self.scale).powi(2)
{
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(-0.47 * PI)
@ -1674,7 +1723,11 @@ impl<'a> AgentData<'a> {
controller.inputs.secondary.set_state(true);
agent.action_timer += dt.0;
} else {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -1715,7 +1768,11 @@ impl<'a> AgentData<'a> {
},
) {
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
} else {
@ -1736,7 +1793,11 @@ impl<'a> AgentData<'a> {
controller.inputs.secondary.set_state(true);
agent.action_timer += dt.0;
} else if agent.action_timer < 3.0 {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
} else {
agent.action_timer = 0.0;
@ -1772,7 +1833,11 @@ impl<'a> AgentData<'a> {
.rotated_z(0.47 * PI)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
} else if agent.action_timer < 4.0 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
@ -1780,7 +1845,11 @@ impl<'a> AgentData<'a> {
.rotated_z(-0.47 * PI)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
agent.action_timer += dt.0;
} else if agent.action_timer < 6.0 {
controller.inputs.ability3.set_state(true);
@ -1811,7 +1880,11 @@ impl<'a> AgentData<'a> {
Tactic::Theropod => {
if dist_sqrd < (2.0 * min_attack_dist * self.scale).powi(2) {
controller.inputs.move_dir = Vec2::zero();
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
@ -1834,7 +1907,11 @@ impl<'a> AgentData<'a> {
},
Tactic::Turret => {
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else {
agent.target = None;
}
@ -1842,7 +1919,11 @@ impl<'a> AgentData<'a> {
Tactic::FixedTurret => {
controller.inputs.look_dir = self.ori.look_dir();
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else {
agent.target = None;
}
@ -1856,7 +1937,11 @@ impl<'a> AgentData<'a> {
.unwrap_or_default(),
);
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.inputs.primary.set_state(true);
controller.actions.push(ControlAction::StartInput {
ability: InputKind::Primary,
target: None,
});
//controller.inputs.primary.set_state(true);
} else {
agent.target = None;
}

View File

@ -10,16 +10,7 @@ pub struct KeyState {
pub swim_up: bool,
pub swim_down: bool,
pub fly: bool,
pub toggle_wield: bool,
pub toggle_glide: bool,
pub toggle_lantern: bool,
pub toggle_sit: bool,
pub toggle_sneak: bool,
pub toggle_dance: bool,
pub auto_walk: bool,
pub swap_loadout: bool,
pub respawn: bool,
pub interact: bool,
pub trade: bool,
pub analog_matrix: Vec2<f32>,
}
@ -36,16 +27,7 @@ impl Default for KeyState {
swim_up: false,
swim_down: false,
fly: false,
toggle_wield: false,
toggle_glide: false,
toggle_lantern: false,
toggle_sit: false,
toggle_sneak: false,
toggle_dance: false,
auto_walk: false,
swap_loadout: false,
respawn: false,
interact: false,
trade: false,
analog_matrix: Vec2::zero(),
}

View File

@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc, time::Duration};
use std::{cell::RefCell, collections::HashSet, rc::Rc, time::Duration};
use ordered_float::OrderedFloat;
use specs::{Join, WorldExt};
@ -10,8 +10,8 @@ use common::{
assets::AssetExt,
comp,
comp::{
inventory::slot::Slot, invite::InviteKind, ChatMsg, ChatType, InventoryUpdateEvent, Pos,
Vel,
inventory::slot::Slot, invite::InviteKind, ChatMsg, ChatType, InputKind,
InventoryUpdateEvent, Pos, Vel,
},
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
outcome::Outcome,
@ -61,6 +61,7 @@ pub struct SessionState {
hud: Hud,
key_state: KeyState,
inputs: comp::ControllerInputs,
inputs_state: HashSet<GameInput>,
selected_block: Block,
walk_forward_dir: Vec2<f32>,
walk_right_dir: Vec2<f32>,
@ -96,6 +97,7 @@ impl SessionState {
client,
key_state: KeyState::default(),
inputs: comp::ControllerInputs::default(),
inputs_state: HashSet::new(),
hud,
selected_block: Block::new(BlockKind::Misc, Rgb::broadcast(255)),
walk_forward_dir,
@ -349,313 +351,297 @@ impl PlayState for SessionState {
Event::Close => {
return PlayStateResult::Shutdown;
},
Event::InputUpdate(GameInput::Primary, state) => {
// If we can build, use LMB to break blocks, if not, use it to attack
let mut client = self.client.borrow_mut();
if state && can_build {
if let Some(select_pos) = select_pos {
client.remove_block(select_pos);
}
} else {
self.inputs.primary.set_state(state);
}
},
Event::InputUpdate(GameInput::Secondary, state) => {
self.inputs.secondary.set_state(false); // To be changed later on
let mut client = self.client.borrow_mut();
if state && can_build {
if let Some(build_pos) = build_pos {
client.place_block(build_pos, self.selected_block);
}
} else {
self.inputs.secondary.set_state(state);
}
},
Event::InputUpdate(GameInput::Roll, state) => {
let client = self.client.borrow();
if can_build {
if state {
if let Some(block) = select_pos
.and_then(|sp| client.state().terrain().get(sp).ok().copied())
{
self.selected_block = block;
}
}
} else {
self.inputs.roll.set_state(state);
}
},
Event::InputUpdate(GameInput::Respawn, state)
if state != self.key_state.respawn =>
Event::InputUpdate(input, state)
if state != self.inputs_state.contains(&input) =>
{
self.stop_auto_walk();
self.key_state.respawn = state;
if state {
self.client.borrow_mut().respawn();
if !self.inputs_state.insert(input) {
self.inputs_state.remove(&input);
}
}
Event::InputUpdate(GameInput::Jump, state) => {
self.inputs.jump.set_state(state);
},
Event::InputUpdate(GameInput::SwimUp, state) => {
self.key_state.swim_up = state;
},
Event::InputUpdate(GameInput::SwimDown, state) => {
self.key_state.swim_down = state;
},
Event::InputUpdate(GameInput::Sit, state)
if state != self.key_state.toggle_sit =>
{
self.key_state.toggle_sit = state;
if state {
self.stop_auto_walk();
self.client.borrow_mut().toggle_sit();
}
}
Event::InputUpdate(GameInput::Dance, state)
if state != self.key_state.toggle_dance =>
{
self.key_state.toggle_dance = state;
if state {
self.stop_auto_walk();
self.client.borrow_mut().toggle_dance();
}
}
Event::InputUpdate(GameInput::Sneak, state)
if state != self.key_state.toggle_sneak =>
{
self.key_state.toggle_sneak = state;
if state {
self.stop_auto_walk();
self.client.borrow_mut().toggle_sneak();
}
}
Event::InputUpdate(GameInput::MoveForward, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.up = state
},
Event::InputUpdate(GameInput::MoveBack, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.down = state
},
Event::InputUpdate(GameInput::MoveLeft, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.left = state
},
Event::InputUpdate(GameInput::MoveRight, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.right = state
},
Event::InputUpdate(GameInput::Glide, state)
if state != self.key_state.toggle_glide =>
{
self.key_state.toggle_glide = state;
if state {
self.client.borrow_mut().toggle_glide();
}
}
Event::InputUpdate(GameInput::Fly, state) => {
self.key_state.fly ^= state;
},
Event::InputUpdate(GameInput::Climb, state) => {
self.key_state.climb_up = state;
},
Event::InputUpdate(GameInput::ClimbDown, state) => {
self.key_state.climb_down = state;
},
/*Event::InputUpdate(GameInput::WallLeap, state) => {
self.inputs.wall_leap.set_state(state)
},*/
Event::InputUpdate(GameInput::ToggleWield, state)
if state != self.key_state.toggle_wield =>
{
self.key_state.toggle_wield = state;
if state {
self.client.borrow_mut().toggle_wield();
}
}
Event::InputUpdate(GameInput::SwapLoadout, state)
if state != self.key_state.swap_loadout =>
{
self.key_state.swap_loadout = state;
if state {
self.client.borrow_mut().swap_loadout();
}
}
Event::InputUpdate(GameInput::ToggleLantern, true) => {
let mut client = self.client.borrow_mut();
if client.is_lantern_enabled() {
client.disable_lantern();
} else {
client.enable_lantern();
}
},
Event::InputUpdate(GameInput::Mount, true) => {
let mut client = self.client.borrow_mut();
if client.is_mounted() {
client.unmount();
} else {
let player_pos = client
.state()
.read_storage::<comp::Pos>()
.get(client.entity())
.copied();
if let Some(player_pos) = player_pos {
// Find closest mountable entity
let closest_mountable_entity = (
&client.state().ecs().entities(),
&client.state().ecs().read_storage::<comp::Pos>(),
&client.state().ecs().read_storage::<comp::MountState>(),
)
.join()
.filter(|(entity, _, mount_state)| {
*entity != client.entity()
&& **mount_state == comp::MountState::Unmounted
})
.map(|(entity, pos, _)| {
(entity, player_pos.0.distance_squared(pos.0))
})
.filter(|(_, dist_sqr)| *dist_sqr < MAX_MOUNT_RANGE.powi(2))
.min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr));
if let Some((mountee_entity, _)) = closest_mountable_entity {
client.mount(mountee_entity);
}
}
}
},
Event::InputUpdate(GameInput::Interact, state)
if state != self.key_state.interact =>
{
self.key_state.interact = state;
if state {
if let Some(interactable) = self.interactable {
match input {
GameInput::Primary => {
// If we can build, use LMB to break blocks, if not, use it to
// attack
let mut client = self.client.borrow_mut();
match interactable {
Interactable::Block(block, pos) => {
if block.is_collectible() {
client.collect_block(pos);
}
},
Interactable::Entity(entity) => {
if client
.state()
.ecs()
.read_storage::<comp::Item>()
.get(entity)
.is_some()
{
client.pick_up(entity);
} else {
client.npc_interact(entity);
}
},
if state && can_build {
if let Some(select_pos) = select_pos {
client.remove_block(select_pos);
}
} else {
client.handle_input(InputKind::Primary, state);
//self.inputs.primary.set_state(state);
}
}
}
}
Event::InputUpdate(GameInput::Trade, state)
if state != self.key_state.trade =>
{
self.key_state.trade = state;
},
GameInput::Secondary => {
self.inputs.secondary.set_state(false); // To be changed later on
if state {
if let Some(interactable) = self.interactable {
let mut client = self.client.borrow_mut();
match interactable {
Interactable::Block(_, _) => {},
Interactable::Entity(entity) => {
if let Some(uid) =
client.state().ecs().uid_from_entity(entity)
{
let name = client
.player_list()
.get(&uid)
.map(|info| info.player_alias.clone())
.unwrap_or_else(|| format!("<entity {:?}>", uid));
let msg = global_state
.i18n
.read()
.get("hud.trade.invite_sent")
.replace("{playername}", &name);
self.hud.new_message(ChatType::Meta.chat_msg(msg));
client.send_invite(uid, InviteKind::Trade)
};
},
if state && can_build {
if let Some(build_pos) = build_pos {
client.place_block(build_pos, self.selected_block);
}
} else {
self.inputs.secondary.set_state(state);
}
}
}
}
/*Event::InputUpdate(GameInput::Charge, state) => {
self.inputs.charge.set_state(state);
},*/
Event::InputUpdate(GameInput::FreeLook, state) => {
match (global_state.settings.gameplay.free_look_behavior, state) {
(PressBehavior::Toggle, true) => {
self.free_look = !self.free_look;
self.hud.free_look(self.free_look);
},
(PressBehavior::Hold, state) => {
self.free_look = state;
self.hud.free_look(self.free_look);
GameInput::Roll => {
let client = self.client.borrow();
if can_build {
if state {
if let Some(block) = select_pos.and_then(|sp| {
client.state().terrain().get(sp).ok().copied()
}) {
self.selected_block = block;
}
}
} else {
self.inputs.roll.set_state(state);
}
},
_ => {},
};
},
Event::InputUpdate(GameInput::AutoWalk, state) => {
match (global_state.settings.gameplay.auto_walk_behavior, state) {
(PressBehavior::Toggle, true) => {
self.auto_walk = !self.auto_walk;
self.key_state.auto_walk = self.auto_walk;
self.hud.auto_walk(self.auto_walk);
GameInput::Respawn => {
self.stop_auto_walk();
if state {
self.client.borrow_mut().respawn();
}
},
(PressBehavior::Hold, state) => {
self.auto_walk = state;
self.key_state.auto_walk = self.auto_walk;
self.hud.auto_walk(self.auto_walk);
GameInput::Jump => {
self.inputs.jump.set_state(state);
},
GameInput::SwimUp => {
self.key_state.swim_up = state;
},
GameInput::SwimDown => {
self.key_state.swim_down = state;
},
GameInput::Sit => {
if state {
self.stop_auto_walk();
self.client.borrow_mut().toggle_sit();
}
},
GameInput::Dance => {
if state {
self.stop_auto_walk();
self.client.borrow_mut().toggle_dance();
}
},
GameInput::Sneak => {
if state {
self.stop_auto_walk();
self.client.borrow_mut().toggle_sneak();
}
},
GameInput::MoveForward => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.up = state
},
GameInput::MoveBack => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.down = state
},
GameInput::MoveLeft => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.left = state
},
GameInput::MoveRight => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
self.stop_auto_walk();
}
self.key_state.right = state
},
GameInput::Glide => {
if state {
self.client.borrow_mut().toggle_glide();
}
},
GameInput::Fly => {
self.key_state.fly ^= state;
},
GameInput::Climb => {
self.key_state.climb_up = state;
},
GameInput::ClimbDown => {
self.key_state.climb_down = state;
},
GameInput::ToggleWield => {
if state {
self.client.borrow_mut().toggle_wield();
}
},
GameInput::SwapLoadout => {
if state {
self.client.borrow_mut().swap_loadout();
}
},
GameInput::ToggleLantern if state => {
let mut client = self.client.borrow_mut();
if client.is_lantern_enabled() {
client.disable_lantern();
} else {
client.enable_lantern();
}
},
GameInput::Mount if state => {
let mut client = self.client.borrow_mut();
if client.is_mounted() {
client.unmount();
} else {
let player_pos = client
.state()
.read_storage::<comp::Pos>()
.get(client.entity())
.copied();
if let Some(player_pos) = player_pos {
// Find closest mountable entity
let closest_mountable_entity = (
&client.state().ecs().entities(),
&client.state().ecs().read_storage::<comp::Pos>(),
&client
.state()
.ecs()
.read_storage::<comp::MountState>(),
)
.join()
.filter(|(entity, _, mount_state)| {
*entity != client.entity()
&& **mount_state == comp::MountState::Unmounted
})
.map(|(entity, pos, _)| {
(entity, player_pos.0.distance_squared(pos.0))
})
.filter(|(_, dist_sqr)| {
*dist_sqr < MAX_MOUNT_RANGE.powi(2)
})
.min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr));
if let Some((mountee_entity, _)) = closest_mountable_entity
{
client.mount(mountee_entity);
}
}
}
},
GameInput::Interact => {
if state {
if let Some(interactable) = self.interactable {
let mut client = self.client.borrow_mut();
match interactable {
Interactable::Block(block, pos) => {
if block.is_collectible() {
client.collect_block(pos);
}
},
Interactable::Entity(entity) => {
if client
.state()
.ecs()
.read_storage::<comp::Item>()
.get(entity)
.is_some()
{
client.pick_up(entity);
} else {
client.npc_interact(entity);
}
},
}
}
}
},
GameInput::Trade => {
if state {
if let Some(interactable) = self.interactable {
let mut client = self.client.borrow_mut();
match interactable {
Interactable::Block(_, _) => {},
Interactable::Entity(entity) => {
if let Some(uid) =
client.state().ecs().uid_from_entity(entity)
{
let name = client
.player_list()
.get(&uid)
.map(|info| info.player_alias.clone())
.unwrap_or_else(|| {
format!("<entity {:?}>", uid)
});
let msg = global_state
.i18n
.read()
.get("hud.trade.invite_sent")
.replace("{playername}", &name);
self.hud
.new_message(ChatType::Meta.chat_msg(msg));
client.send_invite(uid, InviteKind::Trade)
};
},
}
}
}
},
GameInput::FreeLook => {
match (global_state.settings.gameplay.free_look_behavior, state) {
(PressBehavior::Toggle, true) => {
self.free_look = !self.free_look;
self.hud.free_look(self.free_look);
},
(PressBehavior::Hold, state) => {
self.free_look = state;
self.hud.free_look(self.free_look);
},
_ => {},
};
},
GameInput::AutoWalk => {
match (global_state.settings.gameplay.auto_walk_behavior, state) {
(PressBehavior::Toggle, true) => {
self.auto_walk = !self.auto_walk;
self.key_state.auto_walk = self.auto_walk;
self.hud.auto_walk(self.auto_walk);
},
(PressBehavior::Hold, state) => {
self.auto_walk = state;
self.key_state.auto_walk = self.auto_walk;
self.hud.auto_walk(self.auto_walk);
},
_ => {},
}
},
GameInput::CycleCamera if state => {
// Prevent accessing camera modes which aren't available in
// multiplayer unless you are an
// admin. This is an easily bypassed clientside check.
// The server should do its own filtering of which entities are sent
// to clients to prevent abuse.
let camera = self.scene.camera_mut();
camera.next_mode(self.client.borrow().is_admin());
},
GameInput::Select => {
if !state {
self.selected_entity =
self.target_entity.map(|e| (e, std::time::Instant::now()));
}
},
GameInput::AcceptGroupInvite if state => {
let mut client = self.client.borrow_mut();
if client.invite().is_some() {
client.accept_invite();
}
},
GameInput::DeclineGroupInvite if state => {
let mut client = self.client.borrow_mut();
if client.invite().is_some() {
client.decline_invite();
}
},
_ => {},
}
},
Event::InputUpdate(GameInput::CycleCamera, true) => {
// Prevent accessing camera modes which aren't available in multiplayer
// unless you are an admin. This is an easily bypassed clientside check.
// The server should do its own filtering of which entities are sent to
// clients to prevent abuse.
let camera = self.scene.camera_mut();
camera.next_mode(self.client.borrow().is_admin());
},
Event::InputUpdate(GameInput::Select, state) => {
if !state {
self.selected_entity =
self.target_entity.map(|e| (e, std::time::Instant::now()));
}
},
Event::InputUpdate(GameInput::AcceptGroupInvite, true) => {
let mut client = self.client.borrow_mut();
if client.invite().is_some() {
client.accept_invite();
}
},
Event::InputUpdate(GameInput::DeclineGroupInvite, true) => {
let mut client = self.client.borrow_mut();
if client.invite().is_some() {
client.decline_invite();
}
},
}
Event::AnalogGameInput(input) => match input {
AnalogGameInput::MovementX(v) => {
self.key_state.analog_matrix.x = v;