Merge branch 'master' into switching_to_draw_batches
This commit is contained in:
commit
441b22439f
119 changed files with 1851 additions and 3612 deletions
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{ EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed, WantsToApproach };
|
||||
use rltk::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
use specs::prelude::*;
|
||||
|
||||
pub struct ApproachAI {}
|
||||
|
|
@ -7,7 +7,6 @@ pub struct ApproachAI {}
|
|||
impl<'a> System<'a> for ApproachAI {
|
||||
#[allow(clippy::type_complexity)]
|
||||
type SystemData = (
|
||||
WriteExpect<'a, RandomNumberGenerator>,
|
||||
WriteStorage<'a, TakingTurn>,
|
||||
WriteStorage<'a, WantsToApproach>,
|
||||
WriteStorage<'a, Position>,
|
||||
|
|
@ -20,7 +19,6 @@ impl<'a> System<'a> for ApproachAI {
|
|||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
let (
|
||||
mut rng,
|
||||
mut turns,
|
||||
mut wants_to_approach,
|
||||
mut positions,
|
||||
|
|
@ -50,7 +48,7 @@ impl<'a> System<'a> for ApproachAI {
|
|||
let mut curr_abs_diff = 100;
|
||||
let idx = map.xy_idx(pos.x, pos.y);
|
||||
for tar_idx in target_idxs {
|
||||
let potential_path = rltk::a_star_search(idx, tar_idx, &mut *map);
|
||||
let potential_path = a_star_search(idx, tar_idx, &mut *map);
|
||||
if potential_path.success && potential_path.steps.len() > 1 {
|
||||
if
|
||||
path.is_none() ||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{ Chasing, EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed };
|
||||
use rltk::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
use specs::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use super::approach_ai_system::get_adjacent_unblocked;
|
||||
|
|
@ -26,8 +26,16 @@ impl<'a> System<'a> for ChaseAI {
|
|||
);
|
||||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
let (mut turns, mut chasing, mut positions, mut map, mut viewsheds, mut telepaths, mut entity_moved, entities) =
|
||||
data;
|
||||
let (
|
||||
mut turns,
|
||||
mut chasing,
|
||||
mut positions,
|
||||
mut map,
|
||||
mut viewsheds,
|
||||
mut telepaths,
|
||||
mut entity_moved,
|
||||
entities,
|
||||
) = data;
|
||||
let mut targets: HashMap<Entity, (i32, i32)> = HashMap::new();
|
||||
let mut end_chase: Vec<Entity> = Vec::new();
|
||||
// For every chasing entity with a turn, look for a valid target position,
|
||||
|
|
@ -67,9 +75,12 @@ impl<'a> System<'a> for ChaseAI {
|
|||
let mut path: Option<NavigationPath> = None;
|
||||
let idx = map.xy_idx(pos.x, pos.y);
|
||||
for tar_idx in target_idxs {
|
||||
let potential_path = rltk::a_star_search(idx, tar_idx, &mut *map);
|
||||
let potential_path = a_star_search(idx, tar_idx, &mut *map);
|
||||
if potential_path.success && potential_path.steps.len() > 1 {
|
||||
if path.is_none() || potential_path.steps.len() < path.as_ref().unwrap().steps.len() {
|
||||
if
|
||||
path.is_none() ||
|
||||
potential_path.steps.len() < path.as_ref().unwrap().steps.len()
|
||||
{
|
||||
path = Some(potential_path);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
use crate::{ tile_walkable, EntityMoved, Map, MoveMode, Movement, Position, TakingTurn, Telepath, Viewshed };
|
||||
use crate::{
|
||||
tile_walkable,
|
||||
EntityMoved,
|
||||
Map,
|
||||
MoveMode,
|
||||
Movement,
|
||||
Position,
|
||||
TakingTurn,
|
||||
Telepath,
|
||||
Viewshed,
|
||||
};
|
||||
use specs::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
|
||||
// Rolling a 1d8+x to decide where to move, where x are the number
|
||||
// of dice rolls in which they will remian stationary. i.e. If this
|
||||
|
|
@ -16,7 +27,7 @@ impl<'a> System<'a> for DefaultAI {
|
|||
WriteStorage<'a, Viewshed>,
|
||||
WriteStorage<'a, Telepath>,
|
||||
WriteStorage<'a, EntityMoved>,
|
||||
WriteExpect<'a, rltk::RandomNumberGenerator>,
|
||||
WriteExpect<'a, RandomNumberGenerator>,
|
||||
Entities<'a>,
|
||||
);
|
||||
|
||||
|
|
@ -85,7 +96,9 @@ impl<'a> System<'a> for DefaultAI {
|
|||
let idx = map.xy_idx(pos.x, pos.y);
|
||||
pos.x = x;
|
||||
pos.y = y;
|
||||
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert EntityMoved");
|
||||
entity_moved
|
||||
.insert(entity, EntityMoved {})
|
||||
.expect("Unable to insert EntityMoved");
|
||||
crate::spatial::move_entity(entity, idx, dest_idx);
|
||||
viewshed.dirty = true;
|
||||
if let Some(is_telepath) = telepaths.get_mut(entity) {
|
||||
|
|
@ -102,7 +115,9 @@ impl<'a> System<'a> for DefaultAI {
|
|||
if !crate::spatial::is_blocked(path[1] as usize) {
|
||||
pos.x = (path[1] as i32) % map.width;
|
||||
pos.y = (path[1] as i32) / map.width;
|
||||
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert EntityMoved");
|
||||
entity_moved
|
||||
.insert(entity, EntityMoved {})
|
||||
.expect("Unable to insert EntityMoved");
|
||||
let new_idx = map.xy_idx(pos.x, pos.y);
|
||||
crate::spatial::move_entity(entity, idx, new_idx);
|
||||
viewshed.dirty = true;
|
||||
|
|
@ -112,7 +127,7 @@ impl<'a> System<'a> for DefaultAI {
|
|||
path.remove(0);
|
||||
} else {
|
||||
// If the path is blocked, recalculate a new path to the same waypoint.
|
||||
let path = rltk::a_star_search(
|
||||
let path = a_star_search(
|
||||
map.xy_idx(pos.x, pos.y) as i32,
|
||||
map.xy_idx(
|
||||
(path[path.len() - 1] as i32) % map.width,
|
||||
|
|
@ -121,7 +136,9 @@ impl<'a> System<'a> for DefaultAI {
|
|||
&mut *map
|
||||
);
|
||||
if path.success && path.steps.len() > 1 {
|
||||
move_mode.mode = Movement::RandomWaypoint { path: Some(path.steps) };
|
||||
move_mode.mode = Movement::RandomWaypoint {
|
||||
path: Some(path.steps),
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -132,7 +149,7 @@ impl<'a> System<'a> for DefaultAI {
|
|||
let target_y = rng.roll_dice(1, map.height - 2);
|
||||
let idx = map.xy_idx(target_x, target_y);
|
||||
if tile_walkable(map.tiles[idx]) {
|
||||
let path = rltk::a_star_search(
|
||||
let path = a_star_search(
|
||||
map.xy_idx(pos.x, pos.y) as i32,
|
||||
map.xy_idx(target_x, target_y) as i32,
|
||||
&mut *map
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ use crate::{
|
|||
Map,
|
||||
TakingTurn,
|
||||
Confusion,
|
||||
Intrinsics,
|
||||
};
|
||||
use rltk::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
use specs::prelude::*;
|
||||
use crate::config::CONFIG;
|
||||
use crate::data::events::*;
|
||||
|
|
@ -36,6 +37,7 @@ impl<'a> System<'a> for EnergySystem {
|
|||
ReadStorage<'a, Name>,
|
||||
ReadExpect<'a, Point>,
|
||||
ReadStorage<'a, Confusion>,
|
||||
ReadStorage<'a, Intrinsics>,
|
||||
);
|
||||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
|
|
@ -53,6 +55,7 @@ impl<'a> System<'a> for EnergySystem {
|
|||
names,
|
||||
player_pos,
|
||||
confusion,
|
||||
intrinsics,
|
||||
) = data;
|
||||
// If not ticking, do nothing.
|
||||
if *runstate != RunState::Ticking {
|
||||
|
|
@ -68,7 +71,7 @@ impl<'a> System<'a> for EnergySystem {
|
|||
.insert(entity, TakingTurn {})
|
||||
.expect("Unable to insert turn for turn counter.");
|
||||
energy.current -= TURN_COST;
|
||||
crate::gamelog::record_event(EVENT::TURN(1));
|
||||
crate::gamelog::record_event(EVENT::Turn(1));
|
||||
// Handle spawning mobs each turn
|
||||
if CONFIG.logging.log_ticks {
|
||||
console::log(
|
||||
|
|
@ -87,20 +90,14 @@ impl<'a> System<'a> for EnergySystem {
|
|||
&positions,
|
||||
!&confusion,
|
||||
).join() {
|
||||
let burden_modifier = if let Some(burden) = burdens.get(entity) {
|
||||
match burden.level {
|
||||
BurdenLevel::Burdened => SPEED_MOD_BURDENED,
|
||||
BurdenLevel::Strained => SPEED_MOD_STRAINED,
|
||||
BurdenLevel::Overloaded => SPEED_MOD_OVERLOADED,
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let overmap_mod = if map.overmap { SPEED_MOD_OVERMAP_TRAVEL } else { 1.0 };
|
||||
let burden_modifier = get_burden_modifier(&burdens, entity);
|
||||
let overmap_mod = get_overmap_modifier(&map);
|
||||
let intrinsic_speed = get_intrinsic_speed(&intrinsics, entity);
|
||||
// Every entity has a POTENTIAL equal to their speed.
|
||||
let mut energy_potential: i32 = ((energy.speed as f32) *
|
||||
burden_modifier *
|
||||
overmap_mod) as i32;
|
||||
overmap_mod *
|
||||
intrinsic_speed) as i32;
|
||||
// Increment current energy by NORMAL_SPEED for every
|
||||
// whole number of NORMAL_SPEEDS in their POTENTIAL.
|
||||
while energy_potential >= NORMAL_SPEED {
|
||||
|
|
@ -121,37 +118,61 @@ impl<'a> System<'a> for EnergySystem {
|
|||
// has enough energy, they take a turn and decrement their energy
|
||||
// by TURN_COST. If the current entity is the player, await input.
|
||||
if energy.current >= TURN_COST {
|
||||
let mut my_turn = true;
|
||||
energy.current -= TURN_COST;
|
||||
if entity == *player {
|
||||
*runstate = RunState::AwaitingInput;
|
||||
} else {
|
||||
let distance = rltk::DistanceAlg::Pythagoras.distance2d(
|
||||
*player_pos,
|
||||
Point::new(pos.x, pos.y)
|
||||
);
|
||||
if distance > 20.0 {
|
||||
my_turn = false;
|
||||
}
|
||||
} else if cull_turn_by_distance(&player_pos, pos) {
|
||||
continue;
|
||||
}
|
||||
if my_turn {
|
||||
turns.insert(entity, TakingTurn {}).expect("Unable to insert turn.");
|
||||
if CONFIG.logging.log_ticks {
|
||||
let name = if let Some(name) = names.get(entity) {
|
||||
&name.name
|
||||
} else {
|
||||
"Unknown entity"
|
||||
};
|
||||
console::log(
|
||||
format!(
|
||||
"ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].",
|
||||
name,
|
||||
energy.current
|
||||
)
|
||||
);
|
||||
}
|
||||
turns.insert(entity, TakingTurn {}).expect("Unable to insert turn.");
|
||||
if CONFIG.logging.log_ticks {
|
||||
let name = if let Some(name) = names.get(entity) {
|
||||
&name.name
|
||||
} else {
|
||||
"Unknown entity"
|
||||
};
|
||||
console::log(
|
||||
format!(
|
||||
"ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].",
|
||||
name,
|
||||
energy.current
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_burden_modifier(burdens: &ReadStorage<Burden>, entity: Entity) -> f32 {
|
||||
return if let Some(burden) = burdens.get(entity) {
|
||||
match burden.level {
|
||||
BurdenLevel::Burdened => SPEED_MOD_BURDENED,
|
||||
BurdenLevel::Strained => SPEED_MOD_STRAINED,
|
||||
BurdenLevel::Overloaded => SPEED_MOD_OVERLOADED,
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
}
|
||||
|
||||
fn get_overmap_modifier(map: &ReadExpect<Map>) -> f32 {
|
||||
return if map.overmap { SPEED_MOD_OVERMAP_TRAVEL } else { 1.0 };
|
||||
}
|
||||
|
||||
fn cull_turn_by_distance(player_pos: &Point, pos: &Position) -> bool {
|
||||
let distance = DistanceAlg::Pythagoras.distance2d(*player_pos, Point::new(pos.x, pos.y));
|
||||
if distance > 20.0 {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn get_intrinsic_speed(intrinsics: &ReadStorage<Intrinsics>, entity: Entity) -> f32 {
|
||||
if let Some(intrinsics) = intrinsics.get(entity) {
|
||||
if intrinsics.list.contains(&crate::Intrinsic::Speed) {
|
||||
return 4.0 / 3.0;
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{ EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed, WantsToFlee };
|
||||
use rltk::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
use specs::prelude::*;
|
||||
|
||||
pub struct FleeAI {}
|
||||
|
|
@ -39,7 +39,13 @@ impl<'a> System<'a> for FleeAI {
|
|||
turn_done.push(entity);
|
||||
let my_idx = map.xy_idx(pos.x, pos.y);
|
||||
map.populate_blocked();
|
||||
let flee_map = DijkstraMap::new(map.width as usize, map.height as usize, &fleeing.indices, &*map, 100.0);
|
||||
let flee_map = DijkstraMap::new(
|
||||
map.width as usize,
|
||||
map.height as usize,
|
||||
&fleeing.indices,
|
||||
&*map,
|
||||
100.0
|
||||
);
|
||||
let flee_target = DijkstraMap::find_highest_exit(&flee_map, my_idx, &*map);
|
||||
if let Some(flee_target) = flee_target {
|
||||
if !crate::spatial::is_blocked(flee_target as usize) {
|
||||
|
|
@ -50,7 +56,9 @@ impl<'a> System<'a> for FleeAI {
|
|||
}
|
||||
pos.x = (flee_target as i32) % map.width;
|
||||
pos.y = (flee_target as i32) / map.width;
|
||||
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert EntityMoved");
|
||||
entity_moved
|
||||
.insert(entity, EntityMoved {})
|
||||
.expect("Unable to insert EntityMoved");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{ gamelog, gui::renderable_colour, Name, Quips, Renderable, TakingTurn, Viewshed };
|
||||
use rltk::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
use specs::prelude::*;
|
||||
|
||||
pub struct QuipSystem {}
|
||||
|
|
@ -19,8 +19,18 @@ impl<'a> System<'a> for QuipSystem {
|
|||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
let (entities, mut quips, names, renderables, turns, player_pos, viewsheds, mut rng) = data;
|
||||
for (entity, quip, name, viewshed, _turn) in (&entities, &mut quips, &names, &viewsheds, &turns).join() {
|
||||
if !quip.available.is_empty() && viewshed.visible_tiles.contains(&player_pos) && rng.roll_dice(1, 6) == 1 {
|
||||
for (entity, quip, name, viewshed, _turn) in (
|
||||
&entities,
|
||||
&mut quips,
|
||||
&names,
|
||||
&viewsheds,
|
||||
&turns,
|
||||
).join() {
|
||||
if
|
||||
!quip.available.is_empty() &&
|
||||
viewshed.visible_tiles.contains(&player_pos) &&
|
||||
rng.roll_dice(1, 6) == 1
|
||||
{
|
||||
let quip_index = if quip.available.len() == 1 {
|
||||
0
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
Position,
|
||||
RandomNumberGenerator,
|
||||
TakingTurn,
|
||||
Intrinsics,
|
||||
};
|
||||
use specs::prelude::*;
|
||||
use crate::data::events::*;
|
||||
|
|
@ -36,10 +37,24 @@ impl<'a> System<'a> for RegenSystem {
|
|||
ReadStorage<'a, HasClass>,
|
||||
ReadStorage<'a, Attributes>,
|
||||
WriteExpect<'a, RandomNumberGenerator>,
|
||||
ReadStorage<'a, Intrinsics>,
|
||||
ReadExpect<'a, Entity>,
|
||||
);
|
||||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
let (clock, entities, positions, mut pools, turns, player, classes, attributes, mut rng) = data;
|
||||
let (
|
||||
clock,
|
||||
entities,
|
||||
positions,
|
||||
mut pools,
|
||||
turns,
|
||||
player,
|
||||
classes,
|
||||
attributes,
|
||||
mut rng,
|
||||
intrinsics,
|
||||
player_entity,
|
||||
) = data;
|
||||
let mut clock_turn = false;
|
||||
for (_e, _c, _t) in (&entities, &clock, &turns).join() {
|
||||
clock_turn = true;
|
||||
|
|
@ -56,19 +71,29 @@ impl<'a> System<'a> for RegenSystem {
|
|||
}
|
||||
// Player HP regen
|
||||
let level = gamelog::get_event_count(EVENT::COUNT_LEVEL);
|
||||
if current_turn % get_player_hp_regen_turn(level) == 0 {
|
||||
if
|
||||
current_turn % get_player_hp_regen_turn(level) == 0 ||
|
||||
intrinsics.get(*player_entity).unwrap().list.contains(&crate::Intrinsic::Regeneration)
|
||||
{
|
||||
for (_e, _p, pool, _player) in (&entities, &positions, &mut pools, &player).join() {
|
||||
try_hp_regen_tick(pool, get_player_hp_regen_per_tick(level));
|
||||
}
|
||||
}
|
||||
// Both MP regen
|
||||
for (e, _p, pool) in (&entities, &positions, &mut pools).join() {
|
||||
let is_wizard = if let Some(class) = classes.get(e) { class.name == Class::Wizard } else { false };
|
||||
let is_wizard = if let Some(class) = classes.get(e) {
|
||||
class.name == Class::Wizard
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let numerator = if is_wizard { WIZARD_MP_REGEN_MOD } else { NONWIZARD_MP_REGEN_MOD };
|
||||
let multiplier: f32 = (numerator as f32) / (MP_REGEN_DIVISOR as f32);
|
||||
let mp_regen_tick = (((MP_REGEN_BASE - pool.level) as f32) * multiplier) as i32;
|
||||
if current_turn % mp_regen_tick == 0 {
|
||||
try_mana_regen_tick(pool, rng.roll_dice(1, get_mana_regen_per_tick(e, &attributes)));
|
||||
try_mana_regen_tick(
|
||||
pool,
|
||||
rng.roll_dice(1, get_mana_regen_per_tick(e, &attributes))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
Item,
|
||||
Prop,
|
||||
};
|
||||
use rltk::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
use specs::prelude::*;
|
||||
use crate::data::events::*;
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ impl<'a> System<'a> for TurnStatusSystem {
|
|||
.colour(WHITE)
|
||||
.append("are confused!");
|
||||
log = true;
|
||||
gamelog::record_event(EVENT::PLAYER_CONFUSED(1));
|
||||
gamelog::record_event(EVENT::PlayerConfused(1));
|
||||
} else {
|
||||
logger = logger
|
||||
.append("The")
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
WantsToApproach,
|
||||
WantsToFlee,
|
||||
};
|
||||
use rltk::prelude::*;
|
||||
use bracket_lib::prelude::*;
|
||||
use specs::prelude::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
|
@ -81,10 +81,16 @@ impl<'a> System<'a> for VisibleAI {
|
|||
}
|
||||
reactions.sort_by(|(a, _, _), (b, _, _)| {
|
||||
let (a_x, a_y) = (a % (map.width as usize), a / (map.width as usize));
|
||||
let dist_a = DistanceAlg::PythagorasSquared.distance2d(Point::new(a_x, a_y), Point::new(pos.x, pos.y));
|
||||
let dist_a = DistanceAlg::PythagorasSquared.distance2d(
|
||||
Point::new(a_x, a_y),
|
||||
Point::new(pos.x, pos.y)
|
||||
);
|
||||
let dist_a_estimate = dist_a as i32;
|
||||
let (b_x, b_y) = (b % (map.width as usize), b / (map.width as usize));
|
||||
let dist_b = DistanceAlg::PythagorasSquared.distance2d(Point::new(b_x, b_y), Point::new(pos.x, pos.y));
|
||||
let dist_b = DistanceAlg::PythagorasSquared.distance2d(
|
||||
Point::new(b_x, b_y),
|
||||
Point::new(pos.x, pos.y)
|
||||
);
|
||||
let dist_b_estimate = dist_b as i32;
|
||||
return dist_b_estimate.cmp(&dist_a_estimate);
|
||||
});
|
||||
|
|
@ -96,7 +102,9 @@ impl<'a> System<'a> for VisibleAI {
|
|||
wants_to_approach
|
||||
.insert(entity, WantsToApproach { idx: reaction.0 as i32 })
|
||||
.expect("Error inserting WantsToApproach");
|
||||
chasing.insert(entity, Chasing { target: reaction.2 }).expect("Unable to insert Chasing");
|
||||
chasing
|
||||
.insert(entity, Chasing { target: reaction.2 })
|
||||
.expect("Unable to insert Chasing");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -108,7 +116,9 @@ impl<'a> System<'a> for VisibleAI {
|
|||
}
|
||||
}
|
||||
if !flee.is_empty() {
|
||||
wants_to_flee.insert(entity, WantsToFlee { indices: flee }).expect("Unable to insert");
|
||||
wants_to_flee
|
||||
.insert(entity, WantsToFlee { indices: flee })
|
||||
.expect("Unable to insert");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue