diff --git a/resources/settings/eberron/characters.yaml b/resources/settings/eberron/characters.yaml index f8081246..86251542 100644 --- a/resources/settings/eberron/characters.yaml +++ b/resources/settings/eberron/characters.yaml @@ -241,48 +241,3 @@ color: White height: millimetre: 1700 -- id: 3 - name: Three - race: 4 - culture: 2 - gender: Genderless - appearance: - type: Humanoid - body: - shape: Rectangle - width: Average - skin: - type: ExoticSkin - color: Gray - clothing: - type: None - head: - ears: - type: None - eyes: - type: Two - eye: - type: Simple - eye_shape: Ellipse - color: Lime - eyebrows: - type: None - distance: Medium - eyewear: - type: None - hair: - type: None - mouth: - type: Simple - beard: - type: None - width: Medium - teeth: - type: None - teeth_color: White - shape: Rectangle - skin: - type: ExoticSkin - color: Gray - height: - millimetre: 1500 diff --git a/resources/settings/eberron/relations/relationships.csv b/resources/settings/eberron/relations/relationships.csv new file mode 100644 index 00000000..3b809736 --- /dev/null +++ b/resources/settings/eberron/relations/relationships.csv @@ -0,0 +1,2 @@ +Id0,Id1,Relation +0,1,Friend diff --git a/resources/settings/eberron/relations/romantic.csv b/resources/settings/eberron/relations/romantic.csv new file mode 100644 index 00000000..1555e348 --- /dev/null +++ b/resources/settings/eberron/relations/romantic.csv @@ -0,0 +1,2 @@ +Id0,Id1,Relation +0,2,ExLover diff --git a/resources/templates/character/details.html.tera b/resources/templates/character/details.html.tera index d1f8ec16..982f075c 100644 --- a/resources/templates/character/details.html.tera +++ b/resources/templates/character/details.html.tera @@ -12,6 +12,23 @@

Edit Data

Delete

+

Relations

+
+

Relationships: {{ relationships | length }}

+ +

Edit Relationships

+

Romantic Relationships: {{ romantic | length }}

+ +

Edit Romantic Relationships

+

Appearance

Front View of the Character diff --git a/resources/templates/generic/delete.html.tera b/resources/templates/generic/delete.html.tera index 05b28e57..919dbd4d 100644 --- a/resources/templates/generic/delete.html.tera +++ b/resources/templates/generic/delete.html.tera @@ -2,17 +2,18 @@ {% block content %}

Failed to delete {{ name }}

-{% if characters %}
+ {% if characters %}

Blocking Characters: {{ characters | length }}

-
-{% endif %} -
+ {% endif %} + {% if relations %} +

Blocking Relations: {{ relations }}

+ {% endif %}

Back

{% endblock content %} diff --git a/resources/templates/generic/edit_relations.html.tera b/resources/templates/generic/edit_relations.html.tera new file mode 100644 index 00000000..40bfc692 --- /dev/null +++ b/resources/templates/generic/edit_relations.html.tera @@ -0,0 +1,41 @@ +{% extends "base" %} + +{% block content %} +
+

Edit {{ title }} of {{ name }}

+

Add

+
+
+ + +
+
+ + +
+
+ +
+
+

Delete

+
+
    + {% for r in relations %} +
  • + Delete + {{ r.1 }}: {{ r.2 }} +
  • + {% endfor %} +
+

Back

