add death screen
This commit is contained in:
parent
7aa6885509
commit
182659eff0
33
src/actor.rs
33
src/actor.rs
|
@ -18,7 +18,8 @@ use bevy::prelude::*;
|
||||||
use bevy_xpbd_3d::prelude::*;
|
use bevy_xpbd_3d::prelude::*;
|
||||||
use bevy::scene::SceneInstance;
|
use bevy::scene::SceneInstance;
|
||||||
use bevy::math::DVec3;
|
use bevy::math::DVec3;
|
||||||
use crate::{actor, audio, camera, chat, commands, hud, nature, var, visual, world};
|
use crate::prelude::*;
|
||||||
|
use crate::{actor, audio, camera, chat, commands, hud, menu, nature, visual, world};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
||||||
|
@ -229,7 +230,7 @@ impl Default for Battery {
|
||||||
|
|
||||||
pub fn update_power(
|
pub fn update_power(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut settings: ResMut<var::Settings>,
|
mut settings: ResMut<Settings>,
|
||||||
mut q_battery: Query<(&mut Battery, Option<&Player>)>,
|
mut q_battery: Query<(&mut Battery, Option<&Player>)>,
|
||||||
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
|
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
|
||||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||||
|
@ -299,7 +300,7 @@ pub fn update_physics_lifeforms(
|
||||||
pub fn handle_input(
|
pub fn handle_input(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
mut settings: ResMut<var::Settings>,
|
mut settings: ResMut<Settings>,
|
||||||
q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>,
|
q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>,
|
||||||
player: Query<Entity, With<actor::Player>>,
|
player: Query<Entity, With<actor::Player>>,
|
||||||
q_camera: Query<&Transform, With<Camera>>,
|
q_camera: Query<&Transform, With<Camera>>,
|
||||||
|
@ -382,7 +383,7 @@ pub fn handle_input(
|
||||||
|
|
||||||
pub fn handle_vehicle_enter_exit(
|
pub fn handle_vehicle_enter_exit(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut settings: ResMut<var::Settings>,
|
mut settings: ResMut<Settings>,
|
||||||
mut er_vehicle: EventReader<VehicleEnterExitEvent>,
|
mut er_vehicle: EventReader<VehicleEnterExitEvent>,
|
||||||
mut q_playerflashlight: Query<&mut Visibility, (With<PlayersFlashLight>, Without<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
|
mut q_playerflashlight: Query<&mut Visibility, (With<PlayersFlashLight>, Without<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
|
||||||
mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
|
mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
|
||||||
|
@ -505,10 +506,10 @@ fn handle_player_death(
|
||||||
ew_spawn: EventWriter<commands::SpawnEvent>,
|
ew_spawn: EventWriter<commands::SpawnEvent>,
|
||||||
mut scene_spawner: ResMut<SceneSpawner>,
|
mut scene_spawner: ResMut<SceneSpawner>,
|
||||||
mut active_asteroids: ResMut<world::ActiveAsteroids>,
|
mut active_asteroids: ResMut<world::ActiveAsteroids>,
|
||||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
||||||
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
|
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
|
||||||
|
mut ew_deathscreen: EventWriter<menu::DeathScreenEvent>,
|
||||||
mut log: ResMut<hud::Log>,
|
mut log: ResMut<hud::Log>,
|
||||||
mut settings: ResMut<var::Settings>,
|
mut settings: ResMut<Settings>,
|
||||||
) {
|
) {
|
||||||
for death in er_playerdies.read() {
|
for death in er_playerdies.read() {
|
||||||
if settings.god_mode {
|
if settings.god_mode {
|
||||||
|
@ -528,20 +529,28 @@ fn handle_player_death(
|
||||||
|
|
||||||
match death.0 {
|
match death.0 {
|
||||||
DamageType::Mental => {
|
DamageType::Mental => {
|
||||||
|
settings.death_cause = "Brain Damage".to_string();
|
||||||
ew_effect.send(visual::SpawnEffectEvent {
|
ew_effect.send(visual::SpawnEffectEvent {
|
||||||
class: visual::Effects::FadeIn(Color::BLACK),
|
class: visual::Effects::FadeIn(Color::BLACK),
|
||||||
duration: 4.0,
|
duration: 4.0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DamageType::Asphyxiation => {
|
DamageType::Asphyxiation => {
|
||||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
|
settings.death_cause = "Suffocation".to_string();
|
||||||
ew_effect.send(visual::SpawnEffectEvent {
|
ew_effect.send(visual::SpawnEffectEvent {
|
||||||
class: visual::Effects::FadeIn(Color::BLACK),
|
class: visual::Effects::FadeIn(Color::BLACK),
|
||||||
duration: 1.0,
|
duration: 1.0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DamageType::Trauma | _ => {
|
DamageType::Trauma => {
|
||||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
|
settings.death_cause = "Trauma".to_string();
|
||||||
|
ew_effect.send(visual::SpawnEffectEvent {
|
||||||
|
class: visual::Effects::FadeIn(Color::MAROON),
|
||||||
|
duration: 1.0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
settings.death_cause = "Unknown".to_string();
|
||||||
ew_effect.send(visual::SpawnEffectEvent {
|
ew_effect.send(visual::SpawnEffectEvent {
|
||||||
class: visual::Effects::FadeIn(Color::MAROON),
|
class: visual::Effects::FadeIn(Color::MAROON),
|
||||||
duration: 1.0,
|
duration: 1.0,
|
||||||
|
@ -549,7 +558,7 @@ fn handle_player_death(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commands::load_defs(ew_spawn);
|
ew_deathscreen.send(menu::DeathScreenEvent::Show);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -557,7 +566,7 @@ fn handle_player_death(
|
||||||
fn handle_damage(
|
fn handle_damage(
|
||||||
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
|
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
|
||||||
mut q_hp: Query<(&mut HitPoints, Option<&Player>), Changed<HitPoints>>,
|
mut q_hp: Query<(&mut HitPoints, Option<&Player>), Changed<HitPoints>>,
|
||||||
settings: Res<var::Settings>,
|
settings: Res<Settings>,
|
||||||
) {
|
) {
|
||||||
for (mut hp, player_maybe) in &mut q_hp {
|
for (mut hp, player_maybe) in &mut q_hp {
|
||||||
if player_maybe.is_some() {
|
if player_maybe.is_some() {
|
||||||
|
@ -621,7 +630,7 @@ fn handle_cheats(
|
||||||
mut q_life: Query<(&mut actor::LifeForm, &mut actor::ExperiencesGForce), With<actor::Player>>,
|
mut q_life: Query<(&mut actor::LifeForm, &mut actor::ExperiencesGForce), With<actor::Player>>,
|
||||||
q_target: Query<(&Transform, &Position, Option<&LinearVelocity>), (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
|
q_target: Query<(&Transform, &Position, Option<&LinearVelocity>), (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
|
||||||
mut ew_playerdies: EventWriter<actor::PlayerDiesEvent>,
|
mut ew_playerdies: EventWriter<actor::PlayerDiesEvent>,
|
||||||
mut settings: ResMut<var::Settings>,
|
mut settings: ResMut<Settings>,
|
||||||
id2pos: Res<actor::Id2Pos>,
|
id2pos: Res<actor::Id2Pos>,
|
||||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||||
) {
|
) {
|
||||||
|
|
11
src/hud.rs
11
src/hud.rs
|
@ -22,7 +22,6 @@ use std::collections::VecDeque;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
pub const HUD_REFRESH_TIME: f32 = 0.1;
|
pub const HUD_REFRESH_TIME: f32 = 0.1;
|
||||||
pub const FONT: &str = "fonts/Yupiter-Regular.ttf";
|
|
||||||
pub const LOG_MAX_TIME_S: f64 = 30.0;
|
pub const LOG_MAX_TIME_S: f64 = 30.0;
|
||||||
pub const LOG_MAX_ROWS: usize = 30;
|
pub const LOG_MAX_ROWS: usize = 30;
|
||||||
pub const LOG_MAX: usize = LOG_MAX_ROWS;
|
pub const LOG_MAX: usize = LOG_MAX_ROWS;
|
||||||
|
@ -214,7 +213,7 @@ impl Log {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(
|
pub fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
settings: Res<Settings>,
|
settings: Res<Settings>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
|
@ -1138,11 +1137,3 @@ fn update_overlay_visibility(
|
||||||
AMBIENT_LIGHT
|
AMBIENT_LIGHT
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bool2vis(parameter: bool) -> Visibility {
|
|
||||||
if parameter {
|
|
||||||
Visibility::Inherited
|
|
||||||
} else {
|
|
||||||
Visibility::Hidden
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -18,6 +18,7 @@ mod chat;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod hud;
|
mod hud;
|
||||||
mod load;
|
mod load;
|
||||||
|
mod menu;
|
||||||
mod var;
|
mod var;
|
||||||
mod visual;
|
mod visual;
|
||||||
mod world;
|
mod world;
|
||||||
|
@ -26,8 +27,15 @@ mod world;
|
||||||
mod nature;
|
mod nature;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::var::Settings;
|
pub use crate::var::{FONT, Settings};
|
||||||
pub use crate::load::load_asset;
|
pub use crate::load::load_asset;
|
||||||
|
pub fn bool2vis(boolean: bool) -> bevy::prelude::Visibility {
|
||||||
|
if boolean {
|
||||||
|
bevy::prelude::Visibility::Inherited
|
||||||
|
} else {
|
||||||
|
bevy::prelude::Visibility::Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
|
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
|
||||||
|
@ -133,6 +141,7 @@ impl Plugin for OutFlyPlugin {
|
||||||
camera::CameraPlugin,
|
camera::CameraPlugin,
|
||||||
chat::ChatPlugin,
|
chat::ChatPlugin,
|
||||||
commands::CommandsPlugin,
|
commands::CommandsPlugin,
|
||||||
|
menu::MenuPlugin,
|
||||||
visual::VisualPlugin,
|
visual::VisualPlugin,
|
||||||
hud::HudPlugin,
|
hud::HudPlugin,
|
||||||
load::LoadPlugin,
|
load::LoadPlugin,
|
||||||
|
|
144
src/menu.rs
Normal file
144
src/menu.rs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
// ▄████████▄ + ███ + ▄█████████ ███ +
|
||||||
|
// ███▀ ▀███ + + ███ ███▀ + ███ + +
|
||||||
|
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
|
||||||
|
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
|
||||||
|
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
|
||||||
|
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
|
||||||
|
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
|
||||||
|
// + + + ███
|
||||||
|
// + ▀████████████████████████████████████████████████████▀
|
||||||
|
//
|
||||||
|
// This plugin manages game menus and the player death screen
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{audio, hud, visual};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct MenuPlugin;
|
||||||
|
impl Plugin for MenuPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Startup, setup.after(hud::setup));
|
||||||
|
app.add_systems(PreUpdate, show_deathscreen.run_if(on_event::<DeathScreenEvent>()));
|
||||||
|
app.add_systems(Update, handle_deathscreen_input);
|
||||||
|
app.add_event::<DeathScreenEvent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)] pub struct DeathScreenElement;
|
||||||
|
#[derive(Component)] pub struct BlackScreen;
|
||||||
|
#[derive(Component)] pub struct DeathText;
|
||||||
|
#[derive(Event, PartialEq)] pub enum DeathScreenEvent { Show, Hide }
|
||||||
|
|
||||||
|
pub fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
settings: Res<Settings>,
|
||||||
|
) {
|
||||||
|
commands.spawn((
|
||||||
|
BlackScreen,
|
||||||
|
DeathScreenElement,
|
||||||
|
NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
width: Val::Vw(100.0),
|
||||||
|
height: Val::Vh(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
top: Val::Px(0.0),
|
||||||
|
left: Val::Px(0.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
background_color: Color::BLACK.into(),
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let font_handle = asset_server.load(FONT);
|
||||||
|
let style_death = TextStyle {
|
||||||
|
font: font_handle.clone(),
|
||||||
|
font_size: settings.font_size_deathtext,
|
||||||
|
color: settings.hud_color_subtitles,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
let style_death_subtext = TextStyle {
|
||||||
|
font: font_handle.clone(),
|
||||||
|
font_size: settings.font_size_deathsubtext,
|
||||||
|
color: settings.hud_color_subtitles,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
let style_death_subsubtext = TextStyle {
|
||||||
|
font: font_handle.clone(),
|
||||||
|
font_size: settings.font_size_deathsubtext * 0.8,
|
||||||
|
color: settings.hud_color_subtitles,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
commands.spawn((
|
||||||
|
DeathScreenElement,
|
||||||
|
NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::SpaceAround,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
)).with_children(|builder| {
|
||||||
|
builder.spawn((
|
||||||
|
DeathText,
|
||||||
|
TextBundle {
|
||||||
|
text: Text {
|
||||||
|
sections: vec![
|
||||||
|
TextSection::new("You are dead.\n", style_death),
|
||||||
|
TextSection::new("Cause: ", style_death_subtext.clone()),
|
||||||
|
TextSection::new("Unknown", style_death_subtext),
|
||||||
|
TextSection::new("\n\n\nPress E to begin anew.", style_death_subsubtext),
|
||||||
|
],
|
||||||
|
justify: JustifyText::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_deathscreen(
|
||||||
|
mut er_deathscreen: EventReader<DeathScreenEvent>,
|
||||||
|
mut q_vis: Query<&mut Visibility, With<DeathScreenElement>>,
|
||||||
|
mut q_text: Query<&mut Text, With<DeathText>>,
|
||||||
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||||
|
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
|
||||||
|
mut settings: ResMut<Settings>,
|
||||||
|
) {
|
||||||
|
for event in er_deathscreen.read() {
|
||||||
|
let show = *event == DeathScreenEvent::Show;
|
||||||
|
for mut vis in &mut q_vis {
|
||||||
|
*vis = bool2vis(show);
|
||||||
|
}
|
||||||
|
settings.deathscreen_active = show;
|
||||||
|
|
||||||
|
if show {
|
||||||
|
if let Ok(mut text) = q_text.get_single_mut() {
|
||||||
|
text.sections[2].value = settings.death_cause.clone();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
|
||||||
|
ew_effect.send(visual::SpawnEffectEvent { class: visual::Effects::FadeIn(Color::BLACK), duration: 0.3 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_deathscreen_input(
|
||||||
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut ew_deathscreen: EventWriter<DeathScreenEvent>,
|
||||||
|
settings: ResMut<Settings>,
|
||||||
|
) {
|
||||||
|
if !settings.deathscreen_active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if keyboard_input.pressed(settings.key_interact) {
|
||||||
|
ew_deathscreen.send(DeathScreenEvent::Hide);
|
||||||
|
}
|
||||||
|
}
|
10
src/var.rs
10
src/var.rs
|
@ -19,6 +19,8 @@ use toml_edit::DocumentMut;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
pub const FONT: &str = "fonts/Yupiter-Regular.ttf";
|
||||||
|
|
||||||
pub const SCOPE_SEPARATOR: &str = "$";
|
pub const SCOPE_SEPARATOR: &str = "$";
|
||||||
|
|
||||||
pub const TOKEN_EQUALS: &str = "==";
|
pub const TOKEN_EQUALS: &str = "==";
|
||||||
|
@ -50,6 +52,8 @@ pub struct Settings {
|
||||||
pub font_size_choices: f32,
|
pub font_size_choices: f32,
|
||||||
pub font_size_console: f32,
|
pub font_size_console: f32,
|
||||||
pub font_size_speedometer: f32,
|
pub font_size_speedometer: f32,
|
||||||
|
pub font_size_deathtext: f32,
|
||||||
|
pub font_size_deathsubtext: f32,
|
||||||
pub hud_color: Color,
|
pub hud_color: Color,
|
||||||
pub hud_color_console: Color,
|
pub hud_color_console: Color,
|
||||||
pub hud_color_console_warn: Color,
|
pub hud_color_console_warn: Color,
|
||||||
|
@ -62,6 +66,8 @@ pub struct Settings {
|
||||||
pub flashlight_active: bool,
|
pub flashlight_active: bool,
|
||||||
pub hud_active: bool,
|
pub hud_active: bool,
|
||||||
pub map_active: bool,
|
pub map_active: bool,
|
||||||
|
pub deathscreen_active: bool,
|
||||||
|
pub death_cause: String,
|
||||||
pub is_zooming: bool,
|
pub is_zooming: bool,
|
||||||
pub third_person: bool,
|
pub third_person: bool,
|
||||||
pub rotation_stabilizer_active: bool,
|
pub rotation_stabilizer_active: bool,
|
||||||
|
@ -157,6 +163,8 @@ impl Default for Settings {
|
||||||
font_size_choices: 28.0,
|
font_size_choices: 28.0,
|
||||||
font_size_console: 20.0,
|
font_size_console: 20.0,
|
||||||
font_size_speedometer: 34.0,
|
font_size_speedometer: 34.0,
|
||||||
|
font_size_deathtext: 64.0,
|
||||||
|
font_size_deathsubtext: 32.0,
|
||||||
hud_color: Color::hex("#BE1251").unwrap(),
|
hud_color: Color::hex("#BE1251").unwrap(),
|
||||||
hud_color_console: Color::hex("#BE1251").unwrap(),
|
hud_color_console: Color::hex("#BE1251").unwrap(),
|
||||||
hud_color_console_warn: Color::hex("#CCCCCC").unwrap(),
|
hud_color_console_warn: Color::hex("#CCCCCC").unwrap(),
|
||||||
|
@ -169,6 +177,8 @@ impl Default for Settings {
|
||||||
flashlight_active: false,
|
flashlight_active: false,
|
||||||
hud_active: true,
|
hud_active: true,
|
||||||
map_active: false,
|
map_active: false,
|
||||||
|
deathscreen_active: false,
|
||||||
|
death_cause: "Unknown".to_string(),
|
||||||
is_zooming: false,
|
is_zooming: false,
|
||||||
third_person: true,
|
third_person: true,
|
||||||
rotation_stabilizer_active: true,
|
rotation_stabilizer_active: true,
|
||||||
|
|
Loading…
Reference in a new issue