Skip to content

Commit

Permalink
poe: implements node overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
Dav1dde committed Sep 11, 2023
1 parent adcb33b commit 5dbd8ba
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 10 deletions.
1 change: 1 addition & 0 deletions app/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl Build {
}

impl Build {
// TODO: this needs a rewrite, accepting additional data from /json is awkward
pub fn new(content: String, nodes: Vec<Nodes>) -> crate::Result<Self> {
let pob = SerdePathOfBuilding::from_export(&content)?;

Expand Down
66 changes: 59 additions & 7 deletions app/src/components/pob_tree_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,23 @@ use crate::{
};

#[derive(Debug)]
struct Tree {
struct Tree<'build> {
name: String,
image_url: String,
tree_url: String,
active: bool,
nodes: Nodes,
nodes: &'build Nodes,
overrides: Vec<Override<'build>>,
allocated: usize,
}

#[derive(Debug)]
struct Override<'build> {
count: usize,
name: &'build str,
effect: &'build str,
}

#[component]
pub fn PobTreePreview<'a, G: Html>(cx: Scope<'a>, build: &'a Build) -> View<G> {
let trees = build
Expand All @@ -32,7 +40,8 @@ pub fn PobTreePreview<'a, G: Html>(cx: Scope<'a>, build: &'a Build) -> View<G> {
image_url: get_tree_image_url(&spec, &url)?,
tree_url: url,
active: spec.active,
nodes: nodes.clone(),
nodes,
overrides: extract_overrides(spec.overrides),
allocated: spec.nodes.len(),
})
})
Expand Down Expand Up @@ -102,7 +111,7 @@ pub fn PobTreePreview<'a, G: Html>(cx: Scope<'a>, build: &'a Build) -> View<G> {
state.borrow_mut().remove_pointer(&event);
};

let nodes = create_memo(cx, move || render_nodes(cx, &current_tree.get().nodes));
let nodes = create_memo(cx, move || render_nodes(cx, &current_tree.get()));
let tree_background = create_memo(cx, || {
format!("background-image: url({})", current_tree.get().image_url)
});
Expand Down Expand Up @@ -188,7 +197,23 @@ fn resolve_level(allocated: usize) -> (usize, usize) {
(allocated - asc, 1 + allocated - asc - bandits - quests)
}

