Skip to content

Commit

Permalink
[#112] Relationships (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
Orchaldir authored Nov 25, 2023
1 parent e937e72 commit c2479e6
Show file tree
Hide file tree
Showing 28 changed files with 731 additions and 103 deletions.
45 changes: 0 additions & 45 deletions resources/settings/eberron/characters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions resources/settings/eberron/relations/relationships.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Id0,Id1,Relation
0,1,Friend
2 changes: 2 additions & 0 deletions resources/settings/eberron/relations/romantic.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Id0,Id1,Relation
0,2,ExLover
17 changes: 17 additions & 0 deletions resources/templates/character/details.html.tera
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@
<p><a href="/character/edit/{{ id }}">Edit Data</a></p>
<p><a href="/character/delete/{{ id }}">Delete</a></p>
</div>
<p><h2>Relations</h2></p>
<div class="text">
<p><b>Relationships:</b> {{ relationships | length }}</p>
<ul>
{% for r in relationships %}
<li><a href="/character/details/{{ r.0 }}">{{ r.1 }}</a>: {{ r.2 }}</li>
{% endfor %}
</ul>
<p><a href="/relation/relationship/edit/{{ id }}">Edit Relationships</a></p>
<p><b>Romantic Relationships:</b> {{ romantic | length }}</p>
<ul>
{% for r in romantic %}
<li><a href="/character/details/{{ r.0 }}">{{ r.1 }}</a>: {{ r.2 }}</li>
{% endfor %}
</ul>
<p><a href="/relation/romantic/edit/{{ id }}">Edit Romantic Relationships</a></p>
</div>
<p><h2>Appearance</h2></p>
<center>
<img src = "/appearance/render/{{ id }}/front.svg" alt="Front View of the Character" width="25%"/>
Expand Down
9 changes: 5 additions & 4 deletions resources/templates/generic/delete.html.tera
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

{% block content %}
<p><h1>Failed to delete {{ name }}</h1></p>
{% if characters %}
<div class="text">
{% if characters %}
<p><b>Blocking Characters:</b> {{ characters | length }}</p>
<ul>
{% for c in characters %}
<li><a href="/character/details/{{ c.0 }}">{{ c.1 }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
<div class="text">
{% endif %}
{% if relations %}
<p><b>Blocking Relations:</b> {{ relations }}</p>
{% endif %}
<p><a href="/{{ endpoint }}/details/{{ id }}">Back</a></p>
</div>
{% endblock content %}
41 changes: 41 additions & 0 deletions resources/templates/generic/edit_relations.html.tera
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{% extends "base" %}

{% block content %}
<div class="left_right_container">
<p><h1>Edit {{ title }} of {{ name }}</h1></p>
<p><h2>Add</h2></p>
<div class="text">
<form action="/relation/{{ link }}/update/{{ id }}" method="post">
<b><label for="character">Name:</label></b>
<select id="character" name="character">
{% for c in characters %}
<option value="{{ c.0 }}">{{ c.1 }}</option>
{% endfor %}
</select>
<br>
<br>
<b><label for="relation">Type:</label></b>
<select id="relation" name="relation">
{% for t in types %}
<option value="{{ t }}">{{ t }}</option>
{% endfor %}
</select>
<br>
<br>
<input type="submit" value="Submit">
</form>
</div>
<p><h2>Delete</h2></p>
<div class="text">
<ul>
{% for r in relations %}
<li>
<a href="/relation/{{ link }}/delete/{{ id }}/{{ r.0 }}">Delete</a>
<a href="/character/details/{{ r.0 }}">{{ r.1 }}</a>: {{ r.2 }}
</li>
{% endfor %}
</ul>
<p><a href="/character/details/{{ id }}">Back</a></p>
</div>
</div>
{% endblock content %}
7 changes: 6 additions & 1 deletion rpg_tools_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
serde = { version = "1.0", features = ["derive"] }

[dev-dependencies]
tempdir = "0.3"
1 change: 1 addition & 0 deletions rpg_tools_core/src/model/character/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 2 additions & 0 deletions rpg_tools_core/src/model/character/relation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod relationship;
pub mod romantic;
13 changes: 13 additions & 0 deletions rpg_tools_core/src/model/character/relation/relationship.rs
Original file line number Diff line number Diff line change
@@ -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,
}
14 changes: 14 additions & 0 deletions rpg_tools_core/src/model/character/relation/romantic.rs
Original file line number Diff line number Diff line change
@@ -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,
}
4 changes: 4 additions & 0 deletions rpg_tools_core/src/model/mod.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -9,13 +10,15 @@ pub mod character;
pub mod culture;
pub mod equipment;
pub mod race;
pub mod relations;

#[derive(Debug)]
pub struct RpgData {
pub setting: String,
pub character_manager: Storage<CharacterId, Character>,
pub culture_manager: Storage<CultureId, Culture>,
pub race_manager: Storage<RaceId, Race>,
pub relations: Relations,
}

impl RpgData {
Expand All @@ -38,6 +41,7 @@ impl Default for RpgData {
character_manager: Default::default(),
culture_manager: Storage::default(),
race_manager: Storage::default(),
relations: Default::default(),
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions rpg_tools_core/src/model/relations.rs
Original file line number Diff line number Diff line change
@@ -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<CharacterId, Relationship>,
pub romantic: RelationStorage<CharacterId, RomanticRelationship>,
}

impl Relations {
/// Loads all relations from a file.
pub fn load(setting: &str) -> Result<Self> {
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(())
}
}
62 changes: 60 additions & 2 deletions rpg_tools_core/src/usecase/delete/character.rs
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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);
}
}
14 changes: 8 additions & 6 deletions rpg_tools_core/src/usecase/delete/culture.rs
Original file line number Diff line number Diff line change
@@ -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).
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
);
}
Expand Down
Loading

0 comments on commit c2479e6

Please sign in to comment.