mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Moved to Skeleton and Animation traits
This commit is contained in:
parent
8184bc6fc0
commit
86e70751e0
@ -1,6 +1,7 @@
|
|||||||
#version 330 core
|
#version 330 core
|
||||||
|
|
||||||
in vec3 f_pos;
|
in vec3 f_pos;
|
||||||
|
in vec3 f_norm;
|
||||||
in vec3 f_col;
|
in vec3 f_col;
|
||||||
|
|
||||||
layout (std140)
|
layout (std140)
|
||||||
@ -22,5 +23,13 @@ uniform u_globals {
|
|||||||
out vec4 tgt_color;
|
out vec4 tgt_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
tgt_color = vec4(f_col, 1.0);
|
vec3 world_norm = (model_mat * vec4(f_norm, 0.0)).xyz;
|
||||||
|
|
||||||
|
float ambient = 0.5;
|
||||||
|
|
||||||
|
vec3 sun_dir = normalize(vec3(1.3, 1.7, 1.1));
|
||||||
|
|
||||||
|
float sun_diffuse = dot(sun_dir, world_norm) * 0.5;
|
||||||
|
|
||||||
|
tgt_color = vec4(f_col * (ambient + sun_diffuse), 1.0);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#version 330 core
|
#version 330 core
|
||||||
|
|
||||||
in vec3 v_pos;
|
in vec3 v_pos;
|
||||||
|
in vec3 v_norm;
|
||||||
in vec3 v_col;
|
in vec3 v_col;
|
||||||
in uint v_bone_idx;
|
in uint v_bone_idx;
|
||||||
|
|
||||||
@ -30,10 +31,12 @@ uniform u_bones {
|
|||||||
};
|
};
|
||||||
|
|
||||||
out vec3 f_pos;
|
out vec3 f_pos;
|
||||||
|
out vec3 f_norm;
|
||||||
out vec3 f_col;
|
out vec3 f_col;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
f_pos = v_pos;
|
f_pos = v_pos;
|
||||||
|
f_norm = v_norm;
|
||||||
f_col = v_col;
|
f_col = v_col;
|
||||||
|
|
||||||
gl_Position =
|
gl_Position =
|
||||||
|
@ -6,7 +6,7 @@ use crate::render::FigureBoneData;
|
|||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Bone {
|
pub struct Bone {
|
||||||
parent: Option<u8>, // MUST be less than the current bone index
|
parent_idx: Option<u8>, // MUST be less than the current bone index
|
||||||
pub offset: Vec3<f32>,
|
pub offset: Vec3<f32>,
|
||||||
pub ori: Quaternion<f32>,
|
pub ori: Quaternion<f32>,
|
||||||
}
|
}
|
||||||
@ -14,50 +14,122 @@ pub struct Bone {
|
|||||||
impl Bone {
|
impl Bone {
|
||||||
pub fn default() -> Self {
|
pub fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
parent: None,
|
parent_idx: None,
|
||||||
offset: Vec3::zero(),
|
offset: Vec3::zero(),
|
||||||
ori: Quaternion::identity(),
|
ori: Quaternion::identity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_parent_idx(&self) -> Option<u8> { self.parent_idx }
|
||||||
|
|
||||||
|
pub fn set_parent_idx(&mut self, parent_idx: u8) {
|
||||||
|
self.parent_idx = Some(parent_idx);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compute_base_matrix(&self) -> Mat4<f32> {
|
pub fn compute_base_matrix(&self) -> Mat4<f32> {
|
||||||
Mat4::<f32>::translation_3d(self.offset) * Mat4::from(self.ori)
|
Mat4::<f32>::translation_3d(self.offset) * Mat4::from(self.ori)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
pub trait Skeleton {
|
||||||
pub struct Skeleton {
|
fn compute_matrices(&self) -> [FigureBoneData; 16];
|
||||||
bones: [Bone; 16],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Skeleton {
|
pub struct CharacterSkeleton {
|
||||||
pub fn default() -> Self {
|
head: Bone,
|
||||||
|
chest: Bone,
|
||||||
|
belt: Bone,
|
||||||
|
leggings: Bone,
|
||||||
|
l_hand: Bone,
|
||||||
|
r_hand: Bone,
|
||||||
|
l_foot: Bone,
|
||||||
|
r_foot: Bone,
|
||||||
|
back: Bone,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharacterSkeleton {
|
||||||
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
bones: [Bone::default(); 16],
|
head: Bone::default(),
|
||||||
|
chest: Bone::default(),
|
||||||
|
belt: Bone::default(),
|
||||||
|
leggings: Bone::default(),
|
||||||
|
l_hand: Bone::default(),
|
||||||
|
r_hand: Bone::default(),
|
||||||
|
l_foot: Bone::default(),
|
||||||
|
r_foot: Bone::default(),
|
||||||
|
back: Bone::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_bone(mut self, bone_idx: u8, bone: Bone) -> Self {
|
impl Skeleton for CharacterSkeleton {
|
||||||
self.bones[bone_idx as usize] = bone;
|
fn compute_matrices(&self) -> [FigureBoneData; 16] {
|
||||||
self
|
let chest_mat = self.chest.compute_base_matrix();
|
||||||
|
|
||||||
|
[
|
||||||
|
FigureBoneData::new(self.head.compute_base_matrix()),
|
||||||
|
FigureBoneData::new(chest_mat),
|
||||||
|
FigureBoneData::new(self.belt.compute_base_matrix()),
|
||||||
|
FigureBoneData::new(self.leggings.compute_base_matrix()),
|
||||||
|
FigureBoneData::new(self.l_hand.compute_base_matrix()),
|
||||||
|
FigureBoneData::new(self.r_hand.compute_base_matrix()),
|
||||||
|
FigureBoneData::new(self.l_foot.compute_base_matrix()),
|
||||||
|
FigureBoneData::new(self.r_foot.compute_base_matrix()),
|
||||||
|
FigureBoneData::new(chest_mat * self.back.compute_base_matrix()),
|
||||||
|
FigureBoneData::default(),
|
||||||
|
FigureBoneData::default(),
|
||||||
|
FigureBoneData::default(),
|
||||||
|
FigureBoneData::default(),
|
||||||
|
FigureBoneData::default(),
|
||||||
|
FigureBoneData::default(),
|
||||||
|
FigureBoneData::default(),
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bone(&self, bone_idx: u8) -> &Bone { &self.bones[bone_idx as usize] }
|
pub trait Animation {
|
||||||
pub fn bone_mut(&mut self, bone_idx: u8) -> &mut Bone { &mut self.bones[bone_idx as usize] }
|
type Skeleton;
|
||||||
|
type Dependency;
|
||||||
|
|
||||||
pub fn compute_matrices(&self) -> [FigureBoneData; 16] {
|
fn update_skeleton(
|
||||||
let mut bone_data = [FigureBoneData::default(); 16];
|
skeleton: &mut Self::Skeleton,
|
||||||
for i in 0..16 {
|
dependency: Self::Dependency,
|
||||||
bone_data[i] = FigureBoneData::new(
|
|
||||||
self.bones[i].compute_base_matrix()
|
|
||||||
// *
|
|
||||||
//if let Some(parent_idx) = self.bones[i].parent {
|
|
||||||
// bone_data[parent_idx as usize]
|
|
||||||
//} else {
|
|
||||||
// Mat4::identity()
|
|
||||||
//}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
bone_data
|
|
||||||
|
pub struct RunAnimation;
|
||||||
|
|
||||||
|
impl Animation for RunAnimation {
|
||||||
|
type Skeleton = CharacterSkeleton;
|
||||||
|
type Dependency = f64;
|
||||||
|
|
||||||
|
fn update_skeleton(
|
||||||
|
skeleton: &mut Self::Skeleton,
|
||||||
|
time: f64,
|
||||||
|
) {
|
||||||
|
let wave = (time as f32 * 10.0).sin();
|
||||||
|
let wave_fast = (time as f32 * 5.0).sin();
|
||||||
|
|
||||||
|
skeleton.head.offset = Vec3::unit_z() * 13.0;
|
||||||
|
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);
|
||||||
|
|
||||||
|
skeleton.chest.offset = Vec3::unit_z() * 9.0;
|
||||||
|
skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3);
|
||||||
|
|
||||||
|
skeleton.belt.offset = Vec3::unit_z() * 7.0;
|
||||||
|
skeleton.belt.ori = Quaternion::rotation_z(wave * 0.3);
|
||||||
|
|
||||||
|
skeleton.leggings.offset = Vec3::unit_z() * 4.0;
|
||||||
|
skeleton.leggings.ori = Quaternion::rotation_z(wave * 0.3);
|
||||||
|
|
||||||
|
skeleton.l_hand.offset = Vec3::new(-8.0, wave * 4.0, 9.0);
|
||||||
|
skeleton.r_hand.offset = Vec3::new(8.0, -wave * 4.0, 9.0);
|
||||||
|
|
||||||
|
skeleton.l_foot.offset = Vec3::new(-3.0, -wave * 4.0, -(wave_fast.abs() - 0.5) * 3.0);
|
||||||
|
skeleton.r_foot.offset = Vec3::new(3.0, wave * 4.0, (wave_fast.abs() - 0.5) * 3.0);
|
||||||
|
|
||||||
|
skeleton.back.offset = Vec3::new(-8.0, 5.0, 16.0);
|
||||||
|
skeleton.back.ori = Quaternion::rotation_y(2.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,14 +29,15 @@ fn create_quad(
|
|||||||
origin: Vec3<f32>,
|
origin: Vec3<f32>,
|
||||||
unit_x: Vec3<f32>,
|
unit_x: Vec3<f32>,
|
||||||
unit_y: Vec3<f32>,
|
unit_y: Vec3<f32>,
|
||||||
|
norm: Vec3<f32>,
|
||||||
col: Rgb<f32>,
|
col: Rgb<f32>,
|
||||||
bone: u8,
|
bone: u8,
|
||||||
) -> Quad<FigurePipeline> {
|
) -> Quad<FigurePipeline> {
|
||||||
Quad::new(
|
Quad::new(
|
||||||
FigureVertex::new(origin, col, bone),
|
FigureVertex::new(origin, norm, col, bone),
|
||||||
FigureVertex::new(origin + unit_x, col, bone),
|
FigureVertex::new(origin + unit_x, norm, col, bone),
|
||||||
FigureVertex::new(origin + unit_x + unit_y, col, bone),
|
FigureVertex::new(origin + unit_x + unit_y, norm, col, bone),
|
||||||
FigureVertex::new(origin + unit_y, col, bone),
|
FigureVertex::new(origin + unit_y, norm, col, bone),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +62,7 @@ impl Meshable for Segment {
|
|||||||
offs + pos.map(|e| e as f32) + Vec3::unit_y(),
|
offs + pos.map(|e| e as f32) + Vec3::unit_y(),
|
||||||
-Vec3::unit_y(),
|
-Vec3::unit_y(),
|
||||||
Vec3::unit_z(),
|
Vec3::unit_z(),
|
||||||
|
-Vec3::unit_x(),
|
||||||
col,
|
col,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
@ -69,6 +71,7 @@ impl Meshable for Segment {
|
|||||||
offs + pos.map(|e| e as f32) + Vec3::unit_x(),
|
offs + pos.map(|e| e as f32) + Vec3::unit_x(),
|
||||||
Vec3::unit_y(),
|
Vec3::unit_y(),
|
||||||
Vec3::unit_z(),
|
Vec3::unit_z(),
|
||||||
|
Vec3::unit_x(),
|
||||||
col,
|
col,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
@ -77,6 +80,7 @@ impl Meshable for Segment {
|
|||||||
offs + pos.map(|e| e as f32),
|
offs + pos.map(|e| e as f32),
|
||||||
Vec3::unit_x(),
|
Vec3::unit_x(),
|
||||||
Vec3::unit_z(),
|
Vec3::unit_z(),
|
||||||
|
-Vec3::unit_y(),
|
||||||
col,
|
col,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
@ -85,6 +89,7 @@ impl Meshable for Segment {
|
|||||||
offs + pos.map(|e| e as f32) + Vec3::unit_y(),
|
offs + pos.map(|e| e as f32) + Vec3::unit_y(),
|
||||||
Vec3::unit_z(),
|
Vec3::unit_z(),
|
||||||
Vec3::unit_x(),
|
Vec3::unit_x(),
|
||||||
|
Vec3::unit_y(),
|
||||||
col,
|
col,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
@ -93,6 +98,7 @@ impl Meshable for Segment {
|
|||||||
offs + pos.map(|e| e as f32),
|
offs + pos.map(|e| e as f32),
|
||||||
Vec3::unit_y(),
|
Vec3::unit_y(),
|
||||||
Vec3::unit_x(),
|
Vec3::unit_x(),
|
||||||
|
-Vec3::unit_z(),
|
||||||
col,
|
col,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
@ -101,6 +107,7 @@ impl Meshable for Segment {
|
|||||||
offs + pos.map(|e| e as f32) + Vec3::unit_z(),
|
offs + pos.map(|e| e as f32) + Vec3::unit_z(),
|
||||||
Vec3::unit_x(),
|
Vec3::unit_x(),
|
||||||
Vec3::unit_y(),
|
Vec3::unit_y(),
|
||||||
|
Vec3::unit_z(),
|
||||||
col,
|
col,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
|
@ -25,6 +25,7 @@ use super::{
|
|||||||
gfx_defines! {
|
gfx_defines! {
|
||||||
vertex Vertex {
|
vertex Vertex {
|
||||||
pos: [f32; 3] = "v_pos",
|
pos: [f32; 3] = "v_pos",
|
||||||
|
norm: [f32; 3] = "v_norm",
|
||||||
col: [f32; 3] = "v_col",
|
col: [f32; 3] = "v_col",
|
||||||
bone_idx: u8 = "v_bone_idx",
|
bone_idx: u8 = "v_bone_idx",
|
||||||
}
|
}
|
||||||
@ -50,10 +51,11 @@ gfx_defines! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Vertex {
|
impl Vertex {
|
||||||
pub fn new(pos: Vec3<f32>, col: Rgb<f32>, bone_idx: u8) -> Self {
|
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>, bone_idx: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pos: pos.into_array(),
|
pos: pos.into_array(),
|
||||||
col: col.into_array(),
|
col: col.into_array(),
|
||||||
|
norm: norm.into_array(),
|
||||||
bone_idx,
|
bone_idx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ use crate::{
|
|||||||
anim::Skeleton,
|
anim::Skeleton,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Figure {
|
pub struct Figure<S: Skeleton> {
|
||||||
// GPU data
|
// GPU data
|
||||||
model: Model<FigurePipeline>,
|
model: Model<FigurePipeline>,
|
||||||
bone_consts: Consts<FigureBoneData>,
|
bone_consts: Consts<FigureBoneData>,
|
||||||
@ -22,15 +22,15 @@ pub struct Figure {
|
|||||||
|
|
||||||
// CPU data
|
// CPU data
|
||||||
bone_meshes: [Option<Mesh<FigurePipeline>>; 16],
|
bone_meshes: [Option<Mesh<FigurePipeline>>; 16],
|
||||||
pub skeleton: Skeleton,
|
pub skeleton: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Figure {
|
impl<S: Skeleton> Figure<S> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
bone_meshes: [Option<Mesh<FigurePipeline>>; 16]
|
bone_meshes: [Option<Mesh<FigurePipeline>>; 16],
|
||||||
|
skeleton: S,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let skeleton = Skeleton::default();
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
model: renderer.create_model(&Mesh::new())?,
|
model: renderer.create_model(&Mesh::new())?,
|
||||||
bone_consts: renderer.create_consts(&skeleton.compute_matrices())?,
|
bone_consts: renderer.create_consts(&skeleton.compute_matrices())?,
|
||||||
|
@ -30,6 +30,11 @@ use crate::{
|
|||||||
},
|
},
|
||||||
window::Event,
|
window::Event,
|
||||||
mesh::Meshable,
|
mesh::Meshable,
|
||||||
|
anim::{
|
||||||
|
Animation,
|
||||||
|
CharacterSkeleton,
|
||||||
|
RunAnimation,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Local
|
// Local
|
||||||
@ -51,7 +56,7 @@ pub struct Scene {
|
|||||||
globals: Consts<Globals>,
|
globals: Consts<Globals>,
|
||||||
skybox: Skybox,
|
skybox: Skybox,
|
||||||
|
|
||||||
test_figure: Figure,
|
test_figure: Figure<CharacterSkeleton>,
|
||||||
|
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
@ -85,10 +90,10 @@ impl Scene {
|
|||||||
Some(load_segment("chest.vox").generate_mesh_with_offset(Vec3::new(-6.0, -3.0, 0.0))),
|
Some(load_segment("chest.vox").generate_mesh_with_offset(Vec3::new(-6.0, -3.0, 0.0))),
|
||||||
Some(load_segment("belt.vox").generate_mesh_with_offset(Vec3::new(-5.0, -3.0, 0.0))),
|
Some(load_segment("belt.vox").generate_mesh_with_offset(Vec3::new(-5.0, -3.0, 0.0))),
|
||||||
Some(load_segment("pants.vox").generate_mesh_with_offset(Vec3::new(-5.0, -3.0, 0.0))),
|
Some(load_segment("pants.vox").generate_mesh_with_offset(Vec3::new(-5.0, -3.0, 0.0))),
|
||||||
Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.0))),
|
|
||||||
Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.0))),
|
|
||||||
Some(load_segment("hand.vox").generate_mesh_with_offset(Vec3::new(-2.0, -2.0, -1.0))),
|
Some(load_segment("hand.vox").generate_mesh_with_offset(Vec3::new(-2.0, -2.0, -1.0))),
|
||||||
Some(load_segment("hand.vox").generate_mesh_with_offset(Vec3::new(-2.0, -2.0, -1.0))),
|
Some(load_segment("hand.vox").generate_mesh_with_offset(Vec3::new(-2.0, -2.0, -1.0))),
|
||||||
|
Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.0))),
|
||||||
|
Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.0))),
|
||||||
Some(load_segment("sword.vox").generate_mesh_with_offset(Vec3::new(-6.5, -1.0, 0.0))),
|
Some(load_segment("sword.vox").generate_mesh_with_offset(Vec3::new(-6.5, -1.0, 0.0))),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@ -98,6 +103,7 @@ impl Scene {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
],
|
],
|
||||||
|
CharacterSkeleton::new(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
||||||
@ -142,26 +148,10 @@ impl Scene {
|
|||||||
.expect("Failed to update global constants");
|
.expect("Failed to update global constants");
|
||||||
|
|
||||||
// TODO: Don't do this here
|
// TODO: Don't do this here
|
||||||
let offs = (self.client.state().get_tick() as f32 * 10.0).sin();
|
RunAnimation::update_skeleton(
|
||||||
self.test_figure.skeleton.bone_mut(0).offset = Vec3::new(0.0, 0.0, 13.0);
|
&mut self.test_figure.skeleton,
|
||||||
self.test_figure.skeleton.bone_mut(0).ori = Quaternion::rotation_z(offs * 0.3);
|
self.client.state().get_tick(),
|
||||||
// Chest
|
);
|
||||||
self.test_figure.skeleton.bone_mut(1).offset = Vec3::new(0.0, 0.0, 9.0);
|
|
||||||
self.test_figure.skeleton.bone_mut(2).offset = Vec3::new(0.0, 0.0, 7.0);
|
|
||||||
self.test_figure.skeleton.bone_mut(3).offset = Vec3::new(0.0, 0.0, 4.0);
|
|
||||||
self.test_figure.skeleton.bone_mut(1).ori = Quaternion::rotation_z(offs * 0.15);
|
|
||||||
self.test_figure.skeleton.bone_mut(2).ori = Quaternion::rotation_z(offs * 0.15);
|
|
||||||
self.test_figure.skeleton.bone_mut(3).ori = Quaternion::rotation_z(offs * 0.15);
|
|
||||||
//Feet
|
|
||||||
self.test_figure.skeleton.bone_mut(4).offset = Vec3::new(-3.0, -offs * 4.0, 0.0);
|
|
||||||
self.test_figure.skeleton.bone_mut(5).offset = Vec3::new(3.0, offs * 4.0, 0.0);
|
|
||||||
// Hands
|
|
||||||
self.test_figure.skeleton.bone_mut(6).offset = Vec3::new(-8.0, offs * 4.0, 9.0);
|
|
||||||
self.test_figure.skeleton.bone_mut(7).offset = Vec3::new(8.0, -offs * 4.0, 9.0);
|
|
||||||
// Sword
|
|
||||||
self.test_figure.skeleton.bone_mut(8).offset = Vec3::new(-8.0, 5.0, 24.0);
|
|
||||||
self.test_figure.skeleton.bone_mut(8).ori = Quaternion::rotation_y(2.5);
|
|
||||||
|
|
||||||
self.test_figure.update_locals(renderer, FigureLocals::default());
|
self.test_figure.update_locals(renderer, FigureLocals::default());
|
||||||
self.test_figure.update_skeleton(renderer);
|
self.test_figure.update_skeleton(renderer);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user