From d9479808cc7a86b411c3a7245a0d8d4a755ed13c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Maria=C5=84ski?= Date: Wed, 26 Apr 2023 23:52:39 +0200 Subject: [PATCH 1/4] add merits --- cofd/app/src/component/merits.rs | 5 +- cofd/lib/src/splat/mod.rs | 165 +++++++++++++++---------------- cofd/lib/src/splat/vampire.rs | 110 ++++++++++++++++++--- 3 files changed, 180 insertions(+), 100 deletions(-) diff --git a/cofd/app/src/component/merits.rs b/cofd/app/src/component/merits.rs index 05e2b0b..4b6b7eb 100644 --- a/cofd/app/src/component/merits.rs +++ b/cofd/app/src/component/merits.rs @@ -83,6 +83,9 @@ impl Component for MeritComponent { vec.push(Merit::_Custom(fl!("custom"))); + let attributes = &character.attributes(); + let skills = &character.skills(); + let vec: Vec> = vec .iter() .cloned() @@ -91,7 +94,7 @@ impl Component for MeritComponent { .merits .iter() .filter(|(merit, _)| *merit == *e) - .count() == 0 && e.is_available(&character) + .count() == 0 && e.is_available(&character, attributes, skills) }) .map(Into::into) .collect(); diff --git a/cofd/lib/src/splat/mod.rs b/cofd/lib/src/splat/mod.rs index 9c21aef..890af64 100644 --- a/cofd/lib/src/splat/mod.rs +++ b/cofd/lib/src/splat/mod.rs @@ -8,7 +8,7 @@ use crate::{ modifier::{Modifier, ModifierOp, ModifierTarget, ModifierValue}, traits::AttributeType, }, - prelude::{Attribute, Character, Skill, Trait}, + prelude::{Attribute, Attributes, Character, Skill, Skills, Trait}, }; pub mod ability; @@ -557,14 +557,14 @@ impl Merit { pub fn get_modifiers(&self, value: u16) -> Vec { match &self { - Merit::DefensiveCombat(true, Some(skill)) => { + Self::DefensiveCombat(true, Some(skill)) => { vec![Modifier::new( ModifierTarget::Trait(Trait::Defense), ModifierValue::Skill(*skill), ModifierOp::Set, )] } - Merit::Giant => { + Self::Giant => { if value == 3 { vec![Modifier::new( ModifierTarget::Trait(Trait::Size), @@ -575,116 +575,109 @@ impl Merit { vec![] } } - Merit::Werewolf(merit) => merit.get_modifiers(value), + Self::FleetOfFoot => { + if value <= 3 { + vec![Modifier::new( + ModifierTarget::Trait(Trait::Speed), + ModifierValue::Num(value as i16), + ModifierOp::Add, + )] + } else { + vec![] + } + } + Self::FastReflexes => { + if value <= 3 { + vec![Modifier::new( + ModifierTarget::Trait(Trait::Initative), + ModifierValue::Num(value as i16), + ModifierOp::Add, + )] + } else { + vec![] + } + } + // Self::Mage(merit) => merit.get_modifiers(value), + Self::Vampire(merit) => merit.get_modifiers(value), + Self::Werewolf(merit) => merit.get_modifiers(value), + // Self::Changeling(merit) => merit.get_modifiers(value), _ => vec![], } } - pub fn is_available(&self, character: &Character) -> bool { + pub fn is_available( + &self, + character: &Character, + attributes: &Attributes, + skills: &Skills, + ) -> bool { match self { - Merit::_Custom(_) => true, - - Merit::AreaOfExpertise(_) => character.attributes().resolve > 1, - Merit::EyeForTheStrange => { - character.attributes().resolve > 1 && character.skills().occult > 0 - } - Merit::FastReflexes => { - let attr = character.attributes(); - attr.wits > 2 || attr.dexterity > 2 - } - Merit::GoodTimeManagement => { - let skills = character.skills(); - skills.academics > 1 || skills.science > 1 - } - Self::Indomitable => character.attributes().resolve > 2, - Self::InterdisciplinarySpecialty(_, Some(skill)) => *character.skills().get(skill) > 2, - Self::InvestigativeAide(Some(skill)) => *character.skills().get(skill) > 2, - Self::InvestigativeProdigy => { - character.attributes().wits > 2 && character.skills().investigation > 2 - } + Self::_Custom(_) => true, + + Self::AreaOfExpertise(_) => attributes.resolve > 1, + Self::EyeForTheStrange => attributes.resolve >= 2 && skills.occult >= 1, + Self::FastReflexes => attributes.wits > 2 || attributes.dexterity > 2, + Self::GoodTimeManagement => skills.academics > 1 || skills.science > 1, + Self::Indomitable => attributes.resolve > 2, + Self::InterdisciplinarySpecialty(_, Some(skill)) => skills.get(*skill) > 2, + Self::InvestigativeAide(Some(skill)) => skills.get(*skill) > 2, + Self::InvestigativeProdigy => attributes.wits > 2 && skills.investigation > 2, // Self::LibraryAdvanced() // Library 3 + <= Safe Place Self::Scarred(_) => character.integrity <= 5, - Self::ToleranceForBiology => character.attributes().resolve > 2, - Self::TrainedObserver => { - let attrs = character.attributes(); - attrs.wits > 2 || attrs.composure > 2 - } + Self::ToleranceForBiology => attributes.resolve > 2, + Self::TrainedObserver => attributes.wits > 2 || attributes.composure > 2, Self::ViceRidden(_) if character.splat.vice_anchor() != "vice" => false, Self::Virtuous(_) if character.splat.virtue_anchor() != "virtue" => false, // Self::Ambidextrous // Character creation only - Self::AutomotiveGenius => { - let skills = character.skills(); - skills.crafts > 2 && skills.drive > 0 && skills.science > 0 - } + Self::AutomotiveGenius => skills.crafts > 2 && skills.drive > 0 && skills.science > 0, Self::CovertOperative => { - let attr = character.attributes(); - attr.wits > 2 && attr.dexterity > 2 && character.skills().stealth > 1 + attributes.wits > 2 && attributes.dexterity > 2 && skills.stealth > 1 } - Self::CrackDriver => character.skills().drive > 2, - Self::Demolisher => { - let attr = character.attributes(); - attr.strength > 2 || attr.intelligence > 2 - } - Self::DoubleJointed => character.attributes().dexterity > 2, - Self::FleetOfFoot => character.skills().athletics > 1, - Self::Freediving => character.skills().athletics > 1, + Self::CrackDriver => skills.drive > 2, + Self::Demolisher => attributes.strength > 2 || attributes.intelligence > 2, + Self::DoubleJointed => attributes.dexterity > 2, + Self::FleetOfFoot => skills.athletics > 1, + Self::Freediving => skills.athletics > 1, // Self::Giant // Character Creation OR Strength Performance - Self::Hardy => character.attributes().stamina > 2, + Self::Hardy => attributes.stamina > 2, Self::Greyhound => { - let attr = character.attributes(); - character.skills().athletics > 2 && attr.wits > 2 && attr.stamina > 2 + skills.athletics > 2 && attributes.wits > 2 && attributes.stamina > 2 } // IronSkin - Self::IronStamina => { - let attr = character.attributes(); - attr.stamina > 2 || attr.resolve > 2 - } - Self::QuickDraw(_) => character.attributes().wits > 2, + Self::IronStamina => attributes.stamina > 2 || attributes.resolve > 2, + Self::QuickDraw(_) => attributes.wits > 2, Self::PunchDrunk => character.max_willpower() > 5, - Self::Relentless => { - character.skills().athletics > 1 && character.attributes().stamina > 2 - } + Self::Relentless => skills.athletics > 1 && attributes.stamina > 2, // Self::Roadkill // Merit Dep Aggresive Driving 2 - Self::SeizingTheEdge => { - let attr = character.attributes(); - attr.wits > 2 && attr.composure > 2 - } - Self::SleightOfHand => character.skills().larceny > 2, + Self::SeizingTheEdge => attributes.wits > 2 && attributes.composure > 2, + Self::SleightOfHand => skills.larceny > 2, // Self::SmallFramed // Character Creation - // Self::Survivalist => character.skills().survival > 2 // Iron Stamina 3 dependency - Self::AirOfMenace => character.skills().intimidation > 1, + // Self::Survivalist => skills.survival > 2 // Iron Stamina 3 dependency + Self::AirOfMenace => skills.intimidation > 1, // Self::Anonymity // No Fame Merit - Self::Barfly => character.skills().socialize > 1, - Self::ClosedBook => { - let attr = character.attributes(); - attr.manipulation > 2 && attr.resolve > 2 - } - Self::CohesiveUnit => character.attributes().presence > 2, - Self::Empath => character.skills().empathy > 1, + Self::Barfly => skills.socialize > 1, + Self::ClosedBook => attributes.manipulation > 2 && attributes.resolve > 2, + Self::CohesiveUnit => attributes.presence > 2, + Self::Empath => skills.empathy > 1, // Self::Fame // No Anonimity Merit - // Self::Fixer => character.attributes().wits > 2 // Contacts 2 - Self::HobbyistClique(_, Some(skill)) => *character.skills().get(skill) > 1, - Self::Inspiring => character.attributes().presence > 2, - Self::IronWill => character.attributes().resolve > 3, - Self::Peacemaker => character.attributes().wits > 2 && character.skills().empathy > 2, - Self::Pusher => character.skills().persuasion > 1, - Self::SmallUnitTactics => character.attributes().presence > 2, - Self::SpinDoctor => { - character.attributes().manipulation > 2 && character.skills().subterfuge > 1 - } + Self::Fixer => attributes.wits > 2, // && Contacts 2 + Self::HobbyistClique(_, Some(skill)) => skills.get(*skill) > 1, + Self::Inspiring => attributes.presence > 2, + Self::IronWill => attributes.resolve > 3, + Self::Peacemaker => attributes.wits > 2 && skills.empathy > 2, + Self::Pusher => skills.persuasion > 1, + Self::SmallUnitTactics => attributes.presence > 2, + Self::SpinDoctor => attributes.manipulation > 2 && skills.subterfuge > 1, Self::TableTurner => { - let attr = character.attributes(); - attr.composure > 2 && attr.manipulation > 2 && attr.wits > 2 + attributes.composure > 2 && attributes.manipulation > 2 && attributes.wits > 2 } Self::TakesOneToKnowOne if character.splat.vice_anchor() != "vice" => false, - Self::Taste(_, _) => character.skills().crafts > 1, - Self::Untouchable => { - character.attributes().manipulation > 2 && character.skills().subterfuge > 1 - } + Self::Taste(_, _) => skills.crafts > 1, + Self::Untouchable => attributes.manipulation > 2 && skills.subterfuge > 1, Self::Mage(merit) => merit.is_available(character), - Self::Vampire(merit) => merit.is_available(character), + Self::Vampire(merit) => merit.is_available(character, attributes, skills), Self::Werewolf(merit) => merit.is_available(character), Self::Changeling(merit) => merit.is_available(character), _ => true, diff --git a/cofd/lib/src/splat/vampire.rs b/cofd/lib/src/splat/vampire.rs index ebc5a98..bfd153f 100644 --- a/cofd/lib/src/splat/vampire.rs +++ b/cofd/lib/src/splat/vampire.rs @@ -6,7 +6,7 @@ use super::{ability::Ability, Merit, Splat}; use crate::{ character::modifier::{Modifier, ModifierOp}, dice_pool::DicePool, - prelude::{Attribute, Trait}, + prelude::{Attribute, Attributes, Skills, Trait}, }; #[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq, Eq)] @@ -167,9 +167,9 @@ pub enum MaskDirge { pub enum VampireMerit { AcuteSenses, Atrocious, - // Beloved, // TY + Beloved, // TY Bloodhound, - // CallTheBeast // TY + CallTheBeast, // TY ClawsOfTheUnholy, CloseFamily, Cutthroat, @@ -177,20 +177,20 @@ pub enum VampireMerit { DreamVisions, // Mekhet Enticing, FeedingGrounds(String), - // HeartOfStone, // TY + HeartOfStone, // TY Herd, HoneyTrap, KindredStatus(String), // Status? - KissOfTheSuccubus, // Daeva + KissOfTheSuccubus, Lineage(String), LingeringDreams, // DE2 - // MajorDomo, // TY - // MarriedByBlood, // TY - PackAlpha, // Gangrel - // ReceptiveMind, // TY - // RevenantImpostor, // HD - // SaviorOfTheLost, // TY - // SpecialTreat, // TY + MajorDomo, // TY + MarriedByBlood, // TY + PackAlpha, + ReceptiveMind, // TY + RevenantImpostor, // HD + SaviorOfTheLost, // TY + SpecialTreat, // TY SwarmForm, Touchstone, UndeadMenses, @@ -223,8 +223,92 @@ pub enum VampireMerit { } impl VampireMerit { - pub fn is_available(&self, character: &crate::prelude::Character) -> bool { + pub fn is_available( + &self, + character: &crate::prelude::Character, + attributes: &Attributes, + skills: &Skills, + ) -> bool { matches!(character.splat, Splat::Vampire(..)) + && match self { + // VampireMerit::Atrocious => todo!(), // Not Enticing or Cutthroat + VampireMerit::Bloodhound => attributes.wits >= 3, + VampireMerit::CallTheBeast => character.integrity < 5, + VampireMerit::ClawsOfTheUnholy => { + *character + .abilities + .get(&Discipline::Protean.into()) + .unwrap_or(&0) >= 4 + } + // VampireMerit::Cutthroat => todo!(), // Not Enticing or Atrocious + VampireMerit::DreamVisions => { + matches!(character.splat, Splat::Vampire(Clan::Mekhet, ..)) + } + // VampireMerit::Enticing => todo!(), // Not Cutthroat or Atrocious + // VampireMerit::FeedingGrounds(_) => todo!(), + // VampireMerit::HeartOfStone => todo!(), // Feeding Grounds + // VampireMerit::HoneyTrap => todo!(), // Not a Revenant + // VampireMerit::KindredStatus(_) => todo!(), + VampireMerit::KissOfTheSuccubus => { + matches!(character.splat, Splat::Vampire(Clan::Daeva, ..)) + } + // VampireMerit::Lineage(_) => todo!(), Clan Status + VampireMerit::LingeringDreams => { + matches!(character.splat, Splat::Vampire(Clan::Mekhet, ..)) + } // Dream Visions + VampireMerit::PackAlpha => { + matches!(character.splat, Splat::Vampire(Clan::Gangrel, ..)) + } + VampireMerit::ReceptiveMind => { + character.power >= 6 + && *character + .abilities + .get(&Discipline::Auspex.into()) + .unwrap_or(&0) >= 4 + } + VampireMerit::RevenantImpostor => { + attributes.manipulation >= 3 && skills.subterfuge >= 2 + } + VampireMerit::SwarmForm => { + *character + .abilities + .get(&Discipline::Protean.into()) + .unwrap_or(&0) >= 4 + } + VampireMerit::UnsettlingGaze => { + matches!(character.splat, Splat::Vampire(Clan::Nosferatu, ..)) + } + + // VampireMerit::CacophonySavvy => todo!(), // City Status + VampireMerit::Courtoisie => { + attributes.composure >= 3 && skills.socialize >= 2 && skills.weaponry >= 2 + } // Invictus Status + VampireMerit::Crusade => { + attributes.resolve >= 3 && skills.occult >= 2 && skills.weaponry >= 2 + } // Theban Sorcery 2 or Sorc Eunuch + // VampireMerit::DynastyMembership => todo!(), // Clan Status + VampireMerit::KindredDueling => attributes.composure >= 3 && skills.weaponry >= 2, + VampireMerit::MobilizeOutrage => { + character.max_willpower() >= 5 && skills.brawl >= 2 + } // Carthian Status + VampireMerit::RidingTheWave => attributes.composure >= 3 && attributes.resolve >= 3, + VampireMerit::RitesOfTheImpaled => { + attributes.resolve >= 3 && attributes.stamina >= 3 && skills.weaponry >= 2 + } // Sworn + VampireMerit::TempleGuardian => { + skills.athletics >= 2 && skills.brawl >= 2 && skills.weaponry >= 2 + } // Crone Status + // VampireMerit::IndependentStudy => todo!(), + // VampireMerit::SecretSocietyJunkie => todo!(), + // VampireMerit::Sworn(_) => todo!(), + // VampireMerit::TwilightJudge => todo!(), + // VampireMerit::NestGuardian => todo!(), + _ => true, + } + } + + pub fn get_modifiers(&self, value: u16) -> Vec { + Vec::new() } } From 44552970795402a1dc1def31d17bfa94f1aaf139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Maria=C5=84ski?= Date: Wed, 26 Apr 2023 23:54:18 +0200 Subject: [PATCH 2/4] remove unnecesary borrows --- cofd/app/src/component/skills.rs | 2 +- cofd/app/src/view/overview.rs | 10 ++++-- cofd/lib/src/character/mod.rs | 57 +++++++++++++++--------------- cofd/lib/src/character/modifier.rs | 2 +- cofd/lib/src/dice_pool.rs | 2 +- 5 files changed, 39 insertions(+), 34 deletions(-) diff --git a/cofd/app/src/component/skills.rs b/cofd/app/src/component/skills.rs index 898c9e0..0a7a1fe 100644 --- a/cofd/app/src/component/skills.rs +++ b/cofd/app/src/component/skills.rs @@ -120,7 +120,7 @@ impl SkillsComponent { .on_press(Event::SpecialtySkillChanged(skill)), ); - let v = character.base_skills().get(&skill); + let v = character.base_skills().get(skill); let val = character._modified(ModifierTarget::BaseSkill(skill)); let mod_ = val - v; diff --git a/cofd/app/src/view/overview.rs b/cofd/app/src/view/overview.rs index 8a4e21b..0980a17 100644 --- a/cofd/app/src/view/overview.rs +++ b/cofd/app/src/view/overview.rs @@ -49,6 +49,7 @@ pub enum Event { // YSplatChanged(YSplat), AbilityValChanged(Ability, u16), AbilityChanged(Ability, Ability), + NewAbility(Ability), MeritChanged(usize, Merit, u16), // CustomAbilityChanged(Ability, String), HealthChanged(Wound), @@ -165,7 +166,7 @@ impl OverviewTab { } new = new.push( - pick_list(vec, None, |key| Event::AbilityValChanged(key.unwrap(), 0)) + pick_list(vec, None, |key| Event::NewAbility(key.unwrap())) .width(Length::Fill) .padding(INPUT_PADDING) .text_size(20), @@ -199,7 +200,7 @@ where match event { Event::AttrChanged(val, attr) => *character.base_attributes_mut().get_mut(&attr) = val, - Event::SkillChanged(val, skill) => *character.base_skills_mut().get_mut(&skill) = val, + Event::SkillChanged(val, skill) => *character.base_skills_mut().get_mut(skill) = val, Event::SpecialtyChanged(skill, i, val) => { if let Some(vec) = character.specialties.get_mut(&skill) { if val.is_empty() { @@ -222,7 +223,10 @@ where if character.has_ability(&ability) { let val = character.remove_ability(&ability).unwrap_or_default(); character.add_ability(new, val); - } else { + } + } + Event::NewAbility(ability) => { + if !character.has_ability(&ability) { character.add_ability(ability, 0); } } diff --git a/cofd/lib/src/character/mod.rs b/cofd/lib/src/character/mod.rs index 80afb2e..bb46d47 100644 --- a/cofd/lib/src/character/mod.rs +++ b/cofd/lib/src/character/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::{ cmp::{max, min}, collections::HashMap, - ops::{Add, Sub}, + ops::{Add, Index, Sub}, }; use crate::splat::{ability::Ability, Merit, Splat}; @@ -320,6 +320,7 @@ pub struct Character { pub base_size: u16, base_armor: ArmorStruct, pub beats: u16, + #[serde(skip_serializing_if = "is_zero")] pub alternate_beats: u16, pub conditions: Vec, @@ -448,7 +449,7 @@ impl Character { *self._attributes.get(attr) } ModifierTarget::BaseSkill(skill) | ModifierTarget::Skill(skill) => { - *self.skills.get(skill) + self.skills.get(*skill) } _ => 0, }; @@ -931,38 +932,38 @@ pub struct Skills { } impl Skills { - pub fn get(&self, skill: &Skill) -> &u16 { + pub fn get(&self, skill: Skill) -> u16 { match skill { - Skill::Academics => &self.academics, - Skill::Computer => &self.computer, - Skill::Crafts => &self.crafts, - Skill::Investigation => &self.investigation, - Skill::Medicine => &self.medicine, - Skill::Occult => &self.occult, - Skill::Politics => &self.politics, - Skill::Science => &self.science, + Skill::Academics => self.academics, + Skill::Computer => self.computer, + Skill::Crafts => self.crafts, + Skill::Investigation => self.investigation, + Skill::Medicine => self.medicine, + Skill::Occult => self.occult, + Skill::Politics => self.politics, + Skill::Science => self.science, // - Skill::Athletics => &self.athletics, - Skill::Brawl => &self.brawl, - Skill::Drive => &self.drive, - Skill::Firearms => &self.firearms, - Skill::Larceny => &self.larceny, - Skill::Stealth => &self.stealth, - Skill::Survival => &self.survival, - Skill::Weaponry => &self.weaponry, + Skill::Athletics => self.athletics, + Skill::Brawl => self.brawl, + Skill::Drive => self.drive, + Skill::Firearms => self.firearms, + Skill::Larceny => self.larceny, + Skill::Stealth => self.stealth, + Skill::Survival => self.survival, + Skill::Weaponry => self.weaponry, // - Skill::AnimalKen => &self.animal_ken, - Skill::Empathy => &self.empathy, - Skill::Expression => &self.expression, - Skill::Intimidation => &self.intimidation, - Skill::Persuasion => &self.persuasion, - Skill::Socialize => &self.socialize, - Skill::Streetwise => &self.streetwise, - Skill::Subterfuge => &self.subterfuge, + Skill::AnimalKen => self.animal_ken, + Skill::Empathy => self.empathy, + Skill::Expression => self.expression, + Skill::Intimidation => self.intimidation, + Skill::Persuasion => self.persuasion, + Skill::Socialize => self.socialize, + Skill::Streetwise => self.streetwise, + Skill::Subterfuge => self.subterfuge, } } - pub fn get_mut(&mut self, skill: &Skill) -> &mut u16 { + pub fn get_mut(&mut self, skill: Skill) -> &mut u16 { match skill { Skill::Academics => &mut self.academics, Skill::Computer => &mut self.computer, diff --git a/cofd/lib/src/character/modifier.rs b/cofd/lib/src/character/modifier.rs index 8c92a71..71ea785 100644 --- a/cofd/lib/src/character/modifier.rs +++ b/cofd/lib/src/character/modifier.rs @@ -98,7 +98,7 @@ impl ModifierValue { ModifierValue::Ability(ability) => { *character.get_ability_value(ability).unwrap_or(&0) as i16 } - ModifierValue::Skill(skill) => *character.skills.get(skill) as i16, + ModifierValue::Skill(skill) => character.skills.get(*skill) as i16, ModifierValue::DicePool(pool) => pool.value(character), } } diff --git a/cofd/lib/src/dice_pool.rs b/cofd/lib/src/dice_pool.rs index 6b0e444..4027812 100644 --- a/cofd/lib/src/dice_pool.rs +++ b/cofd/lib/src/dice_pool.rs @@ -31,7 +31,7 @@ impl DicePool { match self { Self::Mod(val) => *val, Self::Attribute(attr) => *character.attributes().get(attr) as i16, - Self::Skill(skill) => *character.skills().get(skill) as i16, + Self::Skill(skill) => character.skills().get(*skill) as i16, Self::Trait(trait_) => character.get_trait(trait_) as i16, Self::Add(p1, p2) => p1.value(character) + p2.value(character), Self::Sub(p1, p2) => p1.value(character) - p2.value(character), From aed594463d653e573dd54e366db85b4f39b0525a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Maria=C5=84ski?= Date: Wed, 26 Apr 2023 23:55:31 +0200 Subject: [PATCH 3/4] add merits to scraper --- cofd/lib/data/merits_universal.ron | 691 +++++++++++++++++++++++++++++ cofd/macros/src/lib.rs | 20 +- cofd/util/src/scraper/mod.rs | 55 ++- cofd/xtask/src/bin/gifts.rs | 66 +-- cofd/xtask/src/bin/merits.rs | 72 +++ cofd/xtask/src/bin/scraper.rs | 59 ++- 6 files changed, 909 insertions(+), 54 deletions(-) create mode 100644 cofd/lib/data/merits_universal.ron create mode 100644 cofd/xtask/src/bin/merits.rs diff --git a/cofd/lib/data/merits_universal.ron b/cofd/lib/data/merits_universal.ron new file mode 100644 index 0000000..ed6690a --- /dev/null +++ b/cofd/lib/data/merits_universal.ron @@ -0,0 +1,691 @@ +[ + ( + name: "AreaOfExpertise", + cost: Number(1), + requirements: Some("Resolve ••"), + type_: Mental, + ), + ( + name: "CommonSense", + cost: Number(3), + requirements: None, + type_: Mental, + ), + ( + name: "DangerSense", + cost: Number(2), + requirements: None, + type_: Mental, + ), + ( + name: "DirectionSense", + cost: Number(1), + requirements: None, + type_: Mental, + ), + ( + name: "EideticMemory", + cost: Number(2), + requirements: None, + type_: Mental, + ), + ( + name: "EncyclopedicKnowledge", + cost: Number(2), + requirements: None, + type_: Mental, + ), + ( + name: "EyeForTheStrange", + cost: Number(2), + requirements: Some("Resolve ••, Occult •"), + type_: Mental, + ), + ( + name: "FastReflexes", + cost: Range(2, 4), + requirements: Some("Wits or Dexterity •••"), + type_: Mental, + ), + ( + name: "GoodTimeManagement", + cost: Number(1), + requirements: Some("Academics or Science ••"), + type_: Mental, + ), + ( + name: "HolisticAwareness", + cost: Number(1), + requirements: None, + type_: Mental, + ), + ( + name: "HumanPrey", + cost: Number(2), + requirements: None, + type_: Mental, + ), + ( + name: "Hypervigilance", + cost: Number(1), + requirements: None, + type_: Mental, + ), + ( + name: "Indomitable", + cost: Number(2), + requirements: Some("Resolve •••"), + type_: Mental, + ), + ( + name: "InterdisciplinarySpecialty", + cost: Number(1), + requirements: Some("Any Skill •••"), + type_: Mental, + ), + ( + name: "InvestigativeAide", + cost: Number(1), + requirements: Some("Any Skill •••"), + type_: Mental, + ), + ( + name: "InvestigativeProdigy", + cost: Range(2, 6), + requirements: Some("Wits •••, Investigation •••"), + type_: Mental, + ), + ( + name: "Language", + cost: Number(1), + requirements: None, + type_: Mental, + ), + ( + name: "Library", + cost: Range(2, 4), + requirements: None, + type_: Mental, + ), + ( + name: "AdvancedLibrary", + cost: Range(2, 6), + requirements: Some("Library •••, ≤ Safe Place"), + type_: Mental, + ), + ( + name: "LucidDreamer", + cost: Number(2), + requirements: Some("Resolve •••"), + type_: Mental, + ), + ( + name: "MeditativeMind", + cost: Vec([ + 1, + 2, + 4, + ]), + requirements: None, + type_: Mental, + ), + ( + name: "Multilingual", + cost: Number(1), + requirements: None, + type_: Mental, + ), + ( + name: "ObjectFetishism", + cost: Range(2, 6), + requirements: None, + type_: Mental, + ), + ( + name: "Patient", + cost: Number(1), + requirements: None, + type_: Mental, + ), + ( + name: "RenownedArtisan", + cost: Number(3), + requirements: Some("Crafts ••• with specialty"), + type_: Mental, + ), + ( + name: "Scarred", + cost: Number(1), + requirements: Some("Integrity ≤ 5"), + type_: Mental, + ), + ( + name: "ToleranceForBiology", + cost: Number(1), + requirements: Some("Resolve •••"), + type_: Mental, + ), + ( + name: "TrainedObserver", + cost: Vec([ + 1, + 3, + ]), + requirements: Some("Wits or Composure •••"), + type_: Mental, + ), + ( + name: "ViceRidden", + cost: Number(2), + requirements: Some("Vice"), + type_: Mental, + ), + ( + name: "Virtuous", + cost: Number(2), + requirements: Some("Virtue"), + type_: Mental, + ), + ( + name: "EsotericArmory", + cost: Range(2, 6), + requirements: None, + type_: Supernatural, + ), + ( + name: "Relic", + cost: Range(2, 6), + requirements: None, + type_: Supernatural, + ), + ( + name: "Sandglass", + cost: Number(2), + requirements: None, + type_: Supernatural, + ), + ( + name: "Vestige", + cost: Range(2, 6), + requirements: None, + type_: Supernatural, + ), + ( + name: "ArmedRestraint", + cost: Number(2), + requirements: Some("Staff Fighting •••"), + type_: Fightning, + ), + ( + name: "BodyAsWeapon", + cost: Number(2), + requirements: Some("Stamina •••, Brawl ••"), + type_: Fightning, + ), + ( + name: "BootParty", + cost: Number(2), + requirements: Some("Brawl ••"), + type_: Fightning, + ), + ( + name: "CheapShot", + cost: Number(2), + requirements: Some("Street Fighting •••, Subterfuge ••"), + type_: Fightning, + ), + ( + name: "ChokeHold", + cost: Number(2), + requirements: Some("Brawl ••"), + type_: Fightning, + ), + ( + name: "ClinchStrike", + cost: Number(1), + requirements: Some("Brawl ••"), + type_: Fightning, + ), + ( + name: "DefensiveCombat", + cost: Number(1), + requirements: Some("Brawl • or Weaponry •"), + type_: Fightning, + ), + ( + name: "FightingFinesse", + cost: Number(2), + requirements: Some("Dexterity •••"), + type_: Fightning, + ), + ( + name: "GroundAndPound", + cost: Number(3), + requirements: Some("Brawl ••"), + type_: Fightning, + ), + ( + name: "GroundFighter", + cost: Number(3), + requirements: Some("Wits •••, Dexterity •••, Brawl ••"), + type_: Fightning, + ), + ( + name: "Gunslinger", + cost: Vec([ + 1, + 3, + 5, + ]), + requirements: Some("Wits •••, Firearms •••, Firearms(Revolvers) specialty"), + type_: Fightning, + ), + ( + name: "Headbutt", + cost: Number(1), + requirements: Some("Brawl ••"), + type_: Fightning, + ), + ( + name: "IronChin", + cost: Vec([ + 2, + 4, + ]), + requirements: Some("Resolve •••, Stamina •••"), + type_: Fightning, + ), + ( + name: "IronSkin", + cost: Vec([ + 2, + 4, + ]), + requirements: Some("Martial Arts or Street Fighting ••, Stamina •••"), + type_: Fightning, + ), + ( + name: "KillerInstinct", + cost: Range(2, 4), + requirements: Some("Composure •••, Wits •••, Medicine •"), + type_: Fightning, + ), + ( + name: "LoadedForBear", + cost: Range(2, 3), + requirements: Some("Athletics •, Survival •"), + type_: Fightning, + ), + ( + name: "PhalanxFighter", + cost: Number(2), + requirements: Some("Weapon and Shield ••, Spear and Bayonet •"), + type_: Fightning, + ), + ( + name: "RetainWeapon", + cost: Number(2), + requirements: Some("Wits ••, Brawl ••"), + type_: Fightning, + ), + ( + name: "Shiv", + cost: Vec([ + 1, + 2, + ]), + requirements: Some("Street Fighting ••, Weaponry •"), + type_: Fightning, + ), + ( + name: "SubduingStrikes", + cost: Number(1), + requirements: Some("Weaponry ••"), + type_: Fightning, + ), + ( + name: "TransferManeuver", + cost: Range(2, 4), + requirements: Some("Intelligence ••, Wits •••, Brawl ••, Weaponry ••"), + type_: Fightning, + ), + ( + name: "TriggerDiscipline", + cost: Number(1), + requirements: Some("Wits ••, Firearms ••"), + type_: Fightning, + ), + ( + name: "TrunkSqueeze", + cost: Number(2), + requirements: Some("Brawl ••"), + type_: Fightning, + ), + ( + name: "Ambidextrous", + cost: Number(3), + requirements: None, + type_: Physical, + ), + ( + name: "AutomotiveGenius", + cost: Number(1), + requirements: Some("Crafts •••, Drive •, Science •"), + type_: Physical, + ), + ( + name: "CovertOperative", + cost: Number(1), + requirements: Some("Wits •••, Dexterity •••, Stealth ••"), + type_: Physical, + ), + ( + name: "CrackDriver", + cost: Vec([ + 2, + 3, + ]), + requirements: Some("Drive •••"), + type_: Physical, + ), + ( + name: "Demolisher", + cost: Range(2, 4), + requirements: Some("Strength or Intelligence •••"), + type_: Physical, + ), + ( + name: "DoubleJointed", + cost: Number(2), + requirements: Some("Dexterity •••"), + type_: Physical, + ), + ( + name: "FleetOfFoot", + cost: Range(2, 4), + requirements: Some("Athletics ••"), + type_: Physical, + ), + ( + name: "Freediving", + cost: Number(1), + requirements: Some("Athletics ••"), + type_: Physical, + ), + ( + name: "Giant", + cost: Number(3), + requirements: None, + type_: Physical, + ), + ( + name: "Hardy", + cost: Range(2, 4), + requirements: Some("Stamina •••"), + type_: Physical, + ), + ( + name: "Greyhound", + cost: Number(1), + requirements: Some("Athletics •••, Wits •••, Stamina •••"), + type_: Physical, + ), + ( + name: "IronSkin", + cost: Range(2, 3), + requirements: Some("Brawl ••, Stamina •••"), + type_: Physical, + ), + ( + name: "IronStamina", + cost: Range(2, 4), + requirements: Some("Stamina or Resolve •••"), + type_: Physical, + ), + ( + name: "QuickDraw", + cost: Number(1), + requirements: Some("Wits •••"), + type_: Physical, + ), + ( + name: "PunchDrunk", + cost: Number(2), + requirements: Some("Willpower ••••••"), + type_: Physical, + ), + ( + name: "Relentless", + cost: Number(1), + requirements: Some("Athletics ••, Stamina •••"), + type_: Physical, + ), + ( + name: "Roadkill", + cost: Number(3), + requirements: Some("Aggressive Driving ••"), + type_: Physical, + ), + ( + name: "SeizingTheEdge", + cost: Number(2), + requirements: Some("Wits •••, Composure •••"), + type_: Physical, + ), + ( + name: "SleightOfHand", + cost: Number(2), + requirements: Some("Larceny •••"), + type_: Physical, + ), + ( + name: "SmallFramed", + cost: Number(2), + requirements: None, + type_: Physical, + ), + ( + name: "Survivalist", + cost: Number(1), + requirements: Some("Survival •••, Iron Stamina •••"), + type_: Physical, + ), + ( + name: "AirOfMenace", + cost: Number(2), + requirements: Some("Intimidation ••"), + type_: Social, + ), + ( + name: "Allies", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "AlternateIdentity", + cost: Range(2, 4), + requirements: None, + type_: Social, + ), + ( + name: "Anonymity", + cost: Range(2, 6), + requirements: Some("No Fame"), + type_: Social, + ), + ( + name: "Barfly", + cost: Number(2), + requirements: Some("Socialize ••"), + type_: Social, + ), + ( + name: "ClosedBook", + cost: Range(2, 6), + requirements: Some("Manipulation •••, Resolve •••"), + type_: Social, + ), + ( + name: "CohesiveUnit", + cost: Range(2, 4), + requirements: Some("Presence •••"), + type_: Social, + ), + ( + name: "Contacts", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "Defender", + cost: Range(2, 4), + requirements: None, + type_: Social, + ), + ( + name: "Empath", + cost: Number(2), + requirements: Some("Empathy ••"), + type_: Social, + ), + ( + name: "Fame", + cost: Range(2, 4), + requirements: Some("No Anonymity"), + type_: Social, + ), + ( + name: "Fixer", + cost: Number(2), + requirements: Some("Contacts ••, Wits •••"), + type_: Social, + ), + ( + name: "HobbyistClique", + cost: Number(2), + requirements: Some("Any Skill ••"), + type_: Social, + ), + ( + name: "Inspiring", + cost: Number(3), + requirements: Some("Presence •••"), + type_: Social, + ), + ( + name: "IronWill", + cost: Number(2), + requirements: Some("Resolve ••••"), + type_: Social, + ), + ( + name: "Mentor", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "Peacemaker", + cost: Range(3, 4), + requirements: Some("Wits •••, Empathy •••"), + type_: Social, + ), + ( + name: "Pusher", + cost: Number(1), + requirements: Some("Persuasion ••"), + type_: Social, + ), + ( + name: "Resources", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "Retainer", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "SafePlace", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "SmallUnitTactics", + cost: Number(2), + requirements: Some("Presence •••"), + type_: Social, + ), + ( + name: "SpinDoctor", + cost: Number(1), + requirements: Some("Manipulation •••, Subterfuge ••"), + type_: Social, + ), + ( + name: "Staff", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "Status", + cost: Range(2, 6), + requirements: None, + type_: Social, + ), + ( + name: "StrikingLooks", + cost: Range(2, 3), + requirements: None, + type_: Social, + ), + ( + name: "SupportNetwork", + cost: Range(2, 6), + requirements: Some("Appropriate Social Merit"), + type_: Social, + ), + ( + name: "Sympathetic", + cost: Number(2), + requirements: None, + type_: Social, + ), + ( + name: "TableTurner", + cost: Number(1), + requirements: Some("Composure •••, Manipulation •••, Wits •••"), + type_: Social, + ), + ( + name: "TakesOneToKnowOne", + cost: Number(1), + requirements: Some("Vice"), + type_: Social, + ), + ( + name: "Taste", + cost: Number(1), + requirements: Some("Crafts ••"), + type_: Social, + ), + ( + name: "TrueFriend", + cost: Number(3), + requirements: None, + type_: Social, + ), + ( + name: "Untouchable", + cost: Number(1), + requirements: Some("Manipulation •••, Subterfuge ••"), + type_: Social, + ), +] \ No newline at end of file diff --git a/cofd/macros/src/lib.rs b/cofd/macros/src/lib.rs index 4f75f33..ce63176 100644 --- a/cofd/macros/src/lib.rs +++ b/cofd/macros/src/lib.rs @@ -5,7 +5,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::Error; -use cofd_util::scraper::Gift; +use cofd_util::scraper::{Gift, Merit}; macro_rules! derive_error { ($string: tt) => { @@ -129,3 +129,21 @@ pub fn gifts(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { proc_macro::TokenStream::from(expanded) } + +#[proc_macro] +pub fn merits(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let path = Path::new(&env::var("CARGO_MANIFEST_DIR").expect("")) + .join("data") + .join("merits_universal.ron"); + + let Ok(str) = fs::read_to_string(path) else { + return derive_error!("Error reading merits_universal.ron file"); + }; + + let vec: Vec = ron::from_str(&str).expect("Parsing error"); + for merit in vec {} + + let expanded = quote! {}; + + proc_macro::TokenStream::from(expanded) +} diff --git a/cofd/util/src/scraper/mod.rs b/cofd/util/src/scraper/mod.rs index 3f76c2f..6e4378e 100644 --- a/cofd/util/src/scraper/mod.rs +++ b/cofd/util/src/scraper/mod.rs @@ -1,9 +1,9 @@ use serde::{Deserialize, Serialize}; -pub enum Data { - Gift(Gift), - Facet(Facet), -} +// pub enum Data { +// Gift(Gift), +// Facet(Facet), +// } #[derive(Debug, Serialize, Deserialize)] pub struct Gift { @@ -45,3 +45,50 @@ impl Facet { } } } + +#[derive(Debug, Serialize, Deserialize)] +pub enum MeritType { + Mental, + Physical, + Social, + Supernatural, + Fightning, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum MeritRating { + Number(u8), + Range(u8, u8), + Vec(Vec), +} + +// #[derive(Debug, Serialize, Deserialize)] +// pub enum Requirement { +// Attribute(String, u8), +// Skill(String, u8), +// } + +#[derive(Debug, Serialize, Deserialize)] +pub struct Merit { + name: String, + cost: MeritRating, + // requirements: Vec, + requirements: Option, + type_: MeritType, +} + +impl Merit { + pub fn new( + name: String, + cost: MeritRating, + requirements: Option, + type_: MeritType, + ) -> Self { + Self { + name, + cost, + requirements, + type_, + } + } +} diff --git a/cofd/xtask/src/bin/gifts.rs b/cofd/xtask/src/bin/gifts.rs index 6e9b7e7..87c90e6 100644 --- a/cofd/xtask/src/bin/gifts.rs +++ b/cofd/xtask/src/bin/gifts.rs @@ -1,45 +1,49 @@ +use std::collections::HashMap; + use cofd_util::scraper::{Facet, Gift}; use convert_case::Casing; -pub fn parse_gifts(vec: Vec>) -> Vec { - let mut gift = None; +pub fn parse_gifts(map: HashMap>>) -> Vec { let mut gifts = Vec::new(); - for vec in vec { - if vec.len() == 2 { - if let Some(g) = gift { - gifts.push(g); - gift = None; - } + for (cat, vec) in map { + let mut gift = None; + for vec in vec { + if vec.len() == 1 { + if let Some(g) = gift { + gifts.push(g); + gift = None; + } - if let Some(name) = vec.first() { - if !name.contains('(') { - gift = Some(Gift::new( - gift_name_to_id(name).to_string(), - vec.last().unwrap().split(' ').next().unwrap().to_string(), - )); + if let Some(name) = vec.first() { + if !name.contains('(') { + gift = Some(Gift::new( + gift_name_to_id(name).to_string(), + cat.split(' ').next().unwrap().to_string(), + )); + } } - } - } else if let Some(gift) = &mut gift { - let id = facet_name_to_id(&vec[0]); + } else if let Some(gift) = &mut gift { + let id = facet_name_to_id(&vec[0]); - let str = vec[1].clone(); + let str = vec[1].clone(); - if str.contains('•') { - gift.facets.push(Facet::Moon { - name: id, - level: str.chars().count() as u16, - }); - } else { - gift.facets.push(Facet::Other { - name: id, - renown: str, - }); + if str.contains('•') { + gift.facets.push(Facet::Moon { + name: id, + level: str.chars().count() as u16, + }); + } else { + gift.facets.push(Facet::Other { + name: id, + renown: str, + }); + } } } - } - if let Some(g) = gift { - gifts.push(g); + if let Some(g) = gift { + gifts.push(g); + } } gifts diff --git a/cofd/xtask/src/bin/merits.rs b/cofd/xtask/src/bin/merits.rs new file mode 100644 index 0000000..55853fd --- /dev/null +++ b/cofd/xtask/src/bin/merits.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; + +use cofd_util::scraper::{Merit, MeritType}; +use convert_case::{Case, Casing}; + +pub fn parse_merits(map: HashMap>>) -> Vec { + let mut merits = Vec::new(); + + for (cat, vec) in map { + for vec in vec { + let cost = vec[1].trim(); + let mut rating = cofd_util::scraper::MeritRating::Number(0); + + if cost.chars().all(|char| char.eq(&'•')) { + rating = cofd_util::scraper::MeritRating::Number( + cost.chars().count().try_into().unwrap(), + ); + } else if cost.contains("or") { + let cost = cost.replace("or", ","); + let mut vec: Vec = Vec::new(); + + for part in cost.split(',') { + let part = part.trim(); + if !part.is_empty() { + vec.push(part.chars().count().try_into().unwrap()); + } + } + + if !vec.is_empty() { + rating = cofd_util::scraper::MeritRating::Vec(vec); + } + } else if cost.contains("to") { + let mut split = cost.split("to"); + + let a = split.next().unwrap().chars().count(); + let b = split.next().unwrap().chars().count(); + + rating = cofd_util::scraper::MeritRating::Range( + a.try_into().unwrap(), + b.try_into().unwrap(), + ); + } + + let type_ = match cat.as_str() { + "Mental Merits" => MeritType::Mental, + "Social Merits" => MeritType::Social, + "Physical Merits" => MeritType::Physical, + "Fighting Merits" => MeritType::Fightning, + "Supernatural Merits" => MeritType::Supernatural, + _ => todo!(), + }; + + merits.push(Merit::new( + vec[0].to_case(Case::Pascal), + rating, + if vec[2].is_empty() { + None + } else { + Some(vec[2].clone()) + }, + type_, + )); + } + } + + merits +} + +// fn facet_name_to_id(name: &str) -> String { +// name.replace(['\'', ','], "") +// .to_case(convert_case::Case::Pascal) +// } diff --git a/cofd/xtask/src/bin/scraper.rs b/cofd/xtask/src/bin/scraper.rs index 190cb41..2007813 100644 --- a/cofd/xtask/src/bin/scraper.rs +++ b/cofd/xtask/src/bin/scraper.rs @@ -1,12 +1,15 @@ #![feature(iter_array_chunks)] +use std::collections::HashMap; use std::path::Path; use std::{fs, path::PathBuf}; use lazy_static::lazy_static; use reqwest::Url; +use ron::ser::PrettyConfig; use scraper::{ElementRef, Html, Selector}; mod gifts; +mod merits; lazy_static! { static ref PATH: PathBuf = @@ -16,19 +19,39 @@ lazy_static! { .join("data"); } +enum PageType { + Gifts, + Merits, +} + #[tokio::main] async fn main() -> Result<(), Box> { - let urls = ["https://codexofdarkness.com/wiki/Gifts"]; + let urls = [ + ("https://codexofdarkness.com/wiki/Gifts", PageType::Gifts), + ( + "https://codexofdarkness.com/wiki/Merits,_Universal", + PageType::Merits, + ), + // ( + // "https://codexofdarkness.com/wiki/Merits,_Vampire", + // PageType::Merits, + // ), + ]; + + let mut handles = Vec::new(); + for (url, page) in urls { + handles.push(tokio::spawn(download(url.to_string(), page))); + } - for url in &urls { - download(url).await?; + for handle in handles { + handle.await.unwrap()?; } Ok(()) } -async fn download(url: &str) -> anyhow::Result<()> { - let url = Url::parse(url).expect("Invalid url"); +async fn download(url: String, page: PageType) -> anyhow::Result<()> { + let url = Url::parse(&url).expect("Invalid url"); let page_name = url .path_segments() @@ -43,7 +66,8 @@ async fn download(url: &str) -> anyhow::Result<()> { std::fs::create_dir_all(&path)?; } - let html_path = path.join(format!("{page_name}.html")); + let name = page_name.replace(',', ""); + let html_path = path.join(format!("{name}.html")); let text; if !html_path.exists() { @@ -57,18 +81,18 @@ async fn download(url: &str) -> anyhow::Result<()> { text = fs::read_to_string(html_path)?; } - parse(&page_name, &text)?; + parse(&name, &text, page)?; Ok(()) } -fn parse(page_name: &str, text: &str) -> anyhow::Result<()> { +fn parse(page_name: &str, text: &str, page: PageType) -> anyhow::Result<()> { let document = Html::parse_document(text); let selector = Selector::parse(".mw-parser-output > section, h2").unwrap(); let table_sel = Selector::parse("table").unwrap(); - let mut vec = Vec::new(); + let mut map = HashMap::new(); for [header, section] in document.select(&selector).skip(1).array_chunks() { let title = header.text().last().expect("Last text in header"); let table = section.select(&table_sel).next().unwrap(); @@ -81,21 +105,20 @@ fn parse(page_name: &str, text: &str) -> anyhow::Result<()> { .filter_map(ElementRef::wrap) .skip(1) { - let mut vecc = Vec::new(); + let mut vec = Vec::new(); for td in tr.children().filter_map(ElementRef::wrap) { - vecc.push(td.inner_html().trim().to_string()); + vec.push(td.inner_html().trim().to_string()); } - if vecc.len() == 1 { - vecc.push(title.to_string()); - } - vec.push(vecc); + map.entry(title.to_string()).or_insert(Vec::new()).push(vec); } } - let txt = match page_name { - "Gifts" => ron::ser::to_string(&gifts::parse_gifts(vec))?, - _ => String::new(), + let txt = match page { + PageType::Gifts => ron::ser::to_string(&gifts::parse_gifts(map))?, + PageType::Merits => { + ron::ser::to_string_pretty(&merits::parse_merits(map), PrettyConfig::default())? + } // _ => String::new(), }; fs::write(PATH.join(format!("{}.ron", page_name.to_lowercase())), txt)?; From f99165479812f1f4d2a38a7ee0ed4a7210420ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Maria=C5=84ski?= Date: Wed, 26 Apr 2023 23:55:42 +0200 Subject: [PATCH 4/4] misc --- cofd/app/src/component/traits.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/cofd/app/src/component/traits.rs b/cofd/app/src/component/traits.rs index 2a2b7dd..dfd989e 100644 --- a/cofd/app/src/component/traits.rs +++ b/cofd/app/src/component/traits.rs @@ -6,7 +6,12 @@ use iced_lazy::Component; use cofd::{character::ArmorStruct, prelude::*}; -use crate::{fl, i18n::flt, Element, INPUT_PADDING}; +use crate::{ + fl, + i18n::flt, + widget::dots::{Shape, SheetDots}, + Element, INPUT_PADDING, +}; struct Traits { size: u16, @@ -37,7 +42,7 @@ pub fn traits_component( } #[derive(Clone)] -pub struct Event(String, Trait); +pub struct Event(u16, Trait); impl TraitsComponent { fn new(character: &Character, on_change: impl Fn(u16, Trait) -> Message + 'static) -> Self { @@ -67,22 +72,14 @@ impl Component for TraitsComponent { type Event = Event; fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option { - if let Ok(val) = event.0.parse() { - Some((self.on_change)(val, event.1)) - } else if event.0.is_empty() { - Some((self.on_change)(0, event.1)) - } else { - None - } + Some((self.on_change)(event.0, event.1)) } fn view(&self, _state: &Self::State) -> Element { let beats = row![ text(format!("{}:", fl!("beats"))), text_input("", &format!("{}", self.traits.beats), |val| { - // if let Some(val) = val.parse() { - Event(val, Trait::Beats) - // } + Event(val.parse().unwrap_or(0), Trait::Beats) }) .padding(INPUT_PADDING) ]; @@ -94,7 +91,7 @@ impl Component for TraitsComponent { flt(&self.traits.splat, Some("beats")).unwrap() )), text_input("", &format!("{}", self.traits.alternate_beats), |val| { - Event(val, Trait::AlternateBeats) + Event(val.parse().unwrap_or(0), Trait::AlternateBeats) }) .padding(INPUT_PADDING) ]