use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use super::{
Closeable, Element, Enemy, Entity,
Item::{self, Container},
Lockable, Pathway,
};
use crate::types::{Action, Allies, Attack, CmdResult, Elements, Enemies, Items, Paths};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Room {
name: String,
desc: String,
paths: Paths,
#[serde(default)]
enemies: Enemies,
#[serde(default)]
allies: Allies,
#[serde(default)]
elements: Elements,
#[serde(default)]
items: Items,
}
impl Room {
pub fn long_desc(&self) -> String {
format!(
"{}\n{}{}{}{}{}{}",
self.name,
self.desc,
self.elements.iter().fold(String::new(), |desc, el| {
format!("{}\n{}", desc, el.desc())
}),
self.paths.iter().fold(String::new(), |desc, path| {
format!("{}\n{}", desc, path.long_desc())
}),
self.items.iter().fold(String::new(), |desc, item| {
format!("{}\n{}", desc, item.long_desc())
}),
self.allies.iter().fold(String::new(), |desc, ally| {
format!("{}\n{}", desc, ally.desc())
}),
self.enemies.iter().fold(String::new(), |desc, enemy| {
format!("{}\n{}", desc, enemy.long_desc())
}),
)
}
#[allow(clippy::borrowed_box)]
fn find_element(&self, name: &str) -> Option<&Box<Element>> {
if cfg!(target_arch = "wasm32") {
self.elements.iter().find(|el| {
el.name().split_whitespace().any(|el_word| {
name.split_whitespace()
.any(|name_word| name_word == el_word)
})
})
} else {
self.elements.par_iter().find_any(|el| {
el.name().par_split_whitespace().any(|el_word| {
name.par_split_whitespace()
.any(|name_word| name_word == el_word)
})
})
}
}
fn path_pos(&self, dir_name: &str) -> Option<usize> {
self.paths
.par_iter()
.position_any(|pathway| pathway.any_direction(dir_name))
}
#[allow(clippy::borrowed_box)]
pub fn find_path(&self, direction: &str) -> Option<&Box<Pathway>> {
if let Some(pos) = self.path_pos(direction) {
self.paths.get(pos)
} else {
None
}
}
#[allow(clippy::borrowed_box)]
pub fn find_path_mut(&mut self, direction: &str) -> Option<&mut Box<Pathway>> {
if let Some(pos) = self.path_pos(direction) {
self.paths.get_mut(pos)
} else {
None
}
}
pub fn drain_all(&mut self) -> Items {
self.items.drain(0..).collect()
}
pub fn give_from(
&mut self,
item_name: &str,
container_name: &str,
) -> Result<Box<Item>, CmdResult> {
if let Some(container) = self.find_item_mut(container_name) {
if let Container(ref mut container) = **container {
container.give_item(item_name)
} else {
Err(CmdResult::not_container(container_name))
}
} else {
Err(CmdResult::no_item_here(container_name))
}
}
pub fn insert_into(
&mut self,
item_name: &str,
container_name: &str,
item: Option<Box<Item>>,
) -> (CmdResult, Option<Box<Item>>) {
if let Some(item) = item {
if let Some(container) = self.find_item_mut(container_name) {
if let Container(ref mut container) = **container {
if container.is_closed() {
(
CmdResult::new(
Action::Active,
format!("The {} is closed.", container_name),
),
Some(item),
)
} else {
container.push_item(item);
(CmdResult::new(Action::Active, "Placed."), None)
}
} else {
(CmdResult::not_container(container_name), Some(item))
}
} else {
(CmdResult::no_item_here(container_name), Some(item))
}
} else {
(CmdResult::dont_have(item_name), None)
}
}
pub fn unlock(&mut self, name: &str) -> CmdResult {
if let Some(path) = self.find_path_mut(name) {
if path.is_locked() {
path.unlock()
} else {
CmdResult::already_unlocked(name)
}
} else {
CmdResult::no_item_here(name)
}
}
pub fn open(&mut self, name: &str) -> CmdResult {
if let Some(path) = self.find_path_mut(name) {
if path.is_locked() {
CmdResult::is_locked(name)
} else if path.is_closed() {
path.open()
} else {
CmdResult::already_opened(name)
}
} else if let Some(item) = self.find_item_mut(name) {
if let Container(ref mut container) = **item {
container.open()
} else {
CmdResult::not_container(name)
}
} else {
CmdResult::no_item_here(name)
}
}
pub fn close(&mut self, name: &str) -> CmdResult {
if let Some(path) = self.find_path_mut(name) {
if path.is_closed() {
CmdResult::already_closed(name)
} else {
path.close();
CmdResult::new(Action::Active, "Closed.")
}
} else if let Some(item) = self.find_item_mut(name) {
if let Container(ref mut item) = **item {
item.close()
} else {
CmdResult::not_container(name)
}
} else {
CmdResult::no_item_here(name)
}
}
pub fn hail(&self, _ally_name: &str) -> CmdResult {
CmdResult::new(Action::Passive, "Hail, friend.")
}
fn harm(&mut self, enemy: usize, enemy_name: &str, attack: Attack) -> CmdResult {
if let Some(enemy) = self.enemies.get_mut(enemy) {
if let Some(damage) = attack.damage() {
if let Some(res) = enemy.take_damage(damage) {
res
} else if enemy.is_alive() {
CmdResult::new(
Action::Active,
format!(
"You hit the {} with your {} for {} damage.",
enemy_name,
attack.weapon_name(),
damage,
),
)
} else {
CmdResult::new(
Action::Active,
format!(
"You hit the {} with your {} for {} damage. It is dead.\n{}",
enemy_name,
attack.weapon_name(),
damage,
if !enemy.loot().is_empty() {
let res = enemy
.loot()
.iter()
.fold(String::from("It dropped:\n"), |drops, loot| {
format!("{} {},", drops, loot.long_name())
});
if cfg!(target_arch = "wasm32") {
self.items.extend(enemy.drop_loot());
} else {
self.items.par_extend(enemy.drop_loot());
}
res
} else {
String::new()
}
),
)
}
} else {
CmdResult::dont_have(attack.weapon_name())
}
} else {
CmdResult::no_item_here(enemy_name)
}
}
pub fn harm_enemy(&mut self, enemy_name: &str, attack: Attack) -> CmdResult {
if let Some(enemy) = self.enemy_pos(enemy_name) {
self.harm(enemy, enemy_name, attack)
} else {
CmdResult::no_item_here(enemy_name)
}
}
pub fn inspect(&self, name: &str) -> Option<CmdResult> {
if let Some(item) = self.find_item(name) {
Some(CmdResult::new(Action::Active, item.inspect()))
} else if let Some(item) = self.find_element(name) {
Some(CmdResult::new(Action::Active, item.inspect()))
} else if let Some(pathway) = self.find_path(name) {
Some(CmdResult::new(Action::Active, pathway.inspect()))
} else if let Some(enemy) = self.find_enemy(name) {
Some(CmdResult::new(Action::Active, enemy.inspect()))
} else {
self.allies
.iter()
.find(|x| x.name() == name)
.map(|ally| CmdResult::new(Action::Active, ally.inspect()))
}
}
pub fn take_item(&mut self, name: &str, item: Option<Box<Item>>) -> CmdResult {
if let Some(item) = item {
self.items.push(item);
CmdResult::new(Action::Active, "Dropped.")
} else {
CmdResult::dont_have(name)
}
}
pub fn remove_item(&mut self, name: &str) -> Option<Box<Item>> {
if let Some(item) = self.item_pos(name) {
Some(self.items.remove(item))
} else {
None
}
}
pub fn add_element(&mut self, el: Element) {
self.elements.push(Box::new(el));
}
pub fn add_item(&mut self, item: Item) {
self.items.push(Box::new(item));
}
pub fn spawn_enemy(&mut self, enemy: Enemy) {
self.enemies.push(Box::new(enemy));
}
pub const fn enemies(&self) -> &Enemies {
&self.enemies
}
pub fn enemies_mut(&mut self) -> &mut Enemies {
&mut self.enemies
}
fn item_pos(&self, item_name: &str) -> Option<usize> {
if cfg!(target_arch = "wasm32") {
self.items
.iter()
.map(|item| item.name().split_whitespace().collect())
.position(|item: Vec<&str>| {
item_name
.split_whitespace()
.all(|ref word| item.contains(word))
})
} else {
self.items
.par_iter()
.map(|item| item.name().par_split_whitespace().collect())
.position_any(|item: Vec<&str>| {
item_name
.par_split_whitespace()
.all(|ref word| item.contains(word))
})
}
}
#[allow(clippy::borrowed_box)]
fn find_item(&self, item_name: &str) -> Option<&Box<Item>> {
if let Some(pos) = self.item_pos(item_name) {
self.items.get(pos)
} else {
None
}
}
#[allow(clippy::borrowed_box)]
fn find_item_mut(&mut self, item_name: &str) -> Option<&mut Box<Item>> {
if let Some(pos) = self.item_pos(item_name) {
self.items.get_mut(pos)
} else {
None
}
}
fn enemy_pos(&self, enemy_name: &str) -> Option<usize> {
if cfg!(target_arch = "wasm32") {
self.enemies
.iter()
.map(|enemy| enemy.name().split_whitespace().collect())
.position(|enemy: Vec<&str>| {
enemy_name
.split_whitespace()
.all(|ref word| enemy.contains(word))
})
} else {
self.enemies
.par_iter()
.map(|enemy| enemy.name().par_split_whitespace().collect())
.position_any(|enemy: Vec<&str>| {
enemy_name
.par_split_whitespace()
.all(|ref word| enemy.contains(word))
})
}
}
#[allow(clippy::borrowed_box)]
fn find_enemy(&self, enemy_name: &str) -> Option<&Box<Enemy>> {
if let Some(pos) = self.enemy_pos(enemy_name) {
self.enemies.get(pos)
} else {
None
}
}
}
impl Entity for Room {
fn name(&self) -> &str {
&self.name
}
fn desc(&self) -> &str {
&self.desc
}
fn inspect(&self) -> &str {
&self.desc
}
}