+
+
+{% endblock content %} diff --git a/rpg_tools_core/Cargo.toml b/rpg_tools_core/Cargo.toml index e2fe46d7..e02a0829 100644 --- a/rpg_tools_core/Cargo.toml +++ b/rpg_tools_core/Cargo.toml @@ -7,7 +7,12 @@ edition = "2021" [dependencies] anyhow = "1.0" +csv = "1.3.0" +itertools = "0.12.0" macro_core = { path = "../macro_core" } macro_convert = { path = "../macro_convert" } macro_ui = { path = "../macro_ui" } -serde = { version = "1.0", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0", features = ["derive"] } + +[dev-dependencies] +tempdir = "0.3" \ No newline at end of file diff --git a/rpg_tools_core/src/model/character/mod.rs b/rpg_tools_core/src/model/character/mod.rs index cac08c3c..160b08f0 100644 --- a/rpg_tools_core/src/model/character/mod.rs +++ b/rpg_tools_core/src/model/character/mod.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; pub mod appearance; pub mod gender; +pub mod relation; /// The unique identifier of a [`character`](Character). #[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] diff --git a/rpg_tools_core/src/model/character/relation/mod.rs b/rpg_tools_core/src/model/character/relation/mod.rs new file mode 100644 index 00000000..d4cd8a0a --- /dev/null +++ b/rpg_tools_core/src/model/character/relation/mod.rs @@ -0,0 +1,2 @@ +pub mod relationship; +pub mod romantic; diff --git a/rpg_tools_core/src/model/character/relation/relationship.rs b/rpg_tools_core/src/model/character/relation/relationship.rs new file mode 100644 index 00000000..c8346b35 --- /dev/null +++ b/rpg_tools_core/src/model/character/relation/relationship.rs @@ -0,0 +1,13 @@ +use macro_convert::Convert; +use serde::{Deserialize, Serialize}; + +pub const RELATIONSHIPS_FILE: &str = "relations/relationships.csv"; + +/// The relationship types between 2 [`characters`](crate::model::character::Character) +/// that are not romantic or between family. +#[derive(Convert, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum Relationship { + Enemy, + Friend, + Rival, +} diff --git a/rpg_tools_core/src/model/character/relation/romantic.rs b/rpg_tools_core/src/model/character/relation/romantic.rs new file mode 100644 index 00000000..4dd8bfc1 --- /dev/null +++ b/rpg_tools_core/src/model/character/relation/romantic.rs @@ -0,0 +1,14 @@ +use macro_convert::Convert; +use serde::{Deserialize, Serialize}; + +pub const ROMANTIC_FILE: &str = "relations/romantic.csv"; + +/// The relationship types between 2 [`characters`](crate::model::character::Character) +/// that are romantic. +#[derive(Convert, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum RomanticRelationship { + ExLover, + ExSpouse, + Lover, + Spouse, +} diff --git a/rpg_tools_core/src/model/mod.rs b/rpg_tools_core/src/model/mod.rs index a9609db7..48d0135e 100644 --- a/rpg_tools_core/src/model/mod.rs +++ b/rpg_tools_core/src/model/mod.rs @@ -1,6 +1,7 @@ use crate::model::character::{Character, CharacterId}; use crate::model::culture::{Culture, CultureId}; use crate::model::race::{Race, RaceId}; +use crate::model::relations::Relations; use crate::utils::storage::Storage; use std::path::PathBuf; @@ -9,6 +10,7 @@ pub mod character; pub mod culture; pub mod equipment; pub mod race; +pub mod relations; #[derive(Debug)] pub struct RpgData { @@ -16,6 +18,7 @@ pub struct RpgData { pub character_manager: Storage, pub culture_manager: Storage, pub race_manager: Storage, + pub relations: Relations, } impl RpgData { @@ -38,6 +41,7 @@ impl Default for RpgData { character_manager: Default::default(), culture_manager: Storage::default(), race_manager: Storage::default(), + relations: Default::default(), } } } diff --git a/rpg_tools_core/src/model/relations.rs b/rpg_tools_core/src/model/relations.rs new file mode 100644 index 00000000..e5ed57b5 --- /dev/null +++ b/rpg_tools_core/src/model/relations.rs @@ -0,0 +1,42 @@ +use crate::model::character::relation::relationship::{Relationship, RELATIONSHIPS_FILE}; +use crate::model::character::relation::romantic::{RomanticRelationship, ROMANTIC_FILE}; +use crate::model::character::CharacterId; +use crate::model::get_setting_path; +use crate::utils::relation::{load_relations, RelationStorage}; +use anyhow::{Context, Result}; + +#[derive(Debug, Default)] +pub struct Relations { + pub relationships: RelationStorage, + pub romantic: RelationStorage, +} + +impl Relations { + /// Loads all relations from a file. + pub fn load(setting: &str) -> Result { + let relationships = load_relations(&get_setting_path(setting, RELATIONSHIPS_FILE))?; + let romantic = load_relations(&get_setting_path(setting, ROMANTIC_FILE))?; + + Ok(Self { + relationships, + romantic, + }) + } + + /// Counts all relations of a character. + pub fn count_for_character(&self, id: CharacterId) -> usize { + self.relationships.count_all_of(id) + self.romantic.count_all_of(id) + } + + /// Swaps a character id with another. + pub fn swap_character(&mut self, old: CharacterId, new: CharacterId) -> Result<()> { + self.relationships + .swap(old, new) + .context("Failed to swap character for relationships")?; + self.romantic + .swap(old, new) + .context("Failed to swap character for romantic relationships")?; + + Ok(()) + } +} diff --git a/rpg_tools_core/src/usecase/delete/character.rs b/rpg_tools_core/src/usecase/delete/character.rs index 066bd6c7..791ae6ad 100644 --- a/rpg_tools_core/src/usecase/delete/character.rs +++ b/rpg_tools_core/src/usecase/delete/character.rs @@ -1,21 +1,42 @@ use crate::model::character::CharacterId; use crate::model::RpgData; -use crate::usecase::delete::DeleteResult; +use crate::usecase::delete::{BlockingReason, DeleteResult}; use crate::utils::storage::DeleteElementResult; /// Tries to delete a [`character`](crate::model::character::Character). pub fn delete_character(data: &mut RpgData, id: CharacterId) -> DeleteResult { + let relations = data.relations.count_for_character(id); + + if relations > 0 { + return DeleteResult::Blocked(BlockingReason { + characters: vec![], + relations, + }); + } + match data.character_manager.delete(id) { DeleteElementResult::NotFound => DeleteResult::NotFound, - _ => DeleteResult::Ok, + DeleteElementResult::DeletedLastElement => DeleteResult::Ok, + DeleteElementResult::SwappedAndRemoved { id_to_update } => { + data.relations.swap_character(id_to_update, id).unwrap(); + + DeleteResult::Ok + } } } #[cfg(test)] mod tests { use super::*; + use crate::model::character::relation::relationship::Relationship::Friend; + use crate::model::character::relation::romantic::RomanticRelationship::Spouse; use DeleteResult::*; + const RESULT: DeleteResult = Blocked(BlockingReason { + characters: vec![], + relations: 1, + }); + #[test] fn test_not_found() { let mut data = RpgData::default(); @@ -33,4 +54,41 @@ mod tests { assert_eq!(Ok, delete_character(&mut data, id)); } + + #[test] + fn test_blocked_by_relationship() { + let mut data = RpgData::default(); + let id0 = data.character_manager.create(); + let id1 = data.character_manager.create(); + data.relations.relationships.add(id0, id1, Friend); + + assert_eq!(RESULT, delete_character(&mut data, id0)); + assert_eq!(RESULT, delete_character(&mut data, id1)); + } + + #[test] + fn test_blocked_by_romantic() { + let mut data = RpgData::default(); + let id0 = data.character_manager.create(); + let id1 = data.character_manager.create(); + data.relations.romantic.add(id0, id1, Spouse); + + assert_eq!(RESULT, delete_character(&mut data, id0)); + assert_eq!(RESULT, delete_character(&mut data, id1)); + } + + #[test] + fn test_update_relations_with_moved_character() { + let mut data = RpgData::default(); + data.culture_manager.create(); + let id0 = data.character_manager.create(); + let id1 = data.character_manager.create(); + let id2 = data.character_manager.create(); + data.relations.relationships.add(id1, id2, Friend); + + assert_eq!(Ok, delete_character(&mut data, id0)); + assert_eq!(data.relations.relationships.get(id0, id1), Some(&Friend)); + assert_eq!(data.relations.relationships.get(id1, id2), None); + assert_eq!(data.relations.relationships.get(id0, id2), None); + } } diff --git a/rpg_tools_core/src/usecase/delete/culture.rs b/rpg_tools_core/src/usecase/delete/culture.rs index 5aea0c01..20a72307 100644 --- a/rpg_tools_core/src/usecase/delete/culture.rs +++ b/rpg_tools_core/src/usecase/delete/culture.rs @@ -1,7 +1,7 @@ use crate::model::character::CharacterId; use crate::model::culture::CultureId; use crate::model::RpgData; -use crate::usecase::delete::DeleteResult; +use crate::usecase::delete::{BlockingReason, DeleteResult}; use crate::utils::storage::{DeleteElementResult, Element}; /// Tries to delete a [`culture`](crate::model::culture::Culture). @@ -15,9 +15,10 @@ pub fn delete_culture(data: &mut RpgData, id: CultureId) -> DeleteResult { .collect(); if !blocking_characters.is_empty() { - return DeleteResult::Blocked { + return DeleteResult::Blocked(BlockingReason { characters: blocking_characters, - }; + relations: 0, + }); } match data.culture_manager.delete(id) { @@ -65,9 +66,10 @@ mod tests { .map(|character| character.set_culture(culture_id)); assert_eq!( - Blocked { - characters: vec![character_id] - }, + Blocked(BlockingReason { + characters: vec![character_id], + relations: 0, + }), delete_culture(&mut data, culture_id) ); } diff --git a/rpg_tools_core/src/usecase/delete/mod.rs b/rpg_tools_core/src/usecase/delete/mod.rs index 506725aa..94f2dc6c 100644 --- a/rpg_tools_core/src/usecase/delete/mod.rs +++ b/rpg_tools_core/src/usecase/delete/mod.rs @@ -8,5 +8,11 @@ pub mod race; pub enum DeleteResult { Ok, NotFound, - Blocked { characters: Vec }, + Blocked(BlockingReason), +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct BlockingReason { + pub characters: Vec, + pub relations: usize, } diff --git a/rpg_tools_core/src/usecase/delete/race.rs b/rpg_tools_core/src/usecase/delete/race.rs index 444b384b..a9def8fa 100644 --- a/rpg_tools_core/src/usecase/delete/race.rs +++ b/rpg_tools_core/src/usecase/delete/race.rs @@ -1,7 +1,7 @@ use crate::model::character::CharacterId; use crate::model::race::RaceId; use crate::model::RpgData; -use crate::usecase::delete::DeleteResult; +use crate::usecase::delete::{BlockingReason, DeleteResult}; use crate::utils::storage::{DeleteElementResult, Element}; /// Tries to delete a [`race`](crate::model::race::Race). @@ -15,9 +15,10 @@ pub fn delete_race(data: &mut RpgData, id: RaceId) -> DeleteResult { .collect(); if !blocking_characters.is_empty() { - return DeleteResult::Blocked { + return DeleteResult::Blocked(BlockingReason { characters: blocking_characters, - }; + relations: 0, + }); } match data.race_manager.delete(id) { @@ -65,9 +66,10 @@ mod tests { .map(|character| character.set_race(race_id)); assert_eq!( - Blocked { - characters: vec![character_id] - }, + Blocked(BlockingReason { + characters: vec![character_id], + relations: 0, + }), delete_race(&mut data, race_id) ); } diff --git a/rpg_tools_core/src/utils/mod.rs b/rpg_tools_core/src/utils/mod.rs index 30f61eb6..6e3e5895 100644 --- a/rpg_tools_core/src/utils/mod.rs +++ b/rpg_tools_core/src/utils/mod.rs @@ -1 +1,2 @@ +pub mod relation; pub mod storage; diff --git a/rpg_tools_core/src/utils/relation.rs b/rpg_tools_core/src/utils/relation.rs new file mode 100644 index 00000000..f42a58d7 --- /dev/null +++ b/rpg_tools_core/src/utils/relation.rs @@ -0,0 +1,204 @@ +use crate::utils::storage::Id; +use anyhow::{bail, Context, Result}; +use itertools::Itertools; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::fmt::Display; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +#[derive(Debug, PartialEq)] +pub struct RelationStorage { + relations: HashMap>, +} + +impl RelationStorage { + pub fn new(relations: HashMap>) -> Self { + Self { relations } + } + + /// Does it contain a specific element? + pub fn contains(&self, id: I) -> bool { + self.relations.contains_key(&id) + } + + /// Counts all relations for a specific element. + pub fn count_all_of(&self, id: I) -> usize { + self.relations + .get(&id) + .map(|map| map.len()) + .unwrap_or_default() + } + + /// Gets all relations for a specific element. + pub fn get_all_of(&self, id: I) -> Option<&HashMap> { + self.relations.get(&id) + } + + /// Gets the relation between 2 elements. + pub fn get(&self, from: I, to: I) -> Option<&T> { + self.relations.get(&from).and_then(|map| map.get(&to)) + } + + /// Adds a relation between 2 elements. + pub fn add(&mut self, from: I, to: I, relation: T) { + self.add_one_direction(from, to, relation.clone()); + self.add_one_direction(to, from, relation); + } + + fn add_one_direction(&mut self, from: I, to: I, relation: T) { + self.relations.entry(from).or_default().insert(to, relation); + } + + /// Deletes the relation between 2 elements. + pub fn delete(&mut self, from: I, to: I) { + self.delete_one_direction(from, to); + self.delete_one_direction(to, from); + } + + fn delete_one_direction(&mut self, from: I, to: I) { + if let Entry::Occupied(mut e) = self.relations.entry(from) { + e.get_mut().remove(&to); + } + } + + /// Swaps an id with another. + pub fn swap(&mut self, old: I, new: I) -> Result<()> { + if self.contains(new) { + bail!( + "Cannot switch id from {} to {}, because it is already contained!", + old.id(), + new.id() + ) + } + + if let Some(relations) = self.relations.remove(&old) { + for (id1, relation) in relations { + self.delete_one_direction(id1, old); + self.add(new, id1, relation); + } + } + + Ok(()) + } + + /// Saves the relations to a file. + pub fn save(&self, path: &Path) -> Result<()> { + let mut file = File::create(path).context(format!("Failed to create {:?}", path))?; + + writeln!(file, "Id0,Id1,Relation")?; + + for (id0, relations) in self.relations.iter().sorted_by_key(|x| x.0.id()) { + for (id1, relation) in relations.iter().sorted_by_key(|x| x.0.id()) { + if id0.id() < id1.id() { + writeln!(file, "{},{},{}", id0.id(), id1.id(), relation)?; + } + } + } + + Ok(()) + } +} + +impl Default for RelationStorage { + fn default() -> Self { + RelationStorage::new(HashMap::new()) + } +} + +/// Loads the relations from a file. +pub fn load_relations From<&'a str>>( + path: &Path, +) -> Result> { + let mut reader = csv::Reader::from_path(path).context(format!("Failed to load {:?}", path))?; + let mut storage = RelationStorage::new(HashMap::new()); + + for (line, result) in reader.records().enumerate() { + let record = result.with_context(|| format!("Cannot read line {}", line))?; + + let id0: usize = record[0] + .parse() + .with_context(|| format!("Failed to parse id0 of line {}", line))?; + let id1: usize = record[1] + .parse() + .with_context(|| format!("Failed to parse id1 of line {}", line))?; + let relation: T = record[2].into(); + storage.add(Id::new(id0), Id::new(id1), relation); + } + + Ok(storage) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::character::relation::relationship::Relationship; + use crate::model::character::relation::relationship::Relationship::{Enemy, Friend}; + use crate::model::character::CharacterId; + use tempdir::TempDir; + + #[test] + fn test_io() { + let storage = init_storage(); + + let dir = TempDir::new("test").unwrap(); + let file_path = dir.path().join("relations.csv"); + + assert!(storage.save(&file_path).is_ok()); + assert_eq!(storage, load_relations(&file_path).unwrap()); + } + + #[test] + fn test_swap() { + let mut storage = init_storage(); + + assert!(storage + .swap(CharacterId::new(2), CharacterId::new(1)) + .is_ok()); + assert_swap(&mut storage, 1, 3, 4); + } + + #[test] + fn test_swap_unknown_new() { + let mut storage = init_storage(); + + assert!(storage + .swap(CharacterId::new(0), CharacterId::new(1)) + .is_ok()); + assert_swap(&mut storage, 2, 3, 4); + } + + #[test] + fn test_swap_known_old() { + let mut storage = init_storage(); + + assert!(storage + .swap(CharacterId::new(2), CharacterId::new(3)) + .is_err()); + assert_swap(&mut storage, 2, 3, 4); + } + + fn assert_swap( + storage: &mut RelationStorage, + id0: usize, + id1: usize, + id2: usize, + ) { + let id0 = CharacterId::new(id0); + let id1 = CharacterId::new(id1); + let id2 = CharacterId::new(id2); + assert_eq!(storage.get(id0, id1), Some(&Friend)); + assert_eq!(storage.get(id1, id0), Some(&Friend)); + assert_eq!(storage.get(id0, id2), Some(&Enemy)); + assert_eq!(storage.get(id2, id0), Some(&Enemy)); + } + + fn init_storage() -> RelationStorage { + let mut storage = RelationStorage::default(); + let id0 = CharacterId::new(2); + storage.add(id0, CharacterId::new(3), Friend); + storage.add(id0, CharacterId::new(4), Enemy); + storage + } +} diff --git a/rpg_tools_core/src/utils/storage.rs b/rpg_tools_core/src/utils/storage.rs index a4009579..35aeb0a7 100644 --- a/rpg_tools_core/src/utils/storage.rs +++ b/rpg_tools_core/src/utils/storage.rs @@ -1,6 +1,7 @@ +use std::hash::Hash; use std::marker::PhantomData; -pub trait Id: Copy { +pub trait Id: Copy + Hash + Eq { fn new(id: usize) -> Self; fn id(&self) -> usize; diff --git a/rpg_tools_editor/src/main.rs b/rpg_tools_editor/src/main.rs index 9d05e14f..6237ffb2 100644 --- a/rpg_tools_editor/src/main.rs +++ b/rpg_tools_editor/src/main.rs @@ -19,6 +19,12 @@ use crate::route::race::{ add_race, delete_race_route, edit_race, get_all_races, get_race_details, update_race, RACES_FILE, }; +use crate::route::relation::relationship::{ + delete_relationship, edit_relationships, update_relationship, +}; +use crate::route::relation::romantic::{ + delete_romantic_relation, edit_romantic_relations, update_romantic_relation, +}; use anyhow::{bail, Result}; use rocket::fs::FileServer; use rocket::State; @@ -27,6 +33,7 @@ use rpg_tools_core::model::character::appearance::Appearance; use rpg_tools_core::model::character::Character; use rpg_tools_core::model::culture::Culture; use rpg_tools_core::model::race::Race; +use rpg_tools_core::model::relations::Relations; use rpg_tools_core::model::{get_setting_path, RpgData}; use rpg_tools_core::utils::storage::Storage; use rpg_tools_rendering::rendering::config::example::create_config; @@ -103,6 +110,12 @@ async fn main() -> Result<()> { edit_culture, update_culture, delete_culture_route, + edit_relationships, + delete_relationship, + update_relationship, + edit_romantic_relations, + delete_romantic_relation, + update_romantic_relation, ], ) .attach(Template::fairing()) @@ -157,10 +170,19 @@ fn init(setting: &str) -> RpgData { } }; + let relations = match Relations::load(setting) { + Ok(relations) => relations, + Err(e) => { + println!("Failed to load the relations: {}", e); + return RpgData::empty(setting.to_string()); + } + }; + RpgData { setting: setting.to_string(), character_manager, culture_manager, race_manager, + relations, } } diff --git a/rpg_tools_editor/src/route/character.rs b/rpg_tools_editor/src/route/character.rs index 0394ac47..041bf810 100644 --- a/rpg_tools_editor/src/route/character.rs +++ b/rpg_tools_editor/src/route/character.rs @@ -1,5 +1,6 @@ use crate::io::write; use crate::route::get_failed_delete_template; +use crate::route::relation::get_relations; use crate::EditorData; use rocket::form::Form; use rocket::State; @@ -8,7 +9,7 @@ use rpg_tools_core::model::character::gender::Gender; use rpg_tools_core::model::character::{Character, CharacterId}; use rpg_tools_core::model::RpgData; use rpg_tools_core::usecase::delete::character::delete_character; -use rpg_tools_core::usecase::delete::DeleteResult; +use rpg_tools_core::usecase::delete::{BlockingReason, DeleteResult}; use rpg_tools_core::usecase::edit::character::{ update_character_culture, update_character_gender, update_character_name, update_character_race, }; @@ -129,20 +130,23 @@ pub fn delete_character_route(data: &State, id: usize) -> Template { let character_id = CharacterId::new(id); let result = delete_character(&mut data, character_id); + let name = data + .character_manager + .get(character_id) + .map(|character| character.name()) + .unwrap_or("Unknown") + .to_string(); match result { DeleteResult::Ok => { write_characters(&data); get_all_template(data) } - _ => { - let name = data - .character_manager - .get(character_id) - .map(|character| character.name()) - .unwrap_or("Unknown") - .to_string(); - get_failed_delete_template(data, "character", id, &name, result) + DeleteResult::NotFound => { + get_failed_delete_template(data, "character", id, &name, BlockingReason::default()) + } + DeleteResult::Blocked(reason) => { + get_failed_delete_template(data, "character", id, &name, reason) } } } @@ -186,6 +190,8 @@ fn get_details_template(data: &RpgData, id: usize, character: &Character) -> Tem culture: culture, gender: character.gender(), appearance: character.appearance(), + relationships: get_relations(data, &data.relations.relationships, character.id()), + romantic: get_relations(data, &data.relations.romantic, character.id()), }, ) } diff --git a/rpg_tools_editor/src/route/culture.rs b/rpg_tools_editor/src/route/culture.rs index 7f155af3..505a433f 100644 --- a/rpg_tools_editor/src/route/culture.rs +++ b/rpg_tools_editor/src/route/culture.rs @@ -7,7 +7,7 @@ use rocket_dyn_templates::{context, Template}; use rpg_tools_core::model::culture::{Culture, CultureId}; use rpg_tools_core::model::RpgData; use rpg_tools_core::usecase::delete::culture::delete_culture; -use rpg_tools_core::usecase::delete::DeleteResult; +use rpg_tools_core::usecase::delete::{BlockingReason, DeleteResult}; use rpg_tools_core::usecase::edit::culture::update_culture_name; use rpg_tools_core::utils::storage::{Element, Id}; use std::sync::MutexGuard; @@ -93,20 +93,23 @@ pub fn delete_culture_route(data: &State, id: usize) -> Template { let culture_id = CultureId::new(id); let result = delete_culture(&mut data, culture_id); + let name = data + .culture_manager + .get(culture_id) + .map(|culture| culture.name()) + .unwrap_or("Unknown") + .to_string(); match result { DeleteResult::Ok => { save_cultures(&data); get_all_template(data) } - _ => { - let name = data - .culture_manager - .get(culture_id) - .map(|culture| culture.name()) - .unwrap_or("Unknown") - .to_string(); - get_failed_delete_template(data, "culture", id, &name, result) + DeleteResult::NotFound => { + get_failed_delete_template(data, "culture", id, &name, BlockingReason::default()) + } + DeleteResult::Blocked(reason) => { + get_failed_delete_template(data, "culture", id, &name, reason) } } } diff --git a/rpg_tools_editor/src/route/mod.rs b/rpg_tools_editor/src/route/mod.rs index 763c7882..f24cca90 100644 --- a/rpg_tools_editor/src/route/mod.rs +++ b/rpg_tools_editor/src/route/mod.rs @@ -1,6 +1,6 @@ use rocket_dyn_templates::{context, Template}; use rpg_tools_core::model::RpgData; -use rpg_tools_core::usecase::delete::DeleteResult; +use rpg_tools_core::usecase::delete::BlockingReason; use rpg_tools_core::utils::storage::{Element, Id}; use std::sync::MutexGuard; @@ -8,22 +8,21 @@ pub mod appearance; pub mod character; pub mod culture; pub mod race; +pub mod relation; pub fn get_failed_delete_template( data: MutexGuard, endpoint: &str, id: usize, name: &str, - result: DeleteResult, + reason: BlockingReason, ) -> Template { - let characters = match result { - DeleteResult::Blocked { characters } => characters - .iter() - .flat_map(|id| data.character_manager.get(*id)) - .map(|r| (r.id().id(), r.name())) - .collect(), - _ => Vec::new(), - }; + let characters: Vec<(usize, &str)> = reason + .characters + .iter() + .flat_map(|id| data.character_manager.get(*id)) + .map(|r| (r.id().id(), r.name())) + .collect(); Template::render( "generic/delete", @@ -32,6 +31,7 @@ pub fn get_failed_delete_template( id: id, name: name, characters: characters, + relations: reason.relations, }, ) } diff --git a/rpg_tools_editor/src/route/race.rs b/rpg_tools_editor/src/route/race.rs index 848db877..e0d26445 100644 --- a/rpg_tools_editor/src/route/race.rs +++ b/rpg_tools_editor/src/route/race.rs @@ -8,7 +8,7 @@ use rpg_tools_core::model::race::gender::GenderOption; use rpg_tools_core::model::race::{Race, RaceId}; use rpg_tools_core::model::RpgData; use rpg_tools_core::usecase::delete::race::delete_race; -use rpg_tools_core::usecase::delete::DeleteResult; +use rpg_tools_core::usecase::delete::{BlockingReason, DeleteResult}; use rpg_tools_core::usecase::edit::race::{update_gender_option, update_race_name}; use rpg_tools_core::utils::storage::{Element, Id}; use std::sync::MutexGuard; @@ -102,20 +102,23 @@ pub fn delete_race_route(data: &State, id: usize) -> Template { let race_id = RaceId::new(id); let result = delete_race(&mut data, race_id); + let name = data + .race_manager + .get(race_id) + .map(|race| race.name()) + .unwrap_or("Unknown") + .to_string(); match result { DeleteResult::Ok => { save_races(&data); get_all_template(data) } - _ => { - let name = data - .race_manager - .get(race_id) - .map(|race| race.name()) - .unwrap_or("Unknown") - .to_string(); - get_failed_delete_template(data, "race", id, &name, result) + DeleteResult::NotFound => { + get_failed_delete_template(data, "race", id, &name, BlockingReason::default()) + } + DeleteResult::Blocked(reason) => { + get_failed_delete_template(data, "race", id, &name, reason) } } } diff --git a/rpg_tools_editor/src/route/relation/mod.rs b/rpg_tools_editor/src/route/relation/mod.rs new file mode 100644 index 00000000..ecb8b39d --- /dev/null +++ b/rpg_tools_editor/src/route/relation/mod.rs @@ -0,0 +1,81 @@ +use rocket_dyn_templates::{context, Template}; +use rpg_tools_core::model::character::CharacterId; +use rpg_tools_core::model::RpgData; +use rpg_tools_core::utils::relation::RelationStorage; +use rpg_tools_core::utils::storage::Element; +use rpg_tools_core::utils::storage::Id; +use serde::Serialize; +use std::fmt::Display; + +pub mod relationship; +pub mod romantic; + +#[derive(FromForm, Debug)] +pub struct RelationUpdate<'r> { + character: usize, + relation: &'r str, +} + +pub fn get_edit_relations_template( + data: &RpgData, + id: CharacterId, + relations: &RelationStorage, + title: &str, + link: &str, + types: Vec, +) -> Option