mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'combat' of https://gitlab.com/veloren/veloren into combat
This commit is contained in:
commit
5a04e37f67
.DS_Store
assets
common/items/armor
pants
shoulder
voxygen
client/src
common/src
server/src
voxygen/src
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
@ -2,7 +2,7 @@ Item(
|
||||
name: "Blue Linen Skirt",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
kind: Belt(ClothBlue0),
|
||||
kind: Pants(ClothBlue0),
|
||||
stats: 20,
|
||||
),
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ Item(
|
||||
name: "Green Linen Skirt",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
kind: Belt(ClothGreen0),
|
||||
kind: Pants(ClothGreen0),
|
||||
stats: 20,
|
||||
),
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ Item(
|
||||
name: "Purple Linen Skirt",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
kind: Belt(ClothPurple0),
|
||||
kind: Pants(ClothPurple0),
|
||||
stats: 20,
|
||||
),
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ Item(
|
||||
name: "Blue Linen Coat",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
kind: Belt(ClothBlue0),
|
||||
kind: Shoulder(ClothBlue0),
|
||||
stats: 20,
|
||||
),
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ Item(
|
||||
name: "Green Linen Coat",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
kind: Belt(ClothGreen0),
|
||||
kind: Shoulder(ClothGreen0),
|
||||
stats: 20,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ Item(
|
||||
name: "Purple Linen Coat",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
kind: Belt(ClothPurple0),
|
||||
kind: Shoulder(ClothPurple0),
|
||||
stats: 20,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
@ -192,7 +192,7 @@ Enjoy your stay in the World of Veloren."#,
|
||||
|
||||
|
||||
// Inventory
|
||||
"hud.bag.inventory": "'s Inventory",
|
||||
"hud.bag.inventory": "'s Inventory",
|
||||
"hud.bag.stats": "'s Stats",
|
||||
"hud.bag.exp": "Exp",
|
||||
// Settings
|
||||
|
@ -74,11 +74,11 @@
|
||||
(0.0, 0.0, 0.0), (-95.0, 140.0, 0.0), 1.1,
|
||||
),
|
||||
Armor(Hand(Assassin)): VoxTrans(
|
||||
"voxel.armor.hand.assa_left",
|
||||
"voxel.armor.hand.assa_right",
|
||||
(0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Shoulder(Assassin)): VoxTrans(
|
||||
"voxel.armor.shoulder.assa_left",
|
||||
"voxel.armor.shoulder.assa_right",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
// Starting Armor - Plate
|
||||
@ -99,15 +99,15 @@
|
||||
(0.0, 0.0, 0.0), (-95.0, 140.0, 0.0), 1.1,
|
||||
),
|
||||
Armor(Hand(Plate0)): VoxTrans(
|
||||
"voxel.armor.hand.plate_left-0",
|
||||
"voxel.armor.hand.plate_right-0",
|
||||
(0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Shoulder(Plate0)): VoxTrans(
|
||||
"voxel.armor.shoulder.plate_left-0",
|
||||
"voxel.armor.shoulder.plate_right-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
//Leather0 Armor
|
||||
Armor(Chest(Leather0)): VoxTrans(
|
||||
Armor(Chest(Leather0)): VoxTrans(
|
||||
"voxel.armor.chest.leather-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
@ -124,15 +124,98 @@
|
||||
(0.0, 0.0, 0.0), (-95.0, 140.0, 0.0), 1.1,
|
||||
),
|
||||
Armor(Hand(Leather0)): VoxTrans(
|
||||
"voxel.armor.hand.leather_left-0",
|
||||
"voxel.armor.hand.leather_right-0",
|
||||
(0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Shoulder(Leather1)): VoxTrans(
|
||||
"voxel.armor.shoulder.leather_left-1",
|
||||
"voxel.armor.shoulder.leather_right-1",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Shoulder(Leather0)): VoxTrans(
|
||||
"voxel.armor.shoulder.leather_left-0",
|
||||
"voxel.armor.shoulder.leather_right-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
//Linen Cloth
|
||||
Armor(Chest(ClothBlue0)): VoxTrans(
|
||||
"voxel.armor.chest.cloth_blue-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Pants(ClothBlue0)): VoxTrans(
|
||||
"voxel.armor.pants.cloth_blue-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Belt(ClothBlue0)): VoxTrans(
|
||||
"voxel.armor.belt.cloth_blue-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.8,
|
||||
),
|
||||
Armor(Foot(ClothBlue0)): VoxTrans(
|
||||
"voxel.armor.foot.cloth_blue-0",
|
||||
(0.0, 0.0, 0.0), (-95.0, 140.0, 0.0), 1.1,
|
||||
),
|
||||
Armor(Hand(ClothBlue0)): VoxTrans(
|
||||
"voxel.armor.hand.cloth_blue_right-0",
|
||||
(0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Shoulder(ClothBlue0)): VoxTrans(
|
||||
"voxel.armor.shoulder.cloth_blue_right-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
//////////////
|
||||
Armor(Chest(ClothGreen0)): VoxTrans(
|
||||
"voxel.armor.chest.cloth_green-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Pants(ClothGreen0)): VoxTrans(
|
||||
"voxel.armor.pants.cloth_green-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Belt(ClothGreen0)): VoxTrans(
|
||||
"voxel.armor.belt.cloth_green-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.8,
|
||||
),
|
||||
Armor(Foot(ClothGreen0)): VoxTrans(
|
||||
"voxel.armor.foot.cloth_green-0",
|
||||
(0.0, 0.0, 0.0), (-95.0, 140.0, 0.0), 1.1,
|
||||
),
|
||||
Armor(Hand(ClothGreen0)): VoxTrans(
|
||||
"voxel.armor.hand.cloth_green_right-0",
|
||||
(0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Shoulder(Leather1)): VoxTrans(
|
||||
"voxel.armor.shoulder.cloth_green_right-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Shoulder(ClothGreen0)): VoxTrans(
|
||||
"voxel.armor.shoulder.cloth_green_right-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
//////
|
||||
Armor(Chest(ClothPurple0)): VoxTrans(
|
||||
"voxel.armor.chest.cloth_purple-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Pants(ClothPurple0)): VoxTrans(
|
||||
"voxel.armor.pants.cloth_purple-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Belt(ClothPurple0)): VoxTrans(
|
||||
"voxel.armor.belt.cloth_purple-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.8,
|
||||
),
|
||||
Armor(Foot(ClothPurple0)): VoxTrans(
|
||||
"voxel.armor.foot.cloth_purple-0",
|
||||
(0.0, 0.0, 0.0), (-95.0, 140.0, 0.0), 1.1,
|
||||
),
|
||||
Armor(Hand(ClothPurple0)): VoxTrans(
|
||||
"voxel.armor.hand.cloth_purple_right-0",
|
||||
(0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Shoulder(ClothPurple0)): VoxTrans(
|
||||
"voxel.armor.shoulder.cloth_purple_right-1",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
Armor(Shoulder(ClothPurple0)): VoxTrans(
|
||||
"voxel.armor.shoulder.cloth_purple_right-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
|
||||
),
|
||||
// Consumables
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -36,16 +36,16 @@
|
||||
color: None
|
||||
),
|
||||
ClothPurple0:(
|
||||
vox_spec: ("armor.belt.cloth_purple_0", (-5.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.belt.cloth_purple-0", (-5.0, -3.5, 2.0)),
|
||||
color: None
|
||||
),
|
||||
ClothBlue0:(
|
||||
vox_spec: ("armor.belt.cloth_blue_0", (-5.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.belt.cloth_blue-0", (-5.0, -3.5, 2.0)),
|
||||
color: None
|
||||
),
|
||||
ClothGreen0:(
|
||||
vox_spec: ("armor.belt.cloth_purple_0", (-5.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.belt.cloth_green-0", (-5.0, -3.5, 2.0)),
|
||||
color: None
|
||||
),
|
||||
|
||||
|
||||
})
|
||||
|
@ -44,16 +44,16 @@
|
||||
color: None
|
||||
),
|
||||
ClothPurple0:(
|
||||
vox_spec: ("armor.chest.cloth_purple-0", (-7.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.chest.cloth_purple-0", (-7.0, -3.5, 1.0)),
|
||||
color: None
|
||||
),
|
||||
ClothBlue0:(
|
||||
vox_spec: ("armor.chest.cloth_blue-0", (-7.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.chest.cloth_blue-0", (-7.0, -3.5, 1.0)),
|
||||
color: None
|
||||
),
|
||||
ClothGreen0:(
|
||||
vox_spec: ("armor.chest.cloth_green-0", (-7.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.chest.cloth_green-0", (-7.0, -3.5, 1.0)),
|
||||
color: None
|
||||
),
|
||||
|
||||
|
||||
})
|
||||
|
@ -28,15 +28,15 @@
|
||||
color: None
|
||||
),
|
||||
ClothPurple0:(
|
||||
vox_spec: ("armor.foot.cloth_purple_0", (-2.5, -3.5, -9.0)),
|
||||
vox_spec: ("armor.foot.cloth_purple-0", (-2.5, -3.5, -9.0)),
|
||||
color: None
|
||||
),
|
||||
ClothBlue0:(
|
||||
vox_spec: ("armor.foot.cloth_purple_0", (-2.5, -3.5, -9.0)),
|
||||
vox_spec: ("armor.foot.cloth_blue-0", (-2.5, -3.5, -9.0)),
|
||||
color: None
|
||||
),
|
||||
ClothGreen0:(
|
||||
vox_spec: ("armor.foot.cloth_purple_0", (-2.5, -3.5, -9.0)),
|
||||
vox_spec: ("armor.foot.cloth_green-0", (-2.5, -3.5, -9.0)),
|
||||
color: None
|
||||
),
|
||||
})
|
||||
|
@ -51,7 +51,7 @@
|
||||
),
|
||||
ClothPurple0: (
|
||||
left: (
|
||||
vox_spec: ("armor.hand.cloth_purple_left-0", (-1.5, -1.5, -7.0)),
|
||||
vox_spec: ("armor.hand.cloth_purple_right-0", (-1.5, -1.5, -7.0)),
|
||||
color: None
|
||||
),
|
||||
right: (
|
||||
@ -61,7 +61,7 @@
|
||||
),
|
||||
ClothBlue0: (
|
||||
left: (
|
||||
vox_spec: ("armor.hand.cloth_blue_left-0", (-1.5, -1.5, -7.0)),
|
||||
vox_spec: ("armor.hand.cloth_blue_right-0", (-1.5, -1.5, -7.0)),
|
||||
color: None
|
||||
),
|
||||
right: (
|
||||
@ -71,7 +71,7 @@
|
||||
),
|
||||
ClothGreen0: (
|
||||
left: (
|
||||
vox_spec: ("armor.hand.cloth_green_left-0", (-1.5, -1.5, -7.0)),
|
||||
vox_spec: ("armor.hand.cloth_green_right-0", (-1.5, -1.5, -7.0)),
|
||||
color: None
|
||||
),
|
||||
right: (
|
||||
|
@ -40,15 +40,15 @@
|
||||
color: None
|
||||
),
|
||||
ClothPurple0:(
|
||||
vox_spec: ("armor.pants.cloth_purple_0", (-5.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.pants.cloth_purple-0", (-5.0, -3.5, 0.0)),
|
||||
color: None
|
||||
),
|
||||
ClothBlue0:(
|
||||
vox_spec: ("armor.pants.cloth_blue_0", (-5.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.pants.cloth_blue-0", (-5.0, -3.5, 0.0)),
|
||||
color: None
|
||||
),
|
||||
ClothGreen0:(
|
||||
vox_spec: ("armor.pants.cloth_purple_0", (-5.0, -3.5, 2.0)),
|
||||
vox_spec: ("armor.pants.cloth_green-0", (-5.0, -3.5, 0.0)),
|
||||
color: None
|
||||
),
|
||||
})
|
||||
|
@ -82,7 +82,7 @@
|
||||
),
|
||||
ClothPurple0: (
|
||||
left: (
|
||||
vox_spec: ("armor.shoulder.cloth_purple_left-0", (-3.2, -3.5, 1.0)),
|
||||
vox_spec: ("armor.shoulder.cloth_purple_right-0", (-3.2, -3.5, 1.0)),
|
||||
color: None
|
||||
),
|
||||
right: (
|
||||
|
@ -393,7 +393,7 @@ impl Client {
|
||||
{
|
||||
if last_character_states
|
||||
.get(entity)
|
||||
.map(|l| !client_character_state.equals(&l.0))
|
||||
.map(|l| !client_character_state.same_variant(&l.0))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let _ = last_character_states
|
||||
@ -692,8 +692,7 @@ impl Client {
|
||||
self.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<SfxEventItem>>()
|
||||
.emitter()
|
||||
.emit(SfxEventItem::at_player_position(SfxEvent::Inventory(event)));
|
||||
.emit_now(SfxEventItem::at_player_position(SfxEvent::Inventory(event)));
|
||||
},
|
||||
ServerMsg::TerrainChunkUpdate { key, chunk } => {
|
||||
if let Ok(chunk) = chunk {
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
states::*,
|
||||
sys::character_behavior::JoinData,
|
||||
};
|
||||
use specs::{Component, FlaggedStorage, HashMapStorage};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -13,6 +13,8 @@ pub enum CharacterAbility {
|
||||
buildup_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
base_damage: u32,
|
||||
range: f32,
|
||||
max_angle: f32,
|
||||
},
|
||||
BasicRanged {
|
||||
recover_duration: Duration,
|
||||
@ -66,10 +68,6 @@ impl CharacterAbility {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CharacterAbility {
|
||||
type Storage = IDVStorage<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct ItemConfig {
|
||||
pub item: Item,
|
||||
@ -99,11 +97,15 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
base_damage,
|
||||
range,
|
||||
max_angle,
|
||||
} => CharacterState::BasicMelee(basic_melee::Data {
|
||||
exhausted: false,
|
||||
buildup_duration: *buildup_duration,
|
||||
recover_duration: *recover_duration,
|
||||
base_damage: *base_damage,
|
||||
range: *range,
|
||||
max_angle: *max_angle,
|
||||
}),
|
||||
CharacterAbility::BasicRanged {
|
||||
recover_duration,
|
||||
@ -133,7 +135,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
}),
|
||||
CharacterAbility::BasicBlock => CharacterState::BasicBlock,
|
||||
CharacterAbility::Roll => CharacterState::Roll(roll::Data {
|
||||
remaining_duration: Duration::from_millis(600),
|
||||
remaining_duration: Duration::from_millis(300),
|
||||
}),
|
||||
CharacterAbility::TimedCombo {
|
||||
buildup_duration,
|
||||
|
@ -111,7 +111,7 @@ impl CharacterState {
|
||||
}
|
||||
|
||||
/// Compares for shallow equality (does not check internal struct equality)
|
||||
pub fn equals(&self, other: &Self) -> bool {
|
||||
pub fn same_variant(&self, other: &Self) -> bool {
|
||||
// Check if state is the same without looking at the inner data
|
||||
std::mem::discriminant(self) == std::mem::discriminant(other)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
use rand::seq::SliceRandom;
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
use std::{fs::File, io::BufReader, time::Duration, vec::Vec};
|
||||
use std::{fs::File, io::BufReader, time::Duration};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum SwordKind {
|
||||
@ -68,28 +68,24 @@ impl ToolData {
|
||||
use ToolKind::*;
|
||||
|
||||
match self.kind {
|
||||
Sword(_) => vec![
|
||||
// BasicMelee {
|
||||
// buildup_duration: Duration::from_millis(100),
|
||||
// recover_duration: Duration::from_millis(500),
|
||||
// base_damage: 6,
|
||||
// },
|
||||
CharacterAbility::TripleStrike { base_damage: 7 },
|
||||
DashMelee {
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
recover_duration: Duration::from_millis(500),
|
||||
base_damage: 20,
|
||||
},
|
||||
],
|
||||
Sword(_) => vec![TripleStrike { base_damage: 7 }, DashMelee {
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
recover_duration: Duration::from_millis(500),
|
||||
base_damage: 20,
|
||||
}],
|
||||
Axe(_) => vec![BasicMelee {
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
recover_duration: Duration::from_millis(100),
|
||||
base_damage: 8,
|
||||
range: 3.5,
|
||||
max_angle: 30.0,
|
||||
}],
|
||||
Hammer(_) => vec![BasicMelee {
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_damage: 10,
|
||||
range: 3.5,
|
||||
max_angle: 60.0,
|
||||
}],
|
||||
Bow(_) => vec![BasicRanged {
|
||||
projectile: Projectile {
|
||||
@ -113,12 +109,16 @@ impl ToolData {
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_damage: 5,
|
||||
range: 3.5,
|
||||
max_angle: 60.0,
|
||||
}],
|
||||
Staff(_) => vec![
|
||||
BasicMelee {
|
||||
buildup_duration: Duration::from_millis(400),
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_damage: 7,
|
||||
base_damage: 3,
|
||||
range: 10.0,
|
||||
max_angle: 45.0,
|
||||
},
|
||||
BasicRanged {
|
||||
projectile: Projectile {
|
||||
@ -321,8 +321,28 @@ impl Item {
|
||||
"common.items.armor.shoulder.plate_0",
|
||||
"common.items.armor.shoulder.leather_1",
|
||||
"common.items.armor.shoulder.leather_0",
|
||||
"common.items.armor.hand.leather_0",
|
||||
"common.items.armor.hand.plate_0",
|
||||
"common.items.weapons.wood_sword",
|
||||
"common.items.weapons.short_sword_0",
|
||||
"common.items.armor.belt.cloth_blue_0",
|
||||
"common.items.armor.chest.cloth_blue_0",
|
||||
"common.items.armor.foot.cloth_blue_0",
|
||||
"common.items.armor.pants.cloth_blue_0",
|
||||
"common.items.armor.shoulder.cloth_blue_0",
|
||||
"common.items.armor.hand.cloth_blue_0",
|
||||
"common.items.armor.belt.cloth_green_0",
|
||||
"common.items.armor.chest.cloth_green_0",
|
||||
"common.items.armor.foot.cloth_green_0",
|
||||
"common.items.armor.pants.cloth_green_0",
|
||||
"common.items.armor.shoulder.cloth_green_0",
|
||||
"common.items.armor.hand.cloth_green_0",
|
||||
"common.items.armor.belt.cloth_purple_0",
|
||||
"common.items.armor.chest.cloth_purple_0",
|
||||
"common.items.armor.foot.cloth_purple_0",
|
||||
"common.items.armor.pants.cloth_purple_0",
|
||||
"common.items.armor.shoulder.cloth_purple_0",
|
||||
"common.items.armor.hand.cloth_purple_0",
|
||||
]
|
||||
.choose(&mut rand::thread_rng())
|
||||
.unwrap(), // Can't fail
|
||||
|
@ -144,7 +144,7 @@ impl<E> EventBus<E> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit(&self, event: E) { self.queue.lock().push_front(event); }
|
||||
pub fn emit_now(&self, event: E) { self.queue.lock().push_back(event); }
|
||||
|
||||
pub fn recv_all(&self) -> impl ExactSizeIterator<Item = E> {
|
||||
std::mem::replace(self.queue.lock().deref_mut(), VecDeque::new()).into_iter()
|
||||
@ -157,7 +157,7 @@ pub struct Emitter<'a, E> {
|
||||
}
|
||||
|
||||
impl<'a, E> Emitter<'a, E> {
|
||||
pub fn emit(&mut self, event: E) { self.events.push_front(event); }
|
||||
pub fn emit(&mut self, event: E) { self.events.push_back(event); }
|
||||
|
||||
pub fn append(&mut self, other: &mut VecDeque<E>) { self.events.append(other) }
|
||||
}
|
||||
|
@ -21,9 +21,7 @@ sum_type! {
|
||||
Mass(comp::Mass),
|
||||
Gravity(comp::Gravity),
|
||||
Sticky(comp::Sticky),
|
||||
CharacterAbility(comp::CharacterAbility),
|
||||
Loadout(comp::Loadout),
|
||||
Attacking(comp::Attacking),
|
||||
CharacterState(comp::CharacterState),
|
||||
Pos(comp::Pos),
|
||||
Vel(comp::Vel),
|
||||
@ -48,9 +46,7 @@ sum_type! {
|
||||
Mass(PhantomData<comp::Mass>),
|
||||
Gravity(PhantomData<comp::Gravity>),
|
||||
Sticky(PhantomData<comp::Sticky>),
|
||||
CharacterAbility(PhantomData<comp::CharacterAbility>),
|
||||
Loadout(PhantomData<comp::Loadout>),
|
||||
Attacking(PhantomData<comp::Attacking>),
|
||||
CharacterState(PhantomData<comp::CharacterState>),
|
||||
Pos(PhantomData<comp::Pos>),
|
||||
Vel(PhantomData<comp::Vel>),
|
||||
@ -75,9 +71,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::CharacterAbility(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Loadout(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Attacking(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world),
|
||||
@ -100,9 +94,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::CharacterAbility(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Loadout(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Attacking(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world),
|
||||
@ -127,11 +119,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
|
||||
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
|
||||
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
|
||||
EcsCompPhantom::CharacterAbility(_) => {
|
||||
sync::handle_remove::<comp::CharacterAbility>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Loadout(_) => sync::handle_remove::<comp::Loadout>(entity, world),
|
||||
EcsCompPhantom::Attacking(_) => sync::handle_remove::<comp::Attacking>(entity, world),
|
||||
EcsCompPhantom::CharacterState(_) => {
|
||||
sync::handle_remove::<comp::CharacterState>(entity, world)
|
||||
},
|
||||
|
@ -107,7 +107,6 @@ impl State {
|
||||
ecs.register_sync_marker();
|
||||
// Register server -> all clients synced components.
|
||||
ecs.register::<comp::Loadout>();
|
||||
ecs.register::<comp::Projectile>();
|
||||
ecs.register::<comp::Body>();
|
||||
ecs.register::<comp::Player>();
|
||||
ecs.register::<comp::Stats>();
|
||||
@ -121,7 +120,6 @@ impl State {
|
||||
ecs.register::<comp::Mass>();
|
||||
ecs.register::<comp::Sticky>();
|
||||
ecs.register::<comp::Gravity>();
|
||||
ecs.register::<comp::Attacking>();
|
||||
|
||||
// Register components send from clients -> server
|
||||
ecs.register::<comp::Controller>();
|
||||
@ -150,6 +148,7 @@ impl State {
|
||||
ecs.register::<comp::Admin>();
|
||||
ecs.register::<comp::Waypoint>();
|
||||
ecs.register::<comp::Projectile>();
|
||||
ecs.register::<comp::Attacking>();
|
||||
|
||||
// Register synced resources used by the ECS.
|
||||
ecs.insert(TimeOfDay(0.0));
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// How long until state should deal damage
|
||||
pub buildup_duration: Duration,
|
||||
@ -13,6 +13,10 @@ pub struct Data {
|
||||
pub recover_duration: Duration,
|
||||
/// Base damage
|
||||
pub base_damage: u32,
|
||||
/// Max range
|
||||
pub range: f32,
|
||||
/// Max angle (45.0 will give you a 90.0 angle window)
|
||||
pub max_angle: f32,
|
||||
/// Whether the attack can deal more damage
|
||||
pub exhausted: bool,
|
||||
}
|
||||
@ -32,13 +36,15 @@ impl CharacterBehavior for Data {
|
||||
.unwrap_or_default(),
|
||||
recover_duration: self.recover_duration,
|
||||
base_damage: self.base_damage,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: false,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
base_damage: self.base_damage,
|
||||
max_angle: 75_f32.to_radians(),
|
||||
max_angle: self.max_angle.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
});
|
||||
@ -47,6 +53,8 @@ impl CharacterBehavior for Data {
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
base_damage: self.base_damage,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: true,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
@ -58,6 +66,8 @@ impl CharacterBehavior for Data {
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
base_damage: self.base_damage,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: true,
|
||||
});
|
||||
} else {
|
||||
|
@ -22,7 +22,7 @@ impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||
let mut update = StateUpdate::from(data);
|
||||
|
||||
if let Err(_) = update.energy.try_change_by(-5, EnergySource::Climb) {
|
||||
if let Err(_) = update.energy.try_change_by(-8, EnergySource::Climb) {
|
||||
update.character = CharacterState::Idle {};
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ impl CharacterBehavior for Data {
|
||||
|
||||
if self.initialize {
|
||||
update.vel.0 = data.inputs.look_dir * 20.0;
|
||||
update.ori.0 = data.vel.0.normalized();
|
||||
}
|
||||
|
||||
if self.buildup_duration != Duration::default() && data.physics.touch_entity.is_none() {
|
||||
|
@ -129,6 +129,9 @@ impl<'a> System<'a> for Sys {
|
||||
mountings,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut local_emitter = local_bus.emitter();
|
||||
|
||||
let mut join_iter = (
|
||||
&entities,
|
||||
&uids,
|
||||
@ -191,8 +194,8 @@ impl<'a> System<'a> for Sys {
|
||||
*tuple.5 = state_update.ori;
|
||||
*tuple.6 = state_update.energy;
|
||||
*tuple.7 = state_update.loadout;
|
||||
local_bus.emitter().append(&mut state_update.local_events);
|
||||
server_bus.emitter().append(&mut state_update.server_events);
|
||||
local_emitter.append(&mut state_update.local_events);
|
||||
server_emitter.append(&mut state_update.server_events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
Attacking, Body, CharacterState, Controller, HealthChange, HealthSource, Ori, Pos, Scale,
|
||||
Stats,
|
||||
Agent, Attacking, Body, CharacterState, Controller, HealthChange, HealthSource, Ori, Pos,
|
||||
Scale, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
sync::Uid,
|
||||
@ -25,6 +25,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Agent>,
|
||||
ReadStorage<'a, Controller>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Stats>,
|
||||
@ -41,6 +42,7 @@ impl<'a> System<'a> for Sys {
|
||||
positions,
|
||||
orientations,
|
||||
scales,
|
||||
agents,
|
||||
controllers,
|
||||
bodies,
|
||||
stats,
|
||||
@ -48,13 +50,15 @@ impl<'a> System<'a> for Sys {
|
||||
character_states,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
// Attacks
|
||||
for (entity, uid, pos, ori, scale_maybe, _, _attacker_stats, attack) in (
|
||||
for (entity, uid, pos, ori, scale_maybe, agent_maybe, _, _attacker_stats, attack) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
agents.maybe(),
|
||||
&controllers,
|
||||
&stats,
|
||||
&mut attacking_storage,
|
||||
@ -81,7 +85,7 @@ impl<'a> System<'a> for Sys {
|
||||
{
|
||||
// 2D versions
|
||||
let pos2 = Vec2::from(pos.0);
|
||||
let pos_b2: Vec2<f32> = Vec2::from(pos_b.0);
|
||||
let pos_b2 = Vec2::<f32>::from(pos_b.0);
|
||||
let ori2 = Vec2::from(ori.0);
|
||||
|
||||
// Scales
|
||||
@ -99,6 +103,15 @@ impl<'a> System<'a> for Sys {
|
||||
// Weapon gives base damage
|
||||
let mut dmg = attack.base_damage;
|
||||
|
||||
// NPCs do less damage:
|
||||
if agent_maybe.is_some() {
|
||||
dmg = (dmg / 2).max(1);
|
||||
}
|
||||
|
||||
if rand::random() {
|
||||
dmg += 1;
|
||||
}
|
||||
|
||||
// Block
|
||||
if character_b.is_block()
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0
|
||||
@ -106,7 +119,7 @@ impl<'a> System<'a> for Sys {
|
||||
dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as u32
|
||||
}
|
||||
|
||||
server_bus.emitter().emit(ServerEvent::Damage {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: -(dmg as i32),
|
||||
|
@ -836,7 +836,7 @@ fn handle_explosion(server: &mut Server, entity: EcsEntity, args: String, action
|
||||
.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<ServerEvent>>()
|
||||
.emit(ServerEvent::Explosion { pos: pos.0, radius }),
|
||||
.emit_now(ServerEvent::Explosion { pos: pos.0, radius }),
|
||||
None => server.notify_client(
|
||||
entity,
|
||||
ServerMsg::private(String::from("You have no position!")),
|
||||
|
@ -45,7 +45,7 @@ impl<'a> System<'a> for Sys {
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
server_emitter,
|
||||
server_event_bus,
|
||||
time,
|
||||
terrain,
|
||||
mut timer,
|
||||
@ -68,6 +68,8 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let time = time.0;
|
||||
|
||||
let mut server_emitter = server_event_bus.emitter();
|
||||
|
||||
let mut new_chat_msgs = Vec::new();
|
||||
|
||||
// Player list to send new players.
|
||||
|
@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys {
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
server_emitter,
|
||||
server_event_bus,
|
||||
tick,
|
||||
mut timer,
|
||||
mut chunk_generator,
|
||||
@ -51,6 +51,8 @@ impl<'a> System<'a> for Sys {
|
||||
) {
|
||||
timer.start();
|
||||
|
||||
let mut server_emitter = server_event_bus.emitter();
|
||||
|
||||
// Fetch any generated `TerrainChunk`s and insert them into the terrain.
|
||||
// Also, send the chunk data to anybody that is close by.
|
||||
'insert_terrain_chunks: while let Some((key, res)) = chunk_generator.recv_new_chunk() {
|
||||
@ -196,16 +198,19 @@ impl<'a> System<'a> for Sys {
|
||||
item,
|
||||
primary_ability: ability_drain.next(),
|
||||
secondary_ability: ability_drain.next(),
|
||||
block_ability: Some(comp::CharacterAbility::BasicBlock),
|
||||
block_ability: None,
|
||||
dodge_ability: Some(comp::CharacterAbility::Roll),
|
||||
})
|
||||
} else {
|
||||
Some(ItemConfig {
|
||||
// We need the empty item so npcs can attack
|
||||
item: Item::empty(),
|
||||
primary_ability: Some(CharacterAbility::BasicMelee {
|
||||
buildup_duration: Duration::from_millis(50),
|
||||
recover_duration: Duration::from_millis(50),
|
||||
base_damage: 1,
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_damage: 2,
|
||||
range: 3.5,
|
||||
max_angle: 60.0,
|
||||
}),
|
||||
secondary_ability: None,
|
||||
block_ability: None,
|
||||
@ -299,6 +304,8 @@ impl<'a> System<'a> for Sys {
|
||||
buildup_duration: Duration::from_millis(800),
|
||||
recover_duration: Duration::from_millis(200),
|
||||
base_damage: 13,
|
||||
range: 3.5,
|
||||
max_angle: 60.0,
|
||||
}),
|
||||
secondary_ability: None,
|
||||
block_ability: None,
|
||||
|
@ -47,6 +47,9 @@ impl MovementEventMapper {
|
||||
const SFX_DIST_LIMIT_SQR: f32 = 20000.0;
|
||||
let ecs = state.ecs();
|
||||
|
||||
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
||||
let mut sfx_emitter = sfx_event_bus.emitter();
|
||||
|
||||
let player_position = ecs
|
||||
.read_storage::<Pos>()
|
||||
.get(player_entity)
|
||||
@ -86,13 +89,11 @@ impl MovementEventMapper {
|
||||
|
||||
// Check for SFX config entry for this movement
|
||||
if Self::should_emit(state, triggers.get_key_value(&mapped_event)) {
|
||||
ecs.read_resource::<EventBus<SfxEventItem>>()
|
||||
.emitter()
|
||||
.emit(SfxEventItem::new(
|
||||
mapped_event,
|
||||
Some(pos.0),
|
||||
Some(Self::get_volume_for_body_type(body)),
|
||||
));
|
||||
sfx_emitter.emit(SfxEventItem::new(
|
||||
mapped_event,
|
||||
Some(pos.0),
|
||||
Some(Self::get_volume_for_body_type(body)),
|
||||
));
|
||||
|
||||
// Set the new previous entity state
|
||||
state.event = mapped_event;
|
||||
|
@ -53,8 +53,7 @@ impl ProgressionEventMapper {
|
||||
|
||||
if sfx_trigger_item.is_some() {
|
||||
ecs.read_resource::<EventBus<SfxEventItem>>()
|
||||
.emitter()
|
||||
.emit(SfxEventItem::at_player_position(mapped_event));
|
||||
.emit_now(SfxEventItem::at_player_position(mapped_event));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -683,7 +683,7 @@ impl CharSelectionUi {
|
||||
Mode::Create {
|
||||
name,
|
||||
body,
|
||||
loadout,
|
||||
loadout: _,
|
||||
tool,
|
||||
} => {
|
||||
let mut to_select = false;
|
||||
|
@ -21,7 +21,6 @@ use common::{
|
||||
ItemKind, Loadout,
|
||||
},
|
||||
figure::{DynaUnionizer, MatSegment, Material, Segment},
|
||||
vol::SizedVol,
|
||||
};
|
||||
use dot_vox::DotVoxData;
|
||||
use hashbrown::HashMap;
|
||||
|
@ -418,7 +418,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -660,7 +660,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -740,7 +740,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -820,7 +820,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -894,7 +894,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -968,7 +968,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -1042,7 +1042,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -1116,7 +1116,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -1190,7 +1190,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
@ -1264,7 +1264,7 @@ impl FigureMgr {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !character.equals(&last_character.0) {
|
||||
if !character.same_variant(&last_character.0) {
|
||||
state.state_time = 0.0;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,10 @@ const LIGHT_DIST_RADIUS: f32 = 64.0; // The distance beyond which lights may not
|
||||
const SHADOW_DIST_RADIUS: f32 = 8.0;
|
||||
const SHADOW_MAX_DIST: f32 = 96.0; // The distance beyond which shadows may not be visible
|
||||
|
||||
/// Above this speed is considered running
|
||||
/// Used for first person camera effects
|
||||
const RUNNING_THRESHOLD: f32 = 0.7;
|
||||
|
||||
struct Skybox {
|
||||
model: Model<SkyboxPipeline>,
|
||||
locals: Consts<SkyboxLocals>,
|
||||
@ -191,7 +195,7 @@ impl Scene {
|
||||
let is_running = ecs
|
||||
.read_storage::<comp::Vel>()
|
||||
.get(scene_data.player_entity)
|
||||
.map(|v| v.0.magnitude_squared() > 0.5);
|
||||
.map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2));
|
||||
|
||||
let on_ground = ecs
|
||||
.read_storage::<comp::PhysicsState>()
|
||||
|
Loading…
x
Reference in New Issue
Block a user