Skip to content

Commit

Permalink
feat: add pf version filter, remove alignment from trait list (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
RakuJa authored May 27, 2024
1 parent 69237f1 commit a506f49
Show file tree
Hide file tree
Showing 17 changed files with 149 additions and 133 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ build = "build/main.rs"
unsafe_code = "forbid"

[dependencies]
actix-web = "4.5.1"
actix-web = "4.6.0"
actix-cors = "0.7.0"
actix-web-validator = "5.0.1"
# Cannot be updated until actix-web updates validator dependency
Expand Down
3 changes: 2 additions & 1 deletion build/creature_core_db_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ pub async fn create_creature_core_table(conn: &Pool<Sqlite>) -> Result<()> {
family TEXT NOT NULL DEFAULT '-',
license TEXT NOT NULL DEFAULT '',
source TEXT NOT NULL DEFAULT '',
remaster BOOL NOT NULL DEFAULT 0
remaster BOOL NOT NULL DEFAULT 0,
alignment TEXT NOT NULL DEFAULT NO
)"
)
.execute(conn)
Expand Down
26 changes: 25 additions & 1 deletion src/db/cr_core_initializer.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::db::data_providers::fetcher::{
fetch_creature_combat_data, fetch_creature_extra_data, fetch_creature_scales,
fetch_creature_spell_caster_data,
fetch_creature_spell_caster_data, fetch_creature_traits,
};
use crate::models::creature_component::creature_core::EssentialData;
use crate::models::creature_metadata::alignment_enum::AlignmentEnum;
use crate::models::creature_metadata::creature_role::CreatureRoleEnum;
use crate::models::creature_metadata::rarity_enum::RarityEnum;
use crate::models::creature_metadata::size_enum::SizeEnum;
Expand All @@ -21,6 +22,9 @@ pub async fn update_creature_core_table(conn: &Pool<Sqlite>) -> Result<()> {
};
let scales = fetch_creature_scales(conn).await?;
for cr in get_creatures_raw_essential_data(conn, &pagination).await? {
let traits = fetch_creature_traits(conn, cr.id).await?;
let alignment = AlignmentEnum::from((&traits, cr.remaster));
update_alignment_column_value(conn, alignment.to_string(), cr.id).await?;
let essential_data = EssentialData {
id: cr.id,
aon_id: cr.aon_id,
Expand All @@ -34,6 +38,7 @@ pub async fn update_creature_core_table(conn: &Pool<Sqlite>) -> Result<()> {
remaster: cr.remaster,
source: cr.source,
cr_type: CreatureTypeEnum::from(cr.cr_type),
alignment,
};
let extra_data = fetch_creature_extra_data(conn, essential_data.id).await?;
let combat_data = fetch_creature_combat_data(conn, essential_data.id).await?;
Expand All @@ -45,6 +50,7 @@ pub async fn update_creature_core_table(conn: &Pool<Sqlite>) -> Result<()> {
&spell_caster_data,
&scales,
);

for (curr_role, curr_percentage) in roles {
update_role_column_value(conn, curr_role, curr_percentage, essential_data.id).await?;
}
Expand Down Expand Up @@ -117,6 +123,24 @@ async fn update_role_column_value(
Ok(())
}

async fn update_alignment_column_value(
conn: &Pool<Sqlite>,
alignment: String,
creature_id: i64,
) -> Result<()> {
let x = sqlx::query!(
"UPDATE CREATURE_CORE SET alignment = ? WHERE id = ?",
alignment,
creature_id
)
.execute(conn)
.await?;
if x.rows_affected() < 1 {
bail!("Error encountered with creature id: {creature_id}. Could not update alignment: {alignment}")
}
Ok(())
}

async fn get_creatures_raw_essential_data(
conn: &Pool<Sqlite>,
paginated_request: &PaginatedRequest,
Expand Down
73 changes: 20 additions & 53 deletions src/db/data_providers/fetcher.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use crate::db::data_providers::raw_query_builder::prepare_filtered_get_creatures_core;
use crate::models::creature::Creature;
use crate::models::creature_component::creature_combat::{CreatureCombatData, SavingThrows};
use crate::models::creature_component::creature_core::{
CreatureCoreData, DerivedData, EssentialData,
};
use crate::models::creature_component::creature_core::CreatureCoreData;
use crate::models::creature_component::creature_extra::{AbilityScores, CreatureExtraData};
use crate::models::creature_component::creature_spell_caster::CreatureSpellCasterData;
use crate::models::creature_component::creature_variant::CreatureVariantData;
use crate::models::creature_filter_enum::CreatureFilter;
use crate::models::creature_metadata::alignment_enum::{AlignmentEnum, ALIGNMENT_TRAITS};
use crate::models::creature_metadata::alignment_enum::ALIGNMENT_TRAITS;
use crate::models::creature_metadata::variant_enum::CreatureVariant;
use crate::models::db::raw_immunity::RawImmunity;
use crate::models::db::raw_language::RawLanguage;
Expand Down Expand Up @@ -193,12 +191,12 @@ async fn fetch_creature_perception_detail(
)
}

async fn fetch_creature_traits(conn: &Pool<Sqlite>, creature_id: i64) -> Result<Vec<RawTrait>> {
pub async fn fetch_creature_traits(conn: &Pool<Sqlite>, creature_id: i64) -> Result<Vec<String>> {
Ok(sqlx::query_as!(
RawTrait,
"SELECT * FROM TRAIT_TABLE INTERSECT SELECT trait_id FROM TRAIT_CREATURE_ASSOCIATION_TABLE WHERE creature_id == ($1)",
creature_id
).fetch_all(conn).await?)
).fetch_all(conn).await?.iter().map(|x| x.name.clone()).collect())
}

async fn fetch_creature_weapons(conn: &Pool<Sqlite>, creature_id: i64) -> Result<Vec<Weapon>> {
Expand Down Expand Up @@ -256,64 +254,33 @@ async fn fetch_creature_core_data(
conn: &Pool<Sqlite>,
creature_id: i64,
) -> Result<CreatureCoreData> {
let essential = fetch_creature_essential_data(conn, creature_id).await?;
let derived = fetch_creature_derived_data(conn, creature_id).await?;
let traits = fetch_creature_traits(conn, creature_id)
let mut cr_core: CreatureCoreData =
sqlx::query_as("SELECT * FROM CREATURE_CORE WHERE id = ? ORDER BY name LIMIT 1")
.bind(creature_id)
.fetch_one(conn)
.await?;
cr_core.traits = fetch_creature_traits(conn, creature_id)
.await
.unwrap_or_default();
let is_remaster = essential.remaster;
Ok(CreatureCoreData {
essential,
derived,
traits: traits.iter().map(|x| x.name.clone()).collect(),
alignment: AlignmentEnum::from((&traits, is_remaster)),
})
}

async fn fetch_creature_essential_data(
conn: &Pool<Sqlite>,
creature_id: i64,
) -> Result<EssentialData> {
Ok(sqlx::query_as!(
EssentialData,
"SELECT id, aon_id, name, hp, level, size, family, rarity,
license, remaster, source, cr_type
FROM CREATURE_CORE WHERE id = ? ORDER BY name LIMIT 1",
creature_id,
)
.fetch_one(conn)
.await?)
}

async fn fetch_creature_derived_data(conn: &Pool<Sqlite>, creature_id: i64) -> Result<DerivedData> {
Ok(sqlx::query_as!(
DerivedData,
"SELECT
archive_link, is_melee, is_ranged, is_spell_caster, brute_percentage,
magical_striker_percentage, skill_paragon_percentage, skirmisher_percentage,
sniper_percentage, soldier_percentage, spell_caster_percentage
FROM CREATURE_CORE WHERE id = ? ORDER BY name LIMIT 1",
creature_id,
)
.fetch_one(conn)
.await?)
.unwrap_or_default()
.iter()
.filter(|x| !ALIGNMENT_TRAITS.contains(&&*x.as_str().to_uppercase()))
.cloned()
.collect();
Ok(cr_core)
}

async fn update_creatures_core_with_traits(
conn: &Pool<Sqlite>,
mut creature_core_data: Vec<CreatureCoreData>,
) -> Vec<CreatureCoreData> {
for core in &mut creature_core_data {
let traits = fetch_creature_traits(conn, core.essential.id)
core.traits = fetch_creature_traits(conn, core.essential.id)
.await
.unwrap_or_default();
let is_remaster = core.essential.remaster;
core.traits = traits
.unwrap_or_default()
.iter()
.filter(|x| !ALIGNMENT_TRAITS.contains(&&*x.name.as_str().to_uppercase()))
.map(|x| x.name.clone())
.filter(|x| !ALIGNMENT_TRAITS.contains(&&*x.as_str().to_uppercase()))
.cloned()
.collect();
core.alignment = AlignmentEnum::from((&traits, is_remaster));
}
creature_core_data
}
Expand Down
28 changes: 17 additions & 11 deletions src/db/data_providers/raw_query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub fn prepare_filtered_get_creatures_core(
for (key, value) in key_value_filters {
match key {
CreatureFilter::Level
| CreatureFilter::PathfinderVersion
| CreatureFilter::Melee
| CreatureFilter::Ranged
| CreatureFilter::SpellCaster => {
Expand All @@ -22,6 +23,7 @@ pub fn prepare_filtered_get_creatures_core(
)
}
CreatureFilter::Family
| CreatureFilter::Alignment
| CreatureFilter::Size
| CreatureFilter::Rarity
| CreatureFilter::CreatureTypes => {
Expand All @@ -33,18 +35,23 @@ pub fn prepare_filtered_get_creatures_core(
)
}
CreatureFilter::Traits => trait_query.push_str(prepare_trait_filter(value).as_str()),
CreatureFilter::CreatureRoles => simple_core_query
.push_str(prepare_bounded_check(value, 0, ACCURACY_THRESHOLD).as_str()),
CreatureFilter::CreatureRoles => {
if !simple_core_query.is_empty() {
simple_core_query.push_str(" AND ")
}
simple_core_query
.push_str(prepare_bounded_check(value, ACCURACY_THRESHOLD, 100).as_str())
}
}
}
let mut where_query = simple_core_query.to_string();
if !trait_query.is_empty() {
where_query.push_str(format!("AND id IN ({trait_query})").as_str());
};
where_query.push_str(format!(" AND id IN ({trait_query}) GROUP BY cc.id").as_str())
}
if !where_query.is_empty() {
where_query = format!("WHERE {where_query}");
}
let query = format!("SELECT * FROM CREATURE_CORE {where_query} ORDER BY RANDOM() LIMIT 20");
let query = format!("SELECT * FROM CREATURE_CORE cc {where_query} ORDER BY RANDOM() LIMIT 20");
debug!("{}", query);
query
}
Expand All @@ -57,12 +64,9 @@ fn prepare_bounded_check(
upper_bound: i64,
) -> String {
let mut bounded_query = String::new();
if column_names.is_empty() {
return bounded_query;
}
for column in column_names {
if !bounded_query.is_empty() {
bounded_query.push_str(" AND ");
bounded_query.push_str(" OR ");
}
bounded_query.push_str(
format!("({column} >= {lower_bound} AND {column} <= {upper_bound})").as_str(),
Expand All @@ -84,13 +88,15 @@ fn prepare_trait_filter(column_values: &HashSet<String>) -> String {
if !in_string.is_empty() {
let select_query = "SELECT tcat.creature_id FROM TRAIT_CREATURE_ASSOCIATION_TABLE";
let inner_query = format!("SELECT * FROM TRAIT_TABLE tt WHERE {in_string}");
return format!("{select_query} tcat RIGHT JOIN ({inner_query}) jt ON tcat.trait_id = jt.name GROUP BY tcat.creature_id");
return format!(
"{select_query} tcat RIGHT JOIN ({inner_query}) jt ON tcat.trait_id = jt.name"
);
}
in_string
}

/// Prepares an 'in' statement in the following format. Assuming a string value
/// "field in ('el1', 'el2', 'el3')"
/// "UPPER(field) in (UPPER('el1'), UPPER('el2'), UPPER('el3'))"
fn prepare_in_statement_for_string_type(
column_name: &str,
column_values: &HashSet<String>,
Expand Down
3 changes: 1 addition & 2 deletions src/db/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,9 @@ pub async fn get_creatures_passing_all_filters(
fetch_elite: bool,
) -> Result<Vec<Creature>> {
let mut creature_vec = Vec::new();
let empty_set = HashSet::new();
let level_vec = key_value_filters
.get(&CreatureFilter::Level)
.unwrap_or(&empty_set)
.unwrap_or(&HashSet::new())
.clone();
let modified_filters =
prepare_filters_for_db_communication(key_value_filters, fetch_weak, fetch_elite);
Expand Down
15 changes: 11 additions & 4 deletions src/models/creature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::models::creature_component::creature_spell_caster::CreatureSpellCaste
use crate::models::creature_component::creature_variant::CreatureVariantData;
use crate::models::creature_metadata::creature_role::CreatureRoleEnum;
use crate::models::creature_metadata::variant_enum::CreatureVariant;
use crate::models::pf_version_enum::PathfinderVersionEnum;
use crate::models::routers_validator_structs::FieldFilters;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -101,10 +102,9 @@ impl Creature {
.size_filter
.as_ref()
.map_or(true, |size| self.core_data.essential.size == *size);
let alignment_pass = filters
.alignment_filter
.as_ref()
.map_or(true, |alignment| self.core_data.alignment == *alignment);
let alignment_pass = filters.alignment_filter.as_ref().map_or(true, |alignment| {
self.core_data.essential.alignment == *alignment
});
let is_melee_pass = filters
.is_melee_filter
.map_or(true, |is_melee| self.core_data.derived.is_melee == is_melee);
Expand Down Expand Up @@ -143,6 +143,12 @@ impl Creature {
}
});

let version_pass = match filters.pathfinder_version.clone().unwrap_or_default() {
PathfinderVersionEnum::Legacy => !self.core_data.essential.remaster,
PathfinderVersionEnum::Remaster => self.core_data.essential.remaster,
PathfinderVersionEnum::Any => true,
};

rarity_pass
&& size_pass
&& alignment_pass
Expand All @@ -151,6 +157,7 @@ impl Creature {
&& is_spell_caster_pass
&& type_pass
&& role_pass
&& version_pass
}

fn check_creature_pass_string_filters(&self, filters: &FieldFilters) -> bool {
Expand Down
5 changes: 3 additions & 2 deletions src/models/creature_component/creature_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub struct CreatureCoreData {
pub essential: EssentialData,
pub derived: DerivedData,
pub traits: Vec<String>,
pub alignment: AlignmentEnum,
}
#[derive(Serialize, Deserialize, Clone, ToSchema, Eq, Hash, PartialEq, FromRow)]
pub struct EssentialData {
Expand All @@ -28,6 +27,7 @@ pub struct EssentialData {
pub remaster: bool,
pub source: String,
pub cr_type: CreatureTypeEnum,
pub alignment: AlignmentEnum,
}

#[derive(Serialize, Deserialize, Clone, ToSchema, Eq, Hash, PartialEq, FromRow)]
Expand All @@ -51,6 +51,7 @@ impl<'r> FromRow<'r, SqliteRow> for CreatureCoreData {
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
let rarity: String = row.try_get("rarity")?;
let size: String = row.try_get("size")?;
let alignment: String = row.try_get("alignment")?;
Ok(CreatureCoreData {
essential: EssentialData {
id: row.try_get("id")?,
Expand All @@ -65,6 +66,7 @@ impl<'r> FromRow<'r, SqliteRow> for CreatureCoreData {
remaster: row.try_get("remaster")?,
source: row.try_get("source")?,
cr_type: CreatureTypeEnum::from(row.try_get("cr_type").ok()),
alignment: AlignmentEnum::from(alignment),
},
derived: DerivedData {
archive_link: row.try_get("archive_link").ok(),
Expand All @@ -80,7 +82,6 @@ impl<'r> FromRow<'r, SqliteRow> for CreatureCoreData {
spell_caster_percentage: row.try_get("spell_caster_percentage")?,
},
traits: vec![],
alignment: Default::default(),
})
}
}
2 changes: 2 additions & 0 deletions src/models/creature_component/filter_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::models::creature_metadata::creature_role::CreatureRoleEnum;
use crate::models::creature_metadata::rarity_enum::RarityEnum;
use crate::models::creature_metadata::size_enum::SizeEnum;
use crate::models::creature_metadata::type_enum::CreatureTypeEnum;
use crate::models::pf_version_enum::PathfinderVersionEnum;
use std::collections::HashSet;

pub struct FilterStruct {
Expand All @@ -14,4 +15,5 @@ pub struct FilterStruct {
pub creature_types: Option<Vec<CreatureTypeEnum>>,
pub creature_roles: Option<Vec<CreatureRoleEnum>>,
pub lvl_combinations: HashSet<String>,
pub pathfinder_version: PathfinderVersionEnum,
}
Loading

0 comments on commit a506f49

Please sign in to comment.