Merge branch 'zesterer/pick' into 'master'

Pickaxe

See merge request veloren/veloren!1959
This commit is contained in:
Joshua Barretto 2021-03-22 00:19:42 +00:00
commit 7ae4230dc8
45 changed files with 609 additions and 1385 deletions

View File

@ -0,0 +1,11 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 0.6,
swing_duration: 0.1,
recover_duration: 0.15,
base_damage: 50,
base_poise_damage: 0,
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
)

View File

@ -206,6 +206,11 @@
secondary: "common.abilities.farming.basic",
abilities: [],
),
Pick: (
primary: "common.abilities.pick.swing",
secondary: "common.abilities.pick.swing",
abilities: [],
),
Empty: (
primary: "common.abilities.empty.basic",
secondary: "common.abilities.empty.basic",

View File

@ -0,0 +1,18 @@
ItemDef(
name: "Iron Pickaxe",
description: "Strike the earth!",
kind: Tool((
kind: Pick,
hands: Two,
stats: Direct((
equip_time_secs: 0.25,
power: 0.75,
poise_strength: 0.25,
speed: 0.75,
crit_chance: 0.0,
crit_mult: 1.0,
)),
)),
quality: Low,
tags: [],
)

View File

@ -375,6 +375,15 @@
(Item("common.items.crafting_tools.sewing_set"), 0),
],
),
"pickaxe": (
("common.items.tool.pick", 1),
[
(Item("common.items.crafting_ing.wool"), 1), // TODO: Replace with plant fiber when obtainable
(Item("common.items.crafting_ing.stones"), 5), // TODO: Replace with iron ingots when obtainable
(Item("common.items.crafting_ing.twigs"), 4),
(Item("common.items.crafting_tools.craftsman_hammer"), 0),
],
),
"cloth_scraps": (
("common.items.crafting_ing.cloth_scraps", 1),
[

View File

@ -1013,7 +1013,11 @@
"voxel.weapon.tool.shovel_gold",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.2,
),
// Picks
Tool("common.items.tool.pick"): VoxTrans(
"voxel.weapon.tool.pickaxe_green-0",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.0,
),
// Other
Utility(Coins): VoxTrans(
"voxel.object.coins",
@ -1709,7 +1713,7 @@
"voxel.armor.misc.head.assa_mask-0",
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0,
),
// Bags
// Bags
Armor(Bag("RedFace")): Png (
"element.icons.item_bag_red_face",
),

View File

@ -168,7 +168,7 @@ void main() {
// reflectivity that accounts for physical surroundings like the ground
if ((material & (1u << 1u)) > 0u) {
vec3 reflect_ray_dir = reflect(cam_to_frag, f_norm);
surf_color *= dot(vec3(1.0) - abs(fract(reflect_ray_dir * 3.5) * 2.0 - 1.0) * 0.85, vec3(1));
surf_color *= dot(vec3(1.0) - abs(fract(reflect_ray_dir * 1.5) * 2.0 - 1.0) * 0.85, vec3(1));
}
vec3 emitted_light, reflected_light;

View File

@ -228,7 +228,7 @@ void main() {
// f_light = 1.0;
// if (select_pos.w > 0) */{
vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs;
f_light = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 5.0 : 1.0;
f_light = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 10.0 : 1.0;
// }
gl_Position =

View File

@ -255,8 +255,7 @@ void main() {
emitted_light = vec3(1.0);
reflected_light = vec3(1.0);
float f_select = (select_pos.w > 0 && select_pos.xyz == floor(f_pos - f_norm * 0.5)) ? 1.0 / PERSISTENT_AMBIANCE : 1.0;
max_light += get_sun_diffuse2(/*time_of_day.x, */sun_info, moon_info, f_norm, view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a * f_select/* * (shade_frac * 0.5 + light_frac * 0.5)*/, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
max_light += get_sun_diffuse2(/*time_of_day.x, */sun_info, moon_info, f_norm, view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a/* * (shade_frac * 0.5 + light_frac * 0.5)*/, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
// emitted_light *= f_light * point_shadow * max(shade_frac, MIN_SHADOW);
// reflected_light *= f_light * point_shadow * shade_frac;
@ -269,6 +268,9 @@ void main() {
vec3 glow = glow_light(f_pos) * (pow(f_glow, 6) * 5 + pow(f_glow, 1.5) * 2);
reflected_light += glow;
float f_select = (select_pos.w > 0 && select_pos.xyz == floor(f_pos - f_norm * 0.5)) ? 0.2 / PERSISTENT_AMBIANCE : 0.0;
reflected_light += f_select;
max_light += lights_at(f_pos, f_norm, view_dir, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
// float f_ao = 1.0;

View File

@ -600,7 +600,7 @@
color: None
),
"common.items.weapons.hammer.cultist_purp_2h-0": (
vox_spec: ("weapon.hammer.cult_purp-0", (-3.5, -4.5, -5.0)),
vox_spec: ("weapon.hammer.cult_purp-0", (-3.5, -4.5, -5.0)),
color: None
),
/*"Craftsman": ( //TODO This should be a 1h hammer!
@ -839,7 +839,7 @@
"common.items.weapons.tool.rake": (
vox_spec: ("weapon.tool.rake-0", (-1.0, -5.5, -4.0)),
color: None
),
),
"common.items.weapons.tool.pickaxe": (
vox_spec: ("weapon.tool.pickaxe_green-0", (-1.5, -7.5, -4.0)),
color: None
@ -876,43 +876,43 @@
"common.items.weapons.staff.phoenix": (
vox_spec: ("weapon.staff.phoenix", (-2.5, -3.5, -5.0)),
color: None
),
),
"common.items.weapons.staff.heated_arm": (
vox_spec: ("weapon.staff.heated_arm", (-2.5, -2.5, -3.0)),
color: None
),
),
"common.items.weapons.staff.aurora": (
vox_spec: ("weapon.staff.aurora", (-2.5, -2.5, -3.0)),
color: None
),
),
"common.items.weapons.staff.infused_tower": (
vox_spec: ("weapon.staff.infused_tower", (-2.5, -2.5, -3.0)),
color: None
),
),
"common.items.weapons.staff.flamethrower_0": (
vox_spec: ("weapon.staff.unstable_thrower", (-1.5, -2.5, -5.0)),
color: None
),
),
"common.items.weapons.staff.ley_seeker": (
vox_spec: ("weapon.staff.ley_seeker", (-2.5, -2.5, -3.0)),
color: None
),
),
"common.items.weapons.staff.lava_rod": (
vox_spec: ("weapon.staff.lava_rod", (-2.5, -2.5, -3.0)),
color: None
),
),
"common.items.weapons.staff.fiery_wishing_rod": (
vox_spec: ("weapon.staff.fiery_wishing_rod", (-2.5, -2.5, -3.0)),
color: None
),
),
"common.items.weapons.staff.orc_iron": (
vox_spec: ("weapon.staff.orcish_branding_iron", (-2.5, -2.5, -3.0)),
color: None
),
),
"common.items.weapons.staff.bent_fuse": (
vox_spec: ("weapon.staff.bent_fuse", (-2.0, -2.5, -5.0)),
color: None
),
),
// Healing sceptre
"common.items.weapons.sceptre.starter_sceptre": (
vox_spec: ("weapon.sceptre.wood-simple", (-1.5, -2.5, -6.0)),
@ -945,10 +945,15 @@
"common.items.weapons.sceptre.root_evil": (
vox_spec: ("weapon.sceptre.root_evil", (-1.5, -2.5, -6.0)),
color: None
),
),
"common.items.weapons.sceptre.sceptre_velorite_0": (
vox_spec: ("weapon.sceptre.ore-nature", (-2.0, -6.0, -5.0)),
color: None
),
// Picks
"common.items.tool.pick": (
vox_spec: ("weapon.tool.pickaxe_green-0", (-1.5, -7.5, -4.0)),
color: None
),
// Misc
"common.items.debug.admin_stick": (

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,7 @@ use num::traits::FloatConst;
use rayon::prelude::*;
use specs::Component;
use std::{
collections::{BTreeSet, VecDeque},
collections::{BTreeMap, VecDeque},
sync::Arc,
time::{Duration, Instant},
};
@ -915,15 +915,18 @@ impl Client {
/// and sends the `ControlAction` event that signals to do the swap.
pub fn swap_loadout(&mut self) { self.control_action(ControlAction::SwapEquippedWeapons) }
pub fn toggle_wield(&mut self) {
let is_wielding = self
.state
/// Determine whether the player is wielding, if they're even capable of
/// being in a wield state.
pub fn is_wielding(&self) -> Option<bool> {
self.state
.ecs()
.read_storage::<comp::CharacterState>()
.get(self.entity())
.map(|cs| cs.is_wield());
.map(|cs| cs.is_wield())
}
match is_wielding {
pub fn toggle_wield(&mut self) {
match self.is_wielding() {
Some(true) => self.control_action(ControlAction::Unwield),
Some(false) => self.control_action(ControlAction::Wield),
None => warn!("Can't toggle wield, client entity doesn't have a `CharacterState`"),
@ -995,11 +998,12 @@ impl Client {
}
}
pub fn handle_input(&mut self, input: InputKind, pressed: bool) {
pub fn handle_input(&mut self, input: InputKind, pressed: bool, select_pos: Option<Vec3<f32>>) {
if pressed {
self.control_action(ControlAction::StartInput {
input,
target: None,
select_pos,
});
} else {
self.control_action(ControlAction::CancelInput(input));
@ -1148,7 +1152,7 @@ impl Client {
entry
.or_insert_with(|| Controller {
inputs: inputs.clone(),
queued_inputs: BTreeSet::new(),
queued_inputs: BTreeMap::new(),
events: Vec::new(),
actions: Vec::new(),
})

View File

@ -1,13 +1,14 @@
use crate::{
combat::Attack,
comp::{Energy, InputKind, Ori, Pos, Vel},
comp::{tool::ToolKind, Energy, InputAttr, 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::{BTreeSet, VecDeque};
use std::collections::{BTreeMap, VecDeque};
use vek::*;
/// Data returned from character behavior fn's to Character Behavior System.
pub struct StateUpdate {
@ -17,7 +18,7 @@ pub struct StateUpdate {
pub ori: Ori,
pub energy: Energy,
pub swap_equipped_weapons: bool,
pub queued_inputs: BTreeSet<InputKind>,
pub queued_inputs: BTreeMap<InputKind, InputAttr>,
pub removed_inputs: Vec<InputKind>,
pub local_events: VecDeque<LocalEvent>,
pub server_events: VecDeque<ServerEvent>,
@ -32,7 +33,7 @@ impl From<&JoinData<'_>> for StateUpdate {
energy: *data.energy,
swap_equipped_weapons: false,
character: data.character.clone(),
queued_inputs: BTreeSet::new(),
queued_inputs: BTreeMap::new(),
removed_inputs: Vec::new(),
local_events: VecDeque::new(),
server_events: VecDeque::new(),
@ -192,6 +193,7 @@ pub struct Melee {
pub max_angle: f32,
pub applied: bool,
pub hit_count: u32,
pub break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
}
impl Component for Melee {

View File

@ -11,7 +11,7 @@ use crate::{
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::collections::BTreeSet;
use std::collections::BTreeMap;
use vek::*;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -111,10 +111,22 @@ pub enum ControlAction {
StartInput {
input: InputKind,
target: Option<Uid>,
// Some inputs need a selected position, such as mining
select_pos: Option<Vec3<f32>>,
},
CancelInput(InputKind),
}
impl ControlAction {
pub fn basic_input(input: InputKind) -> Self {
ControlAction::StartInput {
input,
target: None,
select_pos: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Ord, PartialOrd)]
#[repr(u32)]
pub enum InputKind {
@ -132,6 +144,11 @@ impl InputKind {
}
}
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct InputAttr {
pub select_pos: Option<Vec3<f32>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum Climb {
Up,
@ -146,12 +163,13 @@ pub struct ControllerInputs {
pub move_z: f32, /* z axis (not combined with move_dir because they may have independent
* limits) */
pub look_dir: Dir,
pub select_pos: Option<Vec3<f32>>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Controller {
pub inputs: ControllerInputs,
pub queued_inputs: BTreeSet<InputKind>,
pub queued_inputs: BTreeMap<InputKind, InputAttr>,
// TODO: consider SmallVec
pub events: Vec<ControlEvent>,
pub actions: Vec<ControlAction>,
@ -164,6 +182,7 @@ impl ControllerInputs {
self.move_dir = new.move_dir;
self.move_z = new.move_z;
self.look_dir = new.look_dir;
self.select_pos = new.select_pos;
}
}

View File

@ -25,6 +25,7 @@ use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::{
num::{NonZeroU32, NonZeroU64},
ops::Deref,
sync::Arc,
};
use vek::Rgb;
@ -298,6 +299,12 @@ impl PartialEq for Item {
}
}
impl Deref for Item {
type Target = ItemDef;
fn deref(&self) -> &Self::Target { &self.item_def }
}
impl assets::Compound for ItemDef {
fn load<S: assets_manager::source::Source>(
cache: &assets_manager::AssetCache<S>,
@ -536,10 +543,6 @@ impl Item {
}
}
pub fn is_stackable(&self) -> bool { self.item_def.is_stackable() }
pub fn is_modular(&self) -> bool { self.item_def.is_modular() }
pub fn name(&self) -> &str { &self.item_def.name }
pub fn description(&self) -> &str { &self.item_def.description }
@ -666,6 +669,14 @@ pub trait ItemDesc {
fn item_definition_id(&self) -> &str;
fn components(&self) -> &[Item];
fn tags(&self) -> &[ItemTag];
fn tool(&self) -> Option<&Tool> {
if let ItemKind::Tool(tool) = self.kind() {
Some(tool)
} else {
None
}
}
}
impl ItemDesc for Item {

View File

@ -49,6 +49,7 @@ impl TagExampleInfo for ModularComponentTag {
ToolKind::Unique(_) => "unique damage component",
ToolKind::Debug => "debug damage component",
ToolKind::Farming => "farming damage component",
ToolKind::Pick => "pickaxe head",
ToolKind::Empty => "empty damage component",
},
ModularComponentKind::Held => match self.toolkind {
@ -65,6 +66,7 @@ impl TagExampleInfo for ModularComponentTag {
ToolKind::Unique(_) => "unique held component",
ToolKind::Debug => "debug held component",
ToolKind::Farming => "farming held component",
ToolKind::Pick => "pickaxe handle",
ToolKind::Empty => "empty held component",
},
}
@ -95,6 +97,7 @@ impl TagExampleInfo for ModularComponentTag {
ToolKind::Unique(_) => "common.items.tag_examples.modular.damage.unique",
ToolKind::Debug => "common.items.tag_examples.modular.damage.debug",
ToolKind::Farming => "common.items.tag_examples.modular.damage.farming",
ToolKind::Pick => "common.items.tag_examples.modular.damage.pick",
ToolKind::Empty => "common.items.tag_examples.modular.damage.empty",
},
ModularComponentKind::Held => match self.toolkind {
@ -116,6 +119,7 @@ impl TagExampleInfo for ModularComponentTag {
ToolKind::Unique(_) => "common.items.tag_examples.modular.held.unique",
ToolKind::Debug => "common.items.tag_examples.modular.held.debug",
ToolKind::Farming => "common.items.tag_examples.modular.held.farming",
ToolKind::Pick => "common.items.tag_examples.modular.held.pick",
ToolKind::Empty => "common.items.tag_examples.modular.held.empty",
},
}

View File

@ -32,6 +32,7 @@ pub enum ToolKind {
Unique(UniqueKind),
Debug,
Farming,
Pick,
/// This is an placeholder item, it is used by non-humanoid npcs to attack
Empty,
}
@ -56,6 +57,7 @@ impl ToolKind {
ToolKind::Unique(_) => "unique",
ToolKind::Debug => "debug",
ToolKind::Farming => "farming",
ToolKind::Pick => "pickaxe",
ToolKind::Empty => "empty",
}
}

View File

@ -61,16 +61,15 @@ pub use self::{
},
combo::Combo,
controller::{
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputKind,
InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting,
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr,
InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting,
},
energy::{Energy, EnergyChange, EnergySource},
group::Group,
home_chunk::HomeChunk,
inputs::CanBuild,
inventory::{
item,
item::{Item, ItemConfig, ItemDrop},
item::{self, tool, Item, ItemConfig, ItemDrop},
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
},
last::Last,

View File

@ -149,6 +149,11 @@ pub enum ServerEvent {
entity: EcsEntity,
id: SiteId,
},
// Attempt to mine a block, turning it into an item
MineBlock {
pos: Vec3<i32>,
tool: Option<comp::tool::ToolKind>,
},
}
pub struct EventBus<E> {

View File

@ -41,6 +41,10 @@ pub enum Outcome {
uid: Uid,
combo: u32,
},
BreakBlock {
pos: Vec3<i32>,
color: Option<Rgb<u8>>,
},
}
impl Outcome {
@ -50,6 +54,7 @@ impl Outcome {
| Outcome::ProjectileShot { pos, .. }
| Outcome::Beam { pos, .. }
| Outcome::SkillPointGain { pos, .. } => Some(*pos),
Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None,
}
}

View File

@ -1,6 +1,6 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{CharacterState, Melee, StateUpdate},
comp::{tool::ToolKind, CharacterState, Melee, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
@ -122,6 +122,16 @@ impl CharacterBehavior for Data {
max_angle: self.static_data.max_angle,
applied: false,
hit_count: 0,
break_block: data
.inputs
.select_pos
.map(|p| {
(
p.map(|e| e.floor() as i32),
self.static_data.ability_info.tool,
)
})
.filter(|(_, tool)| tool == &Some(ToolKind::Pick)),
});
} else if self.timer < self.static_data.swing_duration {
// Swings

View File

@ -1,8 +1,8 @@
use crate::{
comp::{
item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction, Controller,
ControllerInputs, Energy, Health, InputKind, Inventory, InventoryAction, Melee, Ori,
PhysicsState, Pos, StateUpdate, Stats, Vel,
ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory, InventoryAction, Melee,
Ori, PhysicsState, Pos, StateUpdate, Stats, Vel,
},
resources::DeltaTime,
uid::Uid,
@ -13,6 +13,7 @@ use specs::{
DerefFlaggedStorage, Entity, LazyUpdate,
};
use specs_idvs::IdvStorage;
use vek::*;
pub trait CharacterBehavior {
fn behavior(&self, data: &JoinData) -> StateUpdate;
@ -29,9 +30,15 @@ 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 start_input(&self, data: &JoinData, input: InputKind, _target: Option<Uid>) -> StateUpdate {
fn start_input(
&self,
data: &JoinData,
input: InputKind,
_target: Option<Uid>,
select_pos: Option<Vec3<f32>>,
) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.queued_inputs.insert(input);
update.queued_inputs.insert(input, InputAttr { select_pos });
update
}
fn cancel_input(&self, data: &JoinData, input: InputKind) -> StateUpdate {
@ -51,7 +58,11 @@ pub trait CharacterBehavior {
ControlAction::Sneak => self.sneak(data),
ControlAction::Stand => self.stand(data),
ControlAction::Talk => self.talk(data),
ControlAction::StartInput { input, target } => self.start_input(data, input, target),
ControlAction::StartInput {
input,
target,
select_pos,
} => self.start_input(data, input, target, select_pos),
ControlAction::CancelInput(input) => self.cancel_input(data, input),
}
}

View File

@ -1,6 +1,6 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
comp::{tool::ToolKind, CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *},
@ -187,6 +187,16 @@ impl CharacterBehavior for Data {
max_angle: self.static_data.max_angle.to_radians(),
applied: false,
hit_count: 0,
break_block: data
.inputs
.select_pos
.map(|p| {
(
p.map(|e| e.floor() as i32),
self.static_data.ability_info.tool,
)
})
.filter(|(_, tool)| tool == &Some(ToolKind::Pick)),
});
} else if self.timer < self.static_data.swing_duration {
// Swings

View File

@ -1,6 +1,6 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{CharacterState, Melee, StateUpdate},
comp::{tool::ToolKind, CharacterState, Melee, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
@ -213,6 +213,16 @@ impl CharacterBehavior for Data {
max_angle: self.static_data.stage_data[stage_index].angle.to_radians(),
applied: false,
hit_count: 0,
break_block: data
.inputs
.select_pos
.map(|p| {
(
p.map(|e| e.floor() as i32),
self.static_data.ability_info.tool,
)
})
.filter(|(_, tool)| tool == &Some(ToolKind::Pick)),
});
}
},

View File

@ -1,6 +1,6 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
comp::{tool::ToolKind, CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
@ -164,6 +164,16 @@ impl CharacterBehavior for Data {
max_angle: self.static_data.angle.to_radians(),
applied: false,
hit_count: 0,
break_block: data
.inputs
.select_pos
.map(|p| {
(
p.map(|e| e.floor() as i32),
self.static_data.ability_info.tool,
)
})
.filter(|(_, tool)| tool == &Some(ToolKind::Pick)),
});
}
update.character = CharacterState::DashMelee(Data {

View File

@ -1,6 +1,6 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{CharacterState, Melee, StateUpdate},
comp::{tool::ToolKind, CharacterState, Melee, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *},
@ -177,6 +177,16 @@ impl CharacterBehavior for Data {
max_angle: self.static_data.max_angle.to_radians(),
applied: false,
hit_count: 0,
break_block: data
.inputs
.select_pos
.map(|p| {
(
p.map(|e| e.floor() as i32),
self.static_data.ability_info.tool,
)
})
.filter(|(_, tool)| tool == &Some(ToolKind::Pick)),
});
update.character = CharacterState::LeapMelee(Data {

View File

@ -1,6 +1,6 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
comp::{tool::ToolKind, CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
consts::GRAVITY,
states::{
behavior::{CharacterBehavior, JoinData},
@ -141,6 +141,16 @@ impl CharacterBehavior for Data {
max_angle: 180_f32.to_radians(),
applied: false,
hit_count: 0,
break_block: data
.inputs
.select_pos
.map(|p| {
(
p.map(|e| e.floor() as i32),
self.static_data.ability_info.tool,
)
})
.filter(|(_, tool)| tool == &Some(ToolKind::Pick)),
});
} else if self.timer < self.static_data.swing_duration {
if matches!(

View File

@ -356,7 +356,7 @@ fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
/// Checks if an input related to an attack is held. If one is, moves entity
/// into wielding state
pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) {
if data.controller.queued_inputs.iter().any(|i| i.is_ability()) {
if data.controller.queued_inputs.keys().any(|i| i.is_ability()) {
attempt_wield(data, update);
}
}
@ -533,7 +533,7 @@ pub fn handle_ability_input(data: &JoinData, update: &mut StateUpdate) {
if let Some(input) = data
.controller
.queued_inputs
.iter()
.keys()
.find(|i| i.is_ability())
{
handle_ability(data, update, *input);
@ -553,7 +553,7 @@ pub fn handle_input(data: &JoinData, update: &mut StateUpdate, input: InputKind)
pub fn attempt_input(data: &JoinData, update: &mut StateUpdate) {
// TODO: look into using first() when it becomes stable
if let Some(input) = data.controller.queued_inputs.iter().next() {
if let Some(input) = data.controller.queued_inputs.keys().next() {
handle_input(data, update, *input);
}
}
@ -646,7 +646,7 @@ pub fn handle_state_interrupt(data: &JoinData, update: &mut StateUpdate, attacks
}
pub fn input_is_pressed(data: &JoinData, input: InputKind) -> bool {
data.controller.queued_inputs.contains(&input)
data.controller.queued_inputs.contains_key(&input)
}
/// Determines what portion a state is in. Used in all attacks (eventually). Is
@ -702,6 +702,7 @@ pub struct AbilityInfo {
pub tool: Option<ToolKind>,
pub hand: Option<HandInfo>,
pub input: InputKind,
pub select_pos: Option<Vec3<f32>>,
}
impl AbilityInfo {
@ -720,7 +721,18 @@ impl AbilityInfo {
)
};
Self { tool, hand, input }
Self {
tool,
hand,
input,
select_pos: data
.controller
.queued_inputs
.get(&input)
.cloned()
.unwrap_or_default()
.select_pos,
}
}
}

View File

@ -1,5 +1,5 @@
use super::SpriteKind;
use crate::make_case_elim;
use crate::{comp::tool::ToolKind, make_case_elim};
use enum_iterator::IntoEnumIterator;
use hashbrown::HashMap;
use lazy_static::lazy_static;
@ -238,6 +238,16 @@ impl Block {
.unwrap_or(false)
}
/// The tool required to mine this block. For blocks that cannot be mined,
/// `None` is returned.
#[inline]
pub fn mine_tool(&self) -> Option<ToolKind> {
match self.kind() {
BlockKind::WeakRock => Some(ToolKind::Pick),
_ => self.get_sprite().and_then(|s| s.mine_tool()),
}
}
#[inline]
pub fn is_opaque(&self) -> bool { self.kind().is_filled() }

View File

@ -1,4 +1,4 @@
use crate::make_case_elim;
use crate::{comp::tool::ToolKind, make_case_elim};
use enum_iterator::IntoEnumIterator;
use hashbrown::HashMap;
use lazy_static::lazy_static;
@ -222,8 +222,8 @@ impl SpriteKind {
SpriteKind::Apple => true,
SpriteKind::Mushroom => true,
SpriteKind::CaveMushroom => true,
SpriteKind::Velorite => true,
SpriteKind::VeloriteFrag => true,
// SpriteKind::Velorite => true,
// SpriteKind::VeloriteFrag => true,
SpriteKind::Chest => true,
SpriteKind::Coconut => true,
SpriteKind::Stones => true,
@ -235,23 +235,32 @@ impl SpriteKind {
SpriteKind::Bowl => true,
SpriteKind::ChestBurried => true,
SpriteKind::Mud => true,
SpriteKind::Amethyst => true,
SpriteKind::Ruby => true,
SpriteKind::Diamond => true,
SpriteKind::Sapphire => true,
SpriteKind::Emerald => true,
SpriteKind::Topaz => true,
SpriteKind::AmethystSmall => true,
SpriteKind::TopazSmall => true,
SpriteKind::DiamondSmall => true,
SpriteKind::RubySmall => true,
SpriteKind::EmeraldSmall => true,
SpriteKind::SapphireSmall => true,
SpriteKind::Seashells => true,
_ => false,
}
}
pub fn mine_tool(&self) -> Option<ToolKind> {
match self {
SpriteKind::Velorite
| SpriteKind::VeloriteFrag
// Gems
| SpriteKind::Amethyst
| SpriteKind::Ruby
| SpriteKind::Diamond
| SpriteKind::Sapphire
| SpriteKind::Emerald
| SpriteKind::Topaz
| SpriteKind::AmethystSmall
| SpriteKind::TopazSmall
| SpriteKind::DiamondSmall
| SpriteKind::RubySmall
| SpriteKind::EmeraldSmall
| SpriteKind::SapphireSmall => Some(ToolKind::Pick),
_ => None,
}
}
pub fn has_ori(&self) -> bool {
matches!(
self,

View File

@ -63,6 +63,24 @@ impl<'a> System<'a> for Sys {
}
melee_attack.applied = true;
// Scales
let eye_pos = pos.0 + Vec3::unit_z() * body.eye_height();
let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0);
let rad = body.radius() * scale;
// Mine blocks broken by the attack
if let Some((block_pos, tool)) = melee_attack.break_block {
// Check distance to block
if eye_pos.distance_squared(block_pos.map(|e| e as f32 + 0.5))
< (rad + scale * melee_attack.range).powi(2)
{
server_emitter.emit(ServerEvent::MineBlock {
pos: block_pos,
tool,
});
}
}
// Go through all other entities
for (target, pos_b, health_b, body_b) in (
&read_data.entities,
@ -80,9 +98,7 @@ impl<'a> System<'a> for Sys {
let ori2 = Vec2::from(look_dir);
// Scales
let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0);
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
let rad = body.radius() * scale;
let rad_b = body_b.radius() * scale_b;
// Check if entity is dodging

View File

@ -1,10 +1,16 @@
use specs::{world::WorldExt, Entity as EcsEntity};
use specs::{world::WorldExt, Builder, Entity as EcsEntity};
use tracing::error;
use vek::*;
use common::{
comp::{self, agent::AgentEvent, inventory::slot::EquipSlot, item, slot::Slot, Inventory, Pos},
comp::{
self, agent::AgentEvent, inventory::slot::EquipSlot, item, slot::Slot, tool::ToolKind,
Inventory, Pos,
},
consts::MAX_MOUNT_RANGE,
outcome::Outcome,
uid::Uid,
vol::ReadVol,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
@ -254,3 +260,29 @@ fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&
_ => false,
}
}
pub fn handle_mine_block(server: &mut Server, pos: Vec3<i32>, tool: Option<ToolKind>) {
let state = server.state_mut();
if state.can_set_block(pos) {
let block = state.terrain().get(pos).ok().copied();
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) {
// Drop item if one is recoverable from the block
if let Some(item) = comp::Item::try_reclaim_from_block(block) {
state
.create_object(Default::default(), comp::object::Body::Pouch)
.with(comp::Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
.with(item)
.build();
}
state.set_block(pos, block.into_vacant());
state
.ecs()
.write_resource::<Vec<Outcome>>()
.push(Outcome::BreakBlock {
pos,
color: block.get_color(),
});
}
}
}

View File

@ -13,7 +13,8 @@ use entity_manipulation::{
use group_manip::handle_group;
use information::handle_site_info;
use interaction::{
handle_lantern, handle_mount, handle_npc_interaction, handle_possess, handle_unmount,
handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction, handle_possess,
handle_unmount,
};
use inventory_manip::handle_inventory;
use invite::{handle_invite, handle_invite_response};
@ -190,6 +191,7 @@ impl Server {
handle_combo_change(&self, entity, change)
},
ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(&self, entity, id),
ServerEvent::MineBlock { pos, tool } => handle_mine_block(self, pos, tool),
}
}

View File

@ -153,6 +153,7 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String {
| UnlockGroup(SkillGroupKind::Weapon(ToolKind::SwordSimple))
| UnlockGroup(SkillGroupKind::Weapon(ToolKind::Debug))
| UnlockGroup(SkillGroupKind::Weapon(ToolKind::Farming))
| UnlockGroup(SkillGroupKind::Weapon(ToolKind::Pick))
| UnlockGroup(SkillGroupKind::Weapon(ToolKind::Empty))
| UnlockGroup(SkillGroupKind::Weapon(ToolKind::Unique(_)))
| UnlockGroup(SkillGroupKind::General) => {
@ -298,6 +299,7 @@ pub fn skill_group_to_db_string(skill_group: comp::skills::SkillGroupKind) -> St
| Weapon(ToolKind::SwordSimple)
| Weapon(ToolKind::Debug)
| Weapon(ToolKind::Farming)
| Weapon(ToolKind::Pick)
| Weapon(ToolKind::Empty)
| Weapon(ToolKind::Unique(_)) => panic!(
"Tried to add unsupported skill group to database: {:?}",

View File

@ -233,10 +233,9 @@ impl<'a> System<'a> for Sys {
// If we need to be able to have entities (dragons maybe?) both fly and
// run/jump, we probably need to refactor to avoid resetting the controller
// every frame.
controller.actions.push(ControlAction::StartInput {
input: InputKind::Fly,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Fly));
}
let flees = alignment
@ -630,10 +629,9 @@ impl<'a> AgentData<'a> {
.1
.map_or(true, |b| b.is_some())
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Fly,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Fly));
} else {
controller
.actions
@ -1136,10 +1134,9 @@ impl<'a> AgentData<'a> {
fn jump_if(&self, controller: &mut Controller, condition: bool) {
if condition {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Jump,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Jump));
} else {
controller
.actions
@ -1259,10 +1256,9 @@ impl<'a> AgentData<'a> {
match tactic {
Tactic::Melee => {
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
controller.inputs.move_dir = Vec2::zero();
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1285,10 +1281,9 @@ impl<'a> AgentData<'a> {
&& dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
@ -1303,10 +1298,9 @@ impl<'a> AgentData<'a> {
.push(ControlAction::CancelInput(InputKind::Secondary));
agent.action_timer = 0.0;
} else if agent.action_timer > 4.0 && self.energy.current() > 10 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else if self
.stats
@ -1315,16 +1309,14 @@ impl<'a> AgentData<'a> {
&& self.energy.current() > 800
&& thread_rng().gen_bool(0.5)
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
} else {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -1347,10 +1339,9 @@ impl<'a> AgentData<'a> {
&& dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
@ -1365,10 +1356,9 @@ impl<'a> AgentData<'a> {
.push(ControlAction::CancelInput(InputKind::Secondary));
agent.action_timer = 0.0;
} else if agent.action_timer > 2.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else if self
.stats
@ -1377,16 +1367,14 @@ impl<'a> AgentData<'a> {
&& self.energy.current() > 700
&& thread_rng().gen_bool(0.9)
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
} else {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -1409,10 +1397,9 @@ impl<'a> AgentData<'a> {
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
&& agent.action_timer > 5.0
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer = 0.0;
} else {
agent.action_timer += dt.0;
@ -1428,10 +1415,9 @@ impl<'a> AgentData<'a> {
&& dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
@ -1447,18 +1433,16 @@ impl<'a> AgentData<'a> {
&& agent.action_timer < 2.0
&& self.energy.current() > 600
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
} else if agent.action_timer > 2.0 {
agent.action_timer = 0.0;
} else {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -1476,10 +1460,9 @@ impl<'a> AgentData<'a> {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
if agent.action_timer > 4.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer = 0.0;
} else {
agent.action_timer += dt.0;
@ -1495,10 +1478,9 @@ impl<'a> AgentData<'a> {
&& dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
@ -1508,10 +1490,9 @@ impl<'a> AgentData<'a> {
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& dist_sqrd < (2.0 * min_attack_dist * self.scale).powi(2)
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
@ -1536,10 +1517,9 @@ impl<'a> AgentData<'a> {
.push(ControlAction::CancelInput(InputKind::Secondary));
agent.action_timer = 0.0;
} else if agent.action_timer > 2.0 && self.energy.current() > 300 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else if self
.stats
@ -1551,19 +1531,17 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Secondary));
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
} else {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Secondary));
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
}
} else {
@ -1577,10 +1555,9 @@ impl<'a> AgentData<'a> {
&& dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1606,10 +1583,9 @@ impl<'a> AgentData<'a> {
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& dist_sqrd < (min_attack_dist * self.scale).powi(2)
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
} else if dist_sqrd < (5.0 * min_attack_dist * self.scale).powi(2) {
if agent.action_timer < 1.5 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
@ -1635,20 +1611,17 @@ impl<'a> AgentData<'a> {
&& self.energy.current() > 800
&& thread_rng().gen::<f32>() > 0.8
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
} else if self.energy.current() > 10 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1668,10 +1641,9 @@ impl<'a> AgentData<'a> {
.try_normalized()
.unwrap_or_else(Vec2::zero)
* speed;
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
@ -1683,10 +1655,9 @@ impl<'a> AgentData<'a> {
&& dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02
{
controller.actions.push(ControlAction::StartInput {
input: InputKind::Roll,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1713,17 +1684,15 @@ 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.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
//controller.inputs.primary.set_state(true);
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if self.vel.0.is_approx_zero() {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
}
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
@ -1739,10 +1708,9 @@ impl<'a> AgentData<'a> {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
if agent.action_timer > 5.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer = 0.0;
} else {
agent.action_timer += dt.0;
@ -1765,10 +1733,9 @@ 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.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} 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()
@ -1785,10 +1752,9 @@ impl<'a> AgentData<'a> {
.unwrap_or_else(Vec2::unit_y);
agent.action_timer += dt.0;
} else if agent.action_timer < circle_time as f32 + 0.5 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else if agent.action_timer < 2.0 * circle_time as f32 + 0.5 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
@ -1798,10 +1764,9 @@ impl<'a> AgentData<'a> {
.unwrap_or_else(Vec2::unit_y);
agent.action_timer += dt.0;
} else if agent.action_timer < 2.0 * circle_time as f32 + 1.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else {
agent.action_timer = 0.0;
@ -1832,10 +1797,9 @@ impl<'a> AgentData<'a> {
.xy()
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
@ -1867,10 +1831,9 @@ impl<'a> AgentData<'a> {
* speed;
agent.action_timer += dt.0;
}
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
} else {
@ -1894,16 +1857,14 @@ impl<'a> AgentData<'a> {
.push(ControlAction::CancelInput(InputKind::Primary));
agent.action_timer = 0.0;
} else if agent.action_timer > 1.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
} else {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
}
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
@ -1934,17 +1895,15 @@ impl<'a> AgentData<'a> {
Tactic::QuadLowQuick => {
if dist_sqrd < (1.5 * min_attack_dist * self.scale).powi(2) {
controller.inputs.move_dir = Vec2::zero();
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} 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.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(-0.47 * PI)
@ -1976,16 +1935,14 @@ impl<'a> AgentData<'a> {
if agent.action_timer > 5.0 {
agent.action_timer = 0.0;
} else if agent.action_timer > 2.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
@ -2011,15 +1968,13 @@ impl<'a> AgentData<'a> {
Tactic::QuadMedJump => {
if dist_sqrd < (1.5 * min_attack_dist * self.scale).powi(2) {
controller.inputs.move_dir = Vec2::zero();
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else if dist_sqrd < (5.0 * min_attack_dist * self.scale).powi(2) {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
@ -2032,10 +1987,9 @@ impl<'a> AgentData<'a> {
},
) {
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
} else {
@ -2053,16 +2007,14 @@ impl<'a> AgentData<'a> {
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
controller.inputs.move_dir = Vec2::zero();
if agent.action_timer < 2.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else if agent.action_timer < 3.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
} else {
agent.action_timer = 0.0;
@ -2090,10 +2042,9 @@ impl<'a> AgentData<'a> {
Tactic::Lavadrake | Tactic::QuadLowBeam => {
if dist_sqrd < (2.5 * min_attack_dist * self.scale).powi(2) {
controller.inputs.move_dir = Vec2::zero();
controller.actions.push(ControlAction::StartInput {
input: InputKind::Secondary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else if dist_sqrd < (7.0 * min_attack_dist * self.scale).powi(2) {
if agent.action_timer < 2.0 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
@ -2101,10 +2052,9 @@ impl<'a> AgentData<'a> {
.rotated_z(0.47 * PI)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
} else if agent.action_timer < 4.0 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
@ -2112,16 +2062,14 @@ impl<'a> AgentData<'a> {
.rotated_z(-0.47 * PI)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
} else if agent.action_timer < 6.0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
} else {
agent.action_timer = 0.0;
@ -2149,10 +2097,9 @@ 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.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
@ -2175,10 +2122,9 @@ impl<'a> AgentData<'a> {
},
Tactic::Turret => {
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else {
agent.target = None;
}
@ -2186,10 +2132,9 @@ 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.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else {
agent.target = None;
}
@ -2203,10 +2148,9 @@ impl<'a> AgentData<'a> {
.unwrap_or_default(),
);
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Primary,
target: None,
});
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else {
agent.target = None;
}

View File

@ -116,7 +116,7 @@ impl Animation for AlphaAnimation {
next.head.orientation =
Quaternion::rotation_z(0.0 + move1 * -1.5 + move2 * 2.5 + move3 * -1.0);
},
Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) => {
Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) | Some(ToolKind::Pick) => {
let (move1, move2, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0),
Some(StageSection::Swing) => (1.0, anim_time, 0.0),

View File

@ -40,7 +40,7 @@ impl Animation for EquipAnimation {
next.hand_l.position = Vec3::new(-7.0, -5.0, 17.0);
next.hand_r.position = Vec3::new(-5.0, -4.5, 14.0);
},
Some(ToolKind::Hammer) => {
Some(ToolKind::Hammer | ToolKind::Pick) => {
next.hand_l.position = Vec3::new(-5.0, -5.0, 13.0);
next.hand_r.position = Vec3::new(-3.0, -4.5, 10.0);
},

View File

@ -92,7 +92,7 @@ impl Animation for StaggeredAnimation {
* Quaternion::rotation_y(s_a.ac.4)
* Quaternion::rotation_z(s_a.ac.5);
},
Some(ToolKind::Hammer) => {
Some(ToolKind::Hammer | ToolKind::Pick) => {
next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4);

View File

@ -90,7 +90,7 @@ impl Animation for StunnedAnimation {
* Quaternion::rotation_y(s_a.ac.4)
* Quaternion::rotation_z(s_a.ac.5);
},
Some(ToolKind::Hammer) => {
Some(ToolKind::Hammer | ToolKind::Pick) => {
next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4);

View File

@ -217,7 +217,7 @@ impl Animation for WieldAnimation {
* Quaternion::rotation_y(s_a.ac.4)
* Quaternion::rotation_z(s_a.ac.5);
},
Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) => {
Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) | Some(ToolKind::Pick) => {
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(0.0);
next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2);

View File

@ -217,5 +217,6 @@ fn empty_ability_info() -> states::utils::AbilityInfo {
tool: None,
hand: None,
input: InputKind::Primary,
select_pos: None,
}
}

View File

@ -365,6 +365,10 @@ impl SfxMgr {
},
},
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => {},
Outcome::BreakBlock { pos, .. } => {
let file_ref = "voxygen.audio.sfx.footsteps.stone_step_1";
audio.play_sfx(file_ref, pos.map(|e| e as f32 + 0.5), Some(3.0));
},
}
}

View File

@ -227,6 +227,7 @@ fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc:
ToolKind::Unique(_) => "Unique",
ToolKind::Debug => "Debug",
ToolKind::Farming => "Farming Tool",
ToolKind::Pick => "Pickaxe",
ToolKind::Empty => "Empty",
};

View File

@ -880,7 +880,20 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::BasicMelee(_) => {
CharacterState::BasicMelee(s) => {
let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section {
StageSection::Buildup => {
stage_time / s.static_data.buildup_duration.as_secs_f32()
},
StageSection::Swing => {
stage_time / s.static_data.swing_duration.as_secs_f32()
},
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::character::AlphaAnimation::update_skeleton(
&target_base,
(
@ -888,9 +901,9 @@ impl FigureMgr {
second_tool_kind,
rel_vel.magnitude(),
time,
None,
Some(s.stage_section),
),
state.state_time,
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)

View File

@ -156,6 +156,17 @@ impl ParticleMgr {
}
},
Outcome::ProjectileShot { .. } => {},
Outcome::BreakBlock { pos, .. } => {
// TODO: Use color field when particle colors are a thing
self.particles.resize_with(self.particles.len() + 30, || {
Particle::new(
Duration::from_millis(100),
time,
ParticleMode::Shrapnel,
pos.map(|e| e as f32 + 0.5),
)
});
},
_ => {},
}
}

View File

@ -10,8 +10,10 @@ use common::{
assets::AssetExt,
comp,
comp::{
inventory::slot::Slot, invite::InviteKind, ChatMsg, ChatType, InputKind,
InventoryUpdateEvent, Pos, Vel,
inventory::slot::{EquipSlot, Slot},
invite::InviteKind,
item::{tool::ToolKind, ItemDesc},
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, Vel,
},
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
outcome::Outcome,
@ -296,36 +298,59 @@ impl PlayState for SessionState {
};
self.is_aiming = is_aiming;
// Check to see whether we're aiming at anything
let (build_pos, select_pos, target_entity) =
under_cursor(&self.client.borrow(), cam_pos, cam_dir);
// Throw out distance info, it will be useful in the future
self.target_entity = target_entity.map(|x| x.0);
let player_entity = self.client.borrow().entity();
let can_build = self
.client
.borrow()
.state()
.read_storage::<comp::CanBuild>()
.get(self.client.borrow().entity())
.get(player_entity)
.is_some();
let is_mining = self
.client
.borrow()
.inventories()
.get(player_entity)
.and_then(|inv| inv.equipped(EquipSlot::Mainhand))
.and_then(|item| item.tool())
.map_or(false, |tool| tool.kind == ToolKind::Pick)
&& self.client.borrow().is_wielding() == Some(true);
// Check to see whether we're aiming at anything
let (build_pos, select_pos, target_entity) =
under_cursor(&self.client.borrow(), cam_pos, cam_dir, |b| {
b.is_filled()
|| if is_mining {
b.mine_tool().is_some()
} else {
b.is_collectible()
}
});
self.inputs.select_pos = select_pos;
// Throw out distance info, it will be useful in the future
self.target_entity = target_entity.map(|x| x.0);
self.interactable = select_interactable(
&self.client.borrow(),
target_entity,
select_pos,
select_pos.map(|sp| sp.map(|e| e.floor() as i32)),
&self.scene,
|b| b.is_collectible() || (is_mining && b.mine_tool().is_some()),
);
// Only highlight interactables
// unless in build mode where select_pos highlighted
self.scene
.set_select_pos(select_pos.filter(|_| can_build).or_else(
|| match self.interactable {
self.scene.set_select_pos(
select_pos
.map(|sp| sp.map(|e| e.floor() as i32))
.filter(|_| can_build || is_mining)
.or_else(|| match self.interactable {
Some(Interactable::Block(_, block_pos)) => Some(block_pos),
_ => None,
},
));
}),
);
// Handle window events.
for event in events {
@ -351,10 +376,10 @@ impl PlayState for SessionState {
let mut client = self.client.borrow_mut();
if state && can_build {
if let Some(select_pos) = select_pos {
client.remove_block(select_pos);
client.remove_block(select_pos.map(|e| e.floor() as i32));
}
} else {
client.handle_input(InputKind::Primary, state);
client.handle_input(InputKind::Primary, state, select_pos);
}
},
GameInput::Secondary => {
@ -362,10 +387,13 @@ impl PlayState for SessionState {
if state && can_build {
if let Some(build_pos) = build_pos {
client.place_block(build_pos, self.selected_block);
client.place_block(
build_pos.map(|e| e.floor() as i32),
self.selected_block,
);
}
} else {
client.handle_input(InputKind::Secondary, state);
client.handle_input(InputKind::Secondary, state, select_pos);
}
},
GameInput::Roll => {
@ -373,13 +401,18 @@ impl PlayState for SessionState {
if can_build {
if state {
if let Some(block) = select_pos.and_then(|sp| {
client.state().terrain().get(sp).ok().copied()
client
.state()
.terrain()
.get(sp.map(|e| e.floor() as i32))
.ok()
.copied()
}) {
self.selected_block = block;
}
}
} else {
client.handle_input(InputKind::Roll, state);
client.handle_input(InputKind::Roll, state, select_pos);
}
},
GameInput::Respawn => {
@ -390,7 +423,7 @@ impl PlayState for SessionState {
},
GameInput::Jump => {
let mut client = self.client.borrow_mut();
client.handle_input(InputKind::Jump, state);
client.handle_input(InputKind::Jump, state, select_pos);
},
GameInput::SwimUp => {
self.key_state.swim_up = state;
@ -451,7 +484,7 @@ 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);
client.handle_input(InputKind::Fly, self.key_state.fly, select_pos);
},
GameInput::Climb => {
self.key_state.climb_up = state;
@ -1209,11 +1242,11 @@ impl PlayState for SessionState {
},
HudEvent::Ability3(state) => {
let mut client = self.client.borrow_mut();
client.handle_input(InputKind::Ability(0), state);
client.handle_input(InputKind::Ability(0), state, select_pos);
},
HudEvent::Ability4(state) => {
let mut client = self.client.borrow_mut();
client.handle_input(InputKind::Ability(1), state);
client.handle_input(InputKind::Ability(1), state, select_pos);
},
HudEvent::ChangeFOV(new_fov) => {
global_state.settings.graphics.fov = new_fov;
@ -1527,9 +1560,10 @@ fn under_cursor(
client: &Client,
cam_pos: Vec3<f32>,
cam_dir: Vec3<f32>,
mut hit: impl FnMut(Block) -> bool,
) -> (
Option<Vec3<i32>>,
Option<Vec3<i32>>,
Option<Vec3<f32>>,
Option<Vec3<f32>>,
Option<(specs::Entity, f32)>,
) {
span!(_guard, "under_cursor");
@ -1555,7 +1589,7 @@ fn under_cursor(
let cam_ray = terrain
.ray(cam_pos, cam_pos + cam_dir * 100.0)
.until(|block| block.is_filled() || block.is_collectible())
.until(|block| hit(*block))
.cast();
let cam_dist = cam_ray.0;
@ -1566,8 +1600,8 @@ fn under_cursor(
<= MAX_PICKUP_RANGE)
{
(
Some((cam_pos + cam_dir * (cam_dist - 0.01)).map(|e| e.floor() as i32)),
Some((cam_pos + cam_dir * (cam_dist + 0.01)).map(|e| e.floor() as i32)),
Some(cam_pos + cam_dir * (cam_dist - 0.01)),
Some(cam_pos + cam_dir * (cam_dist + 0.01)),
)
} else {
(None, None)
@ -1667,6 +1701,7 @@ fn select_interactable(
target_entity: Option<(specs::Entity, f32)>,
selected_pos: Option<Vec3<i32>>,
scene: &Scene,
mut hit: impl FnMut(Block) -> bool,
) -> Option<Interactable> {
span!(_guard, "select_interactable");
// TODO: once there are multiple distances for different types of interactions
@ -1677,7 +1712,8 @@ fn select_interactable(
.and_then(|(e, dist_to_player)| (dist_to_player < MAX_PICKUP_RANGE).then_some(Interactable::Entity(e)))
.or_else(|| selected_pos.and_then(|sp|
client.state().terrain().get(sp).ok().copied()
.filter(Block::is_collectible).map(|b| Interactable::Block(b, sp))
.filter(|b| hit(*b))
.map(|b| Interactable::Block(b, sp))
))
.or_else(|| {
let ecs = client.state().ecs();