pub fn render_nodes<G: GenericNode + Html>(cx: Scope, nodes: &Nodes) -> View<G> {
fn extract_overrides(mut overrides: Vec<pob::Override<'_>>) -> Vec<Override<'_>> {
overrides.sort_unstable_by_key(|k| (k.name, k.effect));

overrides
.into_iter()
.dedup_by_with_count(|a, b| (a.name, a.effect) == (b.name, b.effect))
.map(|(count, o)| Override {
count,
name: o.name,
effect: o.effect,
})
.collect()
}

fn render_nodes<G: GenericNode + Html>(cx: Scope, tree: &Tree<'_>) -> View<G> {
let nodes = tree.nodes;

if nodes.is_empty() {
return view! { cx,
div(class="text-stone-200 hidden lg:block text-center") {
Expand All @@ -197,6 +222,12 @@ pub fn render_nodes<G: GenericNode + Html>(cx: Scope, nodes: &Nodes) -> View<G>
};
}

let overrides = tree
.overrides
.iter()
.map(|o| render_override(cx, o))
.collect_view();

let keystones = nodes
.keystones
.iter()
Expand All @@ -210,8 +241,29 @@ pub fn render_nodes<G: GenericNode + Html>(cx: Scope, nodes: &Nodes) -> View<G>
.collect_view();

view! { cx,
div(class="grid grid-cols-fit-keystone gap-2 lg:gap-1") { (keystones) }
div(class="grid grid-cols-fit-mastery gap-2 lg:gap-1") { (masteries) }
div(class="grid grid-cols-fit-mastery gap-2 lg:gap-1 empty:hidden") { (overrides) }
div(class="grid grid-cols-fit-keystone gap-2 lg:gap-1 empty:hidden") { (keystones) }
div(class="grid grid-cols-fit-mastery gap-2 lg:gap-1 empty:hidden") { (masteries) }
}
}

fn render_override<G: GenericNode + Html>(cx: Scope, r#override: &Override) -> View<G> {
let name = r#override.name.to_owned();
let effect = r#override.effect.to_owned();
let count = if r#override.count > 1 {
format!("(x{})", r#override.count)
} else {
String::new()
};

view! { cx,
div(class="bg-slate-900 rounded-xl px-4 py-3") {
div(class="mb-2 text-stone-200 text-sm md:text-base") {
span() { (name) }
span(class="text-xs ml-1") { (count) }
}
ul(class="flex flex-col gap-2 pb-1 whitespace-pre-line text-xs md:text-sm text-slate-400") { (effect) }
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions pob/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ quick-xml = { workspace = true, features = ["serialize"] }
thiserror.workspace = true
encoding.workspace = true
serde_path_to_error = { workspace = true, optional = true }

[dev-dependencies]
serde_path_to_error.workspace = true
8 changes: 8 additions & 0 deletions pob/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub struct TreeSpec<'a> {
pub nodes: &'a [u32],
pub mastery_effects: &'a [(u32, u32)],
pub sockets: Vec<Socket>,
pub overrides: Vec<Override<'a>>,

/// Whether the tree spec is active/selected
pub active: bool,
Expand All @@ -59,6 +60,13 @@ pub struct Socket {
pub item_id: u16,
}

#[derive(Debug)]
pub struct Override<'a> {
pub name: &'a str,
pub node_id: u32,
pub effect: &'a str,
}

#[derive(Debug)]
pub struct SkillSet<'a> {
pub id: u16,
Expand Down
18 changes: 18 additions & 0 deletions pob/src/serde/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ pub(crate) struct Spec {
pub url: Option<String>,
#[serde(default, rename = "Sockets")]
pub sockets: Sockets,
#[serde(default, rename = "Overrides")]
pub overrides: Overrides,
#[serde(default, rename = "treeVersion")]
pub version: Option<String>,
}
Expand All @@ -245,6 +247,22 @@ pub(crate) struct Socket {
pub item_id: u16,
}

#[derive(Default, Debug, Deserialize)]
pub(crate) struct Overrides {
#[serde(default, rename = "Override")]
pub overrides: Vec<Override>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct Override {
#[serde(default, rename = "dn")]
pub name: String,
#[serde(default, rename = "nodeId")]
pub node_id: u32,
#[serde(default, rename = "$value")]
pub effect: String,
}

#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Items {
Expand Down
52 changes: 50 additions & 2 deletions pob/src/serde/pob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ impl SerdePathOfBuilding {
pub fn from_xml(s: &str) -> Result<Self> {
let mut xd = quick_xml::de::Deserializer::from_reader(s.as_bytes());

#[cfg(feature = "better-errors")]
#[cfg(any(feature = "better-errors", test))]
let pob = match serde_path_to_error::deserialize(&mut xd) {
Ok(pob) => pob,
Err(err) => {
Expand All @@ -20,7 +20,7 @@ impl SerdePathOfBuilding {
}
};

#[cfg(not(feature = "better-errors"))]
#[cfg(not(any(feature = "better-errors", test)))]
let pob = serde::Deserialize::deserialize(&mut xd)
.map_err(|e| Error::ParseXml("Unknown".to_owned(), e))?;

Expand Down Expand Up @@ -267,6 +267,16 @@ impl crate::PathOfBuilding for SerdePathOfBuilding {
item_id: s.item_id,
})
.collect(),
overrides: spec
.overrides
.overrides
.iter()
.map(|o| crate::Override {
node_id: o.node_id,
name: &o.name,
effect: &o.effect,
})
.collect(),
mastery_effects: &spec.mastery_effects,
active: self.pob.tree.active_spec as usize == i + 1,
})
Expand Down Expand Up @@ -371,6 +381,7 @@ mod tests {
static V318_SKILLSET: &str = include_str!("../../test/318_skillset.xml");
static V319_MASTERY_EFFECTS: &str = include_str!("../../test/319_mastery_effects.xml");
static V320_IMPENDING_DOOM: &str = include_str!("../../test/320_impending_doom.xml");
static V322_OVERRIDES: &str = include_str!("../../test/322_overrides.xml");

#[test]
fn parse_v316_empty() {
Expand Down Expand Up @@ -457,4 +468,41 @@ mod tests {
// The skill's `nameSpec` is empty and needs to be supplied from `crate::gems`.
assert_eq!(pob.skill_sets()[0].skills[1].gems[0].name, "Tornado");
}

#[test]
fn parse_v322_overrides() {
let pob = SerdePathOfBuilding::from_xml(V322_OVERRIDES).unwrap();

let overrides = &pob.tree_specs()[0].overrides;
assert_eq!(overrides.len(), 21);
assert_eq!(
overrides
.iter()
.filter(|o| o.name == "Honoured Tattoo of the Oak")
.count(),
19
);
assert_eq!(
overrides
.iter()
.filter(|o| o.effect == "2% increased maximum Life")
.count(),
19
);

let maata = overrides
.iter()
.find(|o| o.name == "Loyalty Tattoo of Maata")
.unwrap();
assert_eq!(maata.node_id, 4502);
// \t come from indentation in the XML
assert_eq!(maata.effect, "A\n\t\t\t\t\tB\n\t\t\t\t\tC");

let wise = overrides
.iter()
.find(|o| o.name == "Honoured Tattoo of the Wise")
.unwrap();
assert_eq!(wise.node_id, 50197);
assert_eq!(wise.effect, "+1\n\t\t\t\t\tLimited to 1");
}
}
96 changes: 96 additions & 0 deletions pob/test/322_overrides.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<PathOfBuilding>
<Build level="98" targetVersion="3_0" pantheonMajorGod="None" bandit="None" className="Shadow" ascendClassName="Saboteur" characterLevelAutoMode="false" mainSocketGroup="8" viewMode="IMPORT" pantheonMinorGod="None">
<PlayerStat stat="AverageHit" value="579451.443"/>
</Build>
<Skills></Skills>
<Tree activeSpec="1">
<Spec ascendClassId="3" masteryEffects="{48505,21468},{41415,31822},{43818,47642},{12873,9074},{25011,38454},{12382,64381},{28284,26621},{4139,34242},{89,21313}" nodes="7388,2292,36634,16940,33783,6289,26725,11088,10511,7374,31703,12873,44967,26196,25011,49080,33287,18635,65760,4139,65698,15549,39834,1203,3656,48287,21835,35894,39648,29353,34098,38190,48778,64842,17821,25531,62319,63723,53118,65769,46519,26712,4397,13009,50570,41415,25058,65762,44169,43818,65770,42744,60398,37671,3533,38129,41472,55190,61653,31931,27659,89,25167,57044,14930,49772,63135,11420,14103,50288,61471,62429,57746,15631,31875,55485,30380,3644,5233,10532,65706,65034,64587,2491,12382,57279,50862,8948,27929,33740,21650,49254,15064,21330,19884,33631,18901,47366,29199,44908,41263,65108,36949,28284,4502,5087,26866,20546,34400,27203,37999,11716,65696,10031,59928,44683,46910,45608,1822,48362,17735,50197,55676,50422,21301,18769,35288,15405,63282,11334,48505" treeVersion="3_22" classId="6">
<URL>
https://www.pathofexile.com/passive-skill-tree/AAAABgYDehzcCPSPGkIsg_cYkWhlK1ApDxzOe9cySa-nZlRhs7-4ggcQKzy9m5oEsw5IvJ9VS4w2muByqYUylS6-iv1KRZ1ju_Nv-OvPfrW3aFgRLTLRxYqhx2HirImrKqb46-6TJw3NlPGiANeW8NV8u2wLAFliT97UOlLCbPafLJw3F8Rw8B_z3eGSPQ98g9i9dqwOPBRxKST-CvxLCbswXt-_xq4i9G0Zg8xUksBmOthTUk2sg19J1bkGcg-vbKEv_lSQVW58EZYT32jyUEKGYGpDlG8txCcv6hi3PrIoBx686kVHxBXZfMT2UzVJUYnYPC33MixGvXkHAOAAogDpAOIA6gCqAKAJI3IySZY2YbOFwhArfE6hx7oaqypTQQBZ-30wXmf9bnxT3L15
</URL>
<Sockets>
<Socket nodeId="10532" itemId="2"/>
<Socket nodeId="36634" itemId="3"/>
<Socket nodeId="26725" itemId="10"/>
<Socket nodeId="55190" itemId="5"/>
<Socket nodeId="46519" itemId="11"/>
<Socket nodeId="33631" itemId="9"/>
<Socket nodeId="2491" itemId="1"/>
<Socket nodeId="26196" itemId="7"/>
<Socket nodeId="49080" itemId="6"/>
<Socket nodeId="44169" itemId="4"/>
<Socket nodeId="41263" itemId="8"/>
</Sockets>
<Overrides>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="7388" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Loyalty Tattoo of Maata" icon="Art/2DArt/SkillIcons/TawhoaTribeSkill.png" nodeId="4502" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/TawhoaTribePassiveBG.png">
A
B
C
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="8948" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="20546" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="36858" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="33783" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="32710" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="15549" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="27415" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Wise" icon="Art/2DArt/SkillIcons/passives/HinekoraPassiveIcon3.png" nodeId="50197" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
+1
Limited to 1
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="17735" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="37999" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="13009" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="3656" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="27659" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="29199" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="60398" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="4397" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="48778" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="11334" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
<Override dn="Honoured Tattoo of the Oak" icon="Art/2DArt/SkillIcons/passives/accuracyint.png" nodeId="37671" activeEffectImage="Art/2DArt/UIImages/InGame/AncestralTrial/PassiveTreeTattoos/HinekoraPassiveBG.png">
2% increased maximum Life
</Override>
</Overrides>
</Spec>
</Tree>
<Notes></Notes>
</PathOfBuilding>
2 changes: 1 addition & 1 deletion shared/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct Paste {
/// A list of node description to display per tree spec.
///
/// List is in the same order as the tree specs.
#[serde(default)]
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub nodes: Vec<Nodes>,
}

Expand Down

0 comments on commit 5dbd8ba

Please sign in to comment.