mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
feat: fireball explosions
This commit is contained in:
parent
1f363da248
commit
f3ca06aa71
@ -21,6 +21,11 @@ pub enum CharacterAbility {
|
|||||||
projectile: Projectile,
|
projectile: Projectile,
|
||||||
projectile_body: Body,
|
projectile_body: Body,
|
||||||
},
|
},
|
||||||
|
CastFireball {
|
||||||
|
recover_duration: Duration,
|
||||||
|
projectile: Projectile,
|
||||||
|
projectile_body: Body,
|
||||||
|
},
|
||||||
Boost {
|
Boost {
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
only_up: bool,
|
only_up: bool,
|
||||||
@ -63,6 +68,13 @@ impl CharacterAbility {
|
|||||||
.try_change_by(-300, EnergySource::Ability)
|
.try_change_by(-300, EnergySource::Ability)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
},
|
},
|
||||||
|
CharacterAbility::CastFireball { .. } => {
|
||||||
|
!data.physics.in_fluid
|
||||||
|
&& update
|
||||||
|
.energy
|
||||||
|
.try_change_by(-500, EnergySource::Ability)
|
||||||
|
.is_ok()
|
||||||
|
},
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,6 +130,17 @@ impl From<&CharacterAbility> for CharacterState {
|
|||||||
projectile: projectile.clone(),
|
projectile: projectile.clone(),
|
||||||
projectile_body: *projectile_body,
|
projectile_body: *projectile_body,
|
||||||
}),
|
}),
|
||||||
|
CharacterAbility::CastFireball {
|
||||||
|
recover_duration,
|
||||||
|
projectile,
|
||||||
|
projectile_body,
|
||||||
|
} => CharacterState::CastFireball(cast_fireball::Data {
|
||||||
|
exhausted: false,
|
||||||
|
prepare_timer: Duration::default(),
|
||||||
|
recover_duration: *recover_duration,
|
||||||
|
projectile: projectile.clone(),
|
||||||
|
projectile_body: *projectile_body,
|
||||||
|
}),
|
||||||
CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data {
|
CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data {
|
||||||
duration: *duration,
|
duration: *duration,
|
||||||
only_up: *only_up,
|
only_up: *only_up,
|
||||||
|
@ -52,6 +52,8 @@ pub enum CharacterState {
|
|||||||
BasicMelee(basic_melee::Data),
|
BasicMelee(basic_melee::Data),
|
||||||
/// A basic ranged attack (e.g. bow)
|
/// A basic ranged attack (e.g. bow)
|
||||||
BasicRanged(basic_ranged::Data),
|
BasicRanged(basic_ranged::Data),
|
||||||
|
/// Cast a fireball
|
||||||
|
CastFireball(cast_fireball::Data),
|
||||||
/// A force will boost you into a direction for some duration
|
/// A force will boost you into a direction for some duration
|
||||||
Boost(boost::Data),
|
Boost(boost::Data),
|
||||||
/// Dash forward and then attack
|
/// Dash forward and then attack
|
||||||
@ -70,6 +72,7 @@ impl CharacterState {
|
|||||||
CharacterState::Wielding
|
CharacterState::Wielding
|
||||||
| CharacterState::BasicMelee(_)
|
| CharacterState::BasicMelee(_)
|
||||||
| CharacterState::BasicRanged(_)
|
| CharacterState::BasicRanged(_)
|
||||||
|
| CharacterState::CastFireball(_)
|
||||||
| CharacterState::DashMelee(_)
|
| CharacterState::DashMelee(_)
|
||||||
| CharacterState::TripleStrike(_)
|
| CharacterState::TripleStrike(_)
|
||||||
| CharacterState::TimedCombo(_)
|
| CharacterState::TimedCombo(_)
|
||||||
@ -89,6 +92,7 @@ impl CharacterState {
|
|||||||
match self {
|
match self {
|
||||||
CharacterState::BasicMelee(_)
|
CharacterState::BasicMelee(_)
|
||||||
| CharacterState::BasicRanged(_)
|
| CharacterState::BasicRanged(_)
|
||||||
|
| CharacterState::CastFireball(_)
|
||||||
| CharacterState::TimedCombo(_)
|
| CharacterState::TimedCombo(_)
|
||||||
| CharacterState::DashMelee(_)
|
| CharacterState::DashMelee(_)
|
||||||
| CharacterState::TripleStrike(_) => true,
|
| CharacterState::TripleStrike(_) => true,
|
||||||
|
@ -120,19 +120,21 @@ impl ToolData {
|
|||||||
range: 10.0,
|
range: 10.0,
|
||||||
max_angle: 45.0,
|
max_angle: 45.0,
|
||||||
},
|
},
|
||||||
BasicRanged {
|
CastFireball {
|
||||||
projectile: Projectile {
|
projectile: Projectile {
|
||||||
hit_ground: vec![projectile::Effect::Vanish],
|
hit_ground: vec![
|
||||||
hit_wall: vec![projectile::Effect::Vanish],
|
projectile::Effect::Explode { power: 5.0 },
|
||||||
hit_entity: vec![
|
|
||||||
projectile::Effect::Damage(HealthChange {
|
|
||||||
// TODO: This should not be fixed (?)
|
|
||||||
amount: -8,
|
|
||||||
cause: HealthSource::Projectile { owner: None },
|
|
||||||
}),
|
|
||||||
projectile::Effect::Vanish,
|
projectile::Effect::Vanish,
|
||||||
],
|
],
|
||||||
time_left: Duration::from_secs(5),
|
hit_wall: vec![
|
||||||
|
projectile::Effect::Explode { power: 5.0 },
|
||||||
|
projectile::Effect::Vanish,
|
||||||
|
],
|
||||||
|
hit_entity: vec![
|
||||||
|
projectile::Effect::Explode { power: 5.0 },
|
||||||
|
projectile::Effect::Vanish,
|
||||||
|
],
|
||||||
|
time_left: Duration::from_secs(20),
|
||||||
owner: None,
|
owner: None,
|
||||||
},
|
},
|
||||||
projectile_body: Body::Object(object::Body::BoltFire),
|
projectile_body: Body::Object(object::Body::BoltFire),
|
||||||
|
@ -6,6 +6,7 @@ use std::time::Duration;
|
|||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Effect {
|
pub enum Effect {
|
||||||
Damage(comp::HealthChange),
|
Damage(comp::HealthChange),
|
||||||
|
Explode { power: f32 },
|
||||||
Vanish,
|
Vanish,
|
||||||
Stick,
|
Stick,
|
||||||
Possess,
|
Possess,
|
||||||
|
@ -73,7 +73,8 @@ pub enum LocalEvent {
|
|||||||
pub enum ServerEvent {
|
pub enum ServerEvent {
|
||||||
Explosion {
|
Explosion {
|
||||||
pos: Vec3<f32>,
|
pos: Vec3<f32>,
|
||||||
radius: f32,
|
power: f32,
|
||||||
|
owner: Option<Uid>,
|
||||||
},
|
},
|
||||||
Damage {
|
Damage {
|
||||||
uid: Uid,
|
uid: Uid,
|
||||||
|
81
common/src/states/cast_fireball.rs
Normal file
81
common/src/states/cast_fireball.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use crate::{
|
||||||
|
comp::{Body, CharacterState, Gravity, Projectile, StateUpdate},
|
||||||
|
event::ServerEvent,
|
||||||
|
states::utils::*,
|
||||||
|
sys::character_behavior::*,
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
/// How long we prepared the weapon already
|
||||||
|
pub prepare_timer: Duration,
|
||||||
|
/// How long the state has until exiting
|
||||||
|
pub recover_duration: Duration,
|
||||||
|
/// Projectile
|
||||||
|
pub projectile: Projectile,
|
||||||
|
/// Projectile
|
||||||
|
pub projectile_body: Body,
|
||||||
|
/// Whether the attack fired already
|
||||||
|
pub exhausted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharacterBehavior for Data {
|
||||||
|
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
|
||||||
|
handle_move(data, &mut update);
|
||||||
|
handle_jump(data, &mut update);
|
||||||
|
|
||||||
|
if !self.exhausted
|
||||||
|
&& (data.inputs.primary.is_pressed() | data.inputs.secondary.is_pressed())
|
||||||
|
{
|
||||||
|
// Prepare (draw the bow)
|
||||||
|
update.character = CharacterState::CastFireball(Data {
|
||||||
|
prepare_timer: self.prepare_timer + Duration::from_secs_f32(data.dt.0),
|
||||||
|
recover_duration: self.recover_duration,
|
||||||
|
projectile: self.projectile.clone(),
|
||||||
|
projectile_body: self.projectile_body,
|
||||||
|
exhausted: false,
|
||||||
|
});
|
||||||
|
} else if !self.exhausted {
|
||||||
|
// Fire
|
||||||
|
let mut projectile = self.projectile.clone();
|
||||||
|
projectile.set_owner(*data.uid);
|
||||||
|
update.server_events.push_front(ServerEvent::Shoot {
|
||||||
|
entity: data.entity,
|
||||||
|
dir: data.inputs.look_dir,
|
||||||
|
body: self.projectile_body,
|
||||||
|
light: None,
|
||||||
|
projectile,
|
||||||
|
gravity: Some(Gravity(0.1)),
|
||||||
|
});
|
||||||
|
|
||||||
|
update.character = CharacterState::CastFireball(Data {
|
||||||
|
prepare_timer: self.prepare_timer,
|
||||||
|
recover_duration: self.recover_duration,
|
||||||
|
projectile: self.projectile.clone(),
|
||||||
|
projectile_body: self.projectile_body,
|
||||||
|
exhausted: true,
|
||||||
|
});
|
||||||
|
} else if self.recover_duration != Duration::default() {
|
||||||
|
// Recovery
|
||||||
|
update.character = CharacterState::CastFireball(Data {
|
||||||
|
prepare_timer: self.prepare_timer,
|
||||||
|
recover_duration: self
|
||||||
|
.recover_duration
|
||||||
|
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
projectile: self.projectile.clone(),
|
||||||
|
projectile_body: self.projectile_body,
|
||||||
|
exhausted: true,
|
||||||
|
});
|
||||||
|
return update;
|
||||||
|
} else {
|
||||||
|
// Done
|
||||||
|
update.character = CharacterState::Wielding;
|
||||||
|
}
|
||||||
|
|
||||||
|
update
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ pub mod basic_block;
|
|||||||
pub mod basic_melee;
|
pub mod basic_melee;
|
||||||
pub mod basic_ranged;
|
pub mod basic_ranged;
|
||||||
pub mod boost;
|
pub mod boost;
|
||||||
|
pub mod cast_fireball;
|
||||||
pub mod climb;
|
pub mod climb;
|
||||||
pub mod dash_melee;
|
pub mod dash_melee;
|
||||||
pub mod equipping;
|
pub mod equipping;
|
||||||
|
@ -183,6 +183,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::TripleStrike(data) => data.behavior(&j),
|
CharacterState::TripleStrike(data) => data.behavior(&j),
|
||||||
CharacterState::BasicMelee(data) => data.behavior(&j),
|
CharacterState::BasicMelee(data) => data.behavior(&j),
|
||||||
CharacterState::BasicRanged(data) => data.behavior(&j),
|
CharacterState::BasicRanged(data) => data.behavior(&j),
|
||||||
|
CharacterState::CastFireball(data) => data.behavior(&j),
|
||||||
CharacterState::Boost(data) => data.behavior(&j),
|
CharacterState::Boost(data) => data.behavior(&j),
|
||||||
CharacterState::DashMelee(data) => data.behavior(&j),
|
CharacterState::DashMelee(data) => data.behavior(&j),
|
||||||
CharacterState::TimedCombo(data) => data.behavior(&j),
|
CharacterState::TimedCombo(data) => data.behavior(&j),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{projectile, HealthSource, Ori, PhysicsState, Projectile, Vel},
|
comp::{projectile, HealthSource, Ori, PhysicsState, Pos, Projectile, Vel},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
state::DeltaTime,
|
state::DeltaTime,
|
||||||
};
|
};
|
||||||
@ -13,6 +13,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
Read<'a, DeltaTime>,
|
Read<'a, DeltaTime>,
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
|
ReadStorage<'a, Pos>,
|
||||||
ReadStorage<'a, PhysicsState>,
|
ReadStorage<'a, PhysicsState>,
|
||||||
ReadStorage<'a, Vel>,
|
ReadStorage<'a, Vel>,
|
||||||
WriteStorage<'a, Ori>,
|
WriteStorage<'a, Ori>,
|
||||||
@ -25,6 +26,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
entities,
|
entities,
|
||||||
dt,
|
dt,
|
||||||
server_bus,
|
server_bus,
|
||||||
|
positions,
|
||||||
physics_states,
|
physics_states,
|
||||||
velocities,
|
velocities,
|
||||||
mut orientations,
|
mut orientations,
|
||||||
@ -34,8 +36,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut server_emitter = server_bus.emitter();
|
let mut server_emitter = server_bus.emitter();
|
||||||
|
|
||||||
// Attacks
|
// Attacks
|
||||||
for (entity, physics, ori, projectile) in (
|
for (entity, pos, physics, ori, projectile) in (
|
||||||
&entities,
|
&entities,
|
||||||
|
&positions,
|
||||||
&physics_states,
|
&physics_states,
|
||||||
&mut orientations,
|
&mut orientations,
|
||||||
&mut projectiles,
|
&mut projectiles,
|
||||||
@ -46,6 +49,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
if physics.on_ground {
|
if physics.on_ground {
|
||||||
for effect in projectile.hit_ground.drain(..) {
|
for effect in projectile.hit_ground.drain(..) {
|
||||||
match effect {
|
match effect {
|
||||||
|
projectile::Effect::Explode { power } => {
|
||||||
|
server_emitter.emit(ServerEvent::Explosion {
|
||||||
|
pos: pos.0,
|
||||||
|
power,
|
||||||
|
owner: projectile.owner,
|
||||||
|
})
|
||||||
|
},
|
||||||
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
||||||
entity,
|
entity,
|
||||||
cause: HealthSource::World,
|
cause: HealthSource::World,
|
||||||
@ -58,6 +68,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
else if physics.on_wall.is_some() {
|
else if physics.on_wall.is_some() {
|
||||||
for effect in projectile.hit_wall.drain(..) {
|
for effect in projectile.hit_wall.drain(..) {
|
||||||
match effect {
|
match effect {
|
||||||
|
projectile::Effect::Explode { power } => {
|
||||||
|
server_emitter.emit(ServerEvent::Explosion {
|
||||||
|
pos: pos.0,
|
||||||
|
power,
|
||||||
|
owner: projectile.owner,
|
||||||
|
})
|
||||||
|
},
|
||||||
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
||||||
entity,
|
entity,
|
||||||
cause: HealthSource::World,
|
cause: HealthSource::World,
|
||||||
@ -73,6 +90,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
projectile::Effect::Damage(change) => {
|
projectile::Effect::Damage(change) => {
|
||||||
server_emitter.emit(ServerEvent::Damage { uid: other, change })
|
server_emitter.emit(ServerEvent::Damage { uid: other, change })
|
||||||
},
|
},
|
||||||
|
projectile::Effect::Explode { power } => {
|
||||||
|
server_emitter.emit(ServerEvent::Explosion {
|
||||||
|
pos: pos.0,
|
||||||
|
power,
|
||||||
|
owner: projectile.owner,
|
||||||
|
})
|
||||||
|
},
|
||||||
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
||||||
entity,
|
entity,
|
||||||
cause: HealthSource::World,
|
cause: HealthSource::World,
|
||||||
|
@ -829,14 +829,18 @@ fn handle_lantern(server: &mut Server, entity: EcsEntity, args: String, action:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_explosion(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
fn handle_explosion(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
||||||
let radius = scan_fmt!(&args, action.arg_fmt, f32).unwrap_or(8.0);
|
let power = scan_fmt!(&args, action.arg_fmt, f32).unwrap_or(8.0);
|
||||||
|
let ecs = server.state.ecs();
|
||||||
|
|
||||||
match server.state.read_component_cloned::<comp::Pos>(entity) {
|
match server.state.read_component_cloned::<comp::Pos>(entity) {
|
||||||
Some(pos) => server
|
Some(pos) => {
|
||||||
.state
|
ecs.read_resource::<EventBus<ServerEvent>>()
|
||||||
.ecs()
|
.emit_now(ServerEvent::Explosion {
|
||||||
.read_resource::<EventBus<ServerEvent>>()
|
pos: pos.0,
|
||||||
.emit_now(ServerEvent::Explosion { pos: pos.0, radius }),
|
power,
|
||||||
|
owner: ecs.read_storage::<Uid>().get(entity).copied(),
|
||||||
|
})
|
||||||
|
},
|
||||||
None => server.notify_client(
|
None => server.notify_client(
|
||||||
entity,
|
entity,
|
||||||
ServerMsg::private(String::from("You have no position!")),
|
ServerMsg::private(String::from("You have no position!")),
|
||||||
|
@ -9,8 +9,11 @@ use common::{
|
|||||||
vol::{ReadVol, Vox},
|
vol::{ReadVol, Vox},
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
use specs::{Entity as EcsEntity, WorldExt};
|
use specs::{join::Join, Entity as EcsEntity, WorldExt};
|
||||||
use vek::Vec3;
|
use vek::{Vec3, *};
|
||||||
|
|
||||||
|
const BLOCK_EFFICIENCY: f32 = 0.9;
|
||||||
|
const BLOCK_ANGLE: f32 = 180.0;
|
||||||
|
|
||||||
pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
|
pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
|
||||||
let state = &server.state;
|
let state = &server.state;
|
||||||
@ -189,7 +192,46 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, radius: f32) {
|
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Option<Uid>) {
|
||||||
|
// Go through all other entities
|
||||||
|
let ecs = &server.state.ecs();
|
||||||
|
for (b, uid_b, pos_b, ori_b, character_b, stats_b) in (
|
||||||
|
&ecs.entities(),
|
||||||
|
&ecs.read_storage::<Uid>(),
|
||||||
|
&ecs.read_storage::<comp::Pos>(),
|
||||||
|
&ecs.read_storage::<comp::Ori>(),
|
||||||
|
&ecs.read_storage::<comp::CharacterState>(),
|
||||||
|
&mut ecs.write_storage::<comp::Stats>(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
{
|
||||||
|
// Check if it is a hit
|
||||||
|
if !stats_b.is_dead
|
||||||
|
// Spherical wedge shaped attack field
|
||||||
|
// RADIUS
|
||||||
|
&& pos.distance_squared(pos_b.0) < 10_f32.powi(2)
|
||||||
|
{
|
||||||
|
// Weapon gives base damage
|
||||||
|
let mut dmg = power as u32 * 2;
|
||||||
|
|
||||||
|
if rand::random() {
|
||||||
|
dmg += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block
|
||||||
|
if character_b.is_block()
|
||||||
|
&& ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0
|
||||||
|
{
|
||||||
|
dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
stats_b.health.change_by(HealthChange {
|
||||||
|
amount: -(dmg as i32),
|
||||||
|
cause: HealthSource::Projectile { owner },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const RAYS: usize = 500;
|
const RAYS: usize = 500;
|
||||||
|
|
||||||
for _ in 0..RAYS {
|
for _ in 0..RAYS {
|
||||||
@ -200,12 +242,11 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, radius: f32) {
|
|||||||
)
|
)
|
||||||
.normalized();
|
.normalized();
|
||||||
|
|
||||||
let ecs = server.state.ecs();
|
|
||||||
let mut block_change = ecs.write_resource::<BlockChange>();
|
let mut block_change = ecs.write_resource::<BlockChange>();
|
||||||
|
|
||||||
let _ = ecs
|
let _ = ecs
|
||||||
.read_resource::<TerrainGrid>()
|
.read_resource::<TerrainGrid>()
|
||||||
.ray(pos, pos + dir * radius)
|
.ray(pos, pos + dir * power)
|
||||||
.until(|_| rand::random::<f32>() < 0.05)
|
.until(|_| rand::random::<f32>() < 0.05)
|
||||||
.for_each(|pos| block_change.set(pos, Block::empty()))
|
.for_each(|pos| block_change.set(pos, Block::empty()))
|
||||||
.cast();
|
.cast();
|
||||||
|
@ -45,7 +45,9 @@ impl Server {
|
|||||||
|
|
||||||
for event in events {
|
for event in events {
|
||||||
match event {
|
match event {
|
||||||
ServerEvent::Explosion { pos, radius } => handle_explosion(&self, pos, radius),
|
ServerEvent::Explosion { pos, power, owner } => {
|
||||||
|
handle_explosion(&self, pos, power, owner)
|
||||||
|
},
|
||||||
ServerEvent::Shoot {
|
ServerEvent::Shoot {
|
||||||
entity,
|
entity,
|
||||||
dir,
|
dir,
|
||||||
|
Loading…
Reference in New Issue
Block a user