poe2-agent 0.5.0

AI agent for Path of Exile 2 build analysis
Documentation
//! Create and verify a dummy PoB build using the headless Lua engine.
//!
//! This example:
//! 1. Initializes PobHeadless (same as the agent uses)
//! 2. Loads the existing Ranger build to verify the pipeline
//! 3. Loads a custom Witch build and verifies it
//! 4. Writes the Witch build XML to a file
//!
//! Usage:
//!   cargo run --example create_build

use poe2_agent::pob::PobHeadless;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pob_rel = std::path::Path::new("vendor/PathOfBuilding-PoE2");
    let pob_path = pob_rel
        .canonicalize()
        .expect("vendor/PathOfBuilding-PoE2 not found. Run from poe2-agent/ root.");
    let pob_path_str = pob_path.to_str().unwrap();

    println!("Initializing PobHeadless...");
    let mut pob = PobHeadless::new()?;
    pob.init(pob_path_str)?;
    println!("PoB initialized.");

    // Test 1: Load existing Ranger build
    println!("\n=== Test 1: Ranger_with_gear.xml ===");
    let ranger_xml = std::fs::read_to_string(pob_path.join("src/Builds/Ranger_with_gear.xml"))?;
    pob.load_build_xml(&ranger_xml)?;
    let ranger_stats = pob.calculate()?;
    println!("Ranger stats: {:?}", ranger_stats);
    println!(
        "  DPS: {:.1}, Life: {:.0}, Fire: {}, Cold: {}, Lightning: {}",
        ranger_stats.total_dps,
        ranger_stats.life,
        ranger_stats.fire_res,
        ranger_stats.cold_res,
        ranger_stats.lightning_res
    );

    // Test 2: Load custom Witch build
    println!("\n=== Test 2: Witch dummy build ===");
    pob.load_build_xml(WITCH_BUILD_XML)?;
    let witch_stats = pob.calculate()?;
    println!("Witch stats: {:?}", witch_stats);
    println!(
        "  DPS: {:.1}, Life: {:.0}, Fire: {}, Cold: {}, Lightning: {}",
        witch_stats.total_dps,
        witch_stats.life,
        witch_stats.fire_res,
        witch_stats.cold_res,
        witch_stats.lightning_res
    );

    // Test 3: Extended queries
    println!("\n=== Test 3: Extended queries ===");
    let build_stats = pob.query_build_stats()?;
    println!(
        "Build stats JSON:\n{}",
        serde_json::to_string_pretty(&build_stats)?
    );

    let skill_list = pob.query_skill_list()?;
    println!(
        "\nSkill list JSON:\n{}",
        serde_json::to_string_pretty(&skill_list)?
    );

    // Write the Witch build to a file
    let output_path = pob_path.join("src/Builds/Witch_dummy.xml");
    std::fs::write(&output_path, WITCH_BUILD_XML)?;
    println!("\nWitch build written to {:?}", output_path);

    Ok(())
}

/// Witch build with Ice Nova + Concentrated Effect, basic gear, and some passives.
/// Modeled after the working Ranger_with_gear.xml format.
const WITCH_BUILD_XML: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<PathOfBuilding2>
	<Build level="70" ascendClassName="None" characterLevelAutoMode="false" className="Witch" mainSocketGroup="1" viewMode="TREE" targetVersion="0_1">
		<Buffs curseList="" combatList="" buffList=""/>
		<TimelessData socketFilterDistance="0" searchListFallback="" searchList="" devotionVariant2="1" devotionVariant1="1"/>
	</Build>
	<Config activeConfigSet="1">
		<ConfigSet title="Default" id="1">
			<Placeholder name="enemyLevel" number="82"/>
			<Placeholder name="enemyFireResist" number="50"/>
			<Placeholder name="enemyColdResist" number="50"/>
			<Placeholder name="enemyLightningResist" number="50"/>
			<Placeholder name="enemyChaosResist" number="0"/>
			<Placeholder name="enemyArmour" number="8063"/>
			<Placeholder name="enemyEvasion" number="1175"/>
			<Placeholder name="enemyDistance" number="20"/>
			<Placeholder name="enemyCritChance" number="5"/>
			<Placeholder name="enemyCritDamage" number="30"/>
			<Placeholder name="enemyDamageRollRange" number="70"/>
			<Placeholder name="enemySpeed" number="700"/>
			<Placeholder name="enemyPhysicalDamage" number="965"/>
			<Placeholder name="enemyFireDamage" number="965"/>
			<Placeholder name="enemyColdDamage" number="965"/>
			<Placeholder name="enemyLightningDamage" number="965"/>
			<Placeholder name="enemyChaosDamage" number="386"/>
			<Placeholder name="enemyFirePen" number="3"/>
			<Placeholder name="enemyColdPen" number="3"/>
			<Placeholder name="enemyLightningPen" number="3"/>
		</ConfigSet>
	</Config>
	<Calcs>
		<Input name="misc_buffMode" string="EFFECTIVE"/>
		<Input name="skill_number" number="1"/>
	</Calcs>
	<Notes>
		Dummy Witch build for testing the headless PoB integration.
		Ice Nova + Concentrated Effect with basic caster gear.
	</Notes>
	<Tree activeSpec="1">
		<Spec nodes="54447" masteryEffects="" classInternalId="5" classId="0" secondaryAscendClassId="nil" ascendancyInternalId="" ascendClassId="0" treeVersion="0_4">
			<URL></URL>
			<Sockets/>
			<Overrides/>
		</Spec>
	</Tree>
	<Import exportParty="false"/>
	<Skills sortGemsByDPS="true" sortGemsByDPSField="CombinedDPS" defaultGemLevel="normalMaximum" showSupportGemTypes="ALL" activeSkillSet="1" defaultGemQuality="0">
		<SkillSet id="1">
			<Skill includeInFullDPS="true" enabled="true" mainActiveSkill="1" mainActiveSkillCalcs="1" label="">
				<Gem quality="0" gemId="Metadata/Items/Gems/SkillGemIceNova" nameSpec="Ice Nova" skillId="IceNovaPlayer" variantId="IceNova" statSetIndexCalcs="nil" enabled="true" enableGlobal2="true" statSetIndex="nil" count="1" level="20" enableGlobal1="true"/>
				<Gem quality="0" gemId="Metadata/Items/Gems/SkillGemConcentratedEffectSupport" nameSpec="Concentrated Effect" skillId="SupportConcentratedEffect" variantId="ConcentratedEffect" statSetIndexCalcs="nil" enabled="true" enableGlobal2="true" statSetIndex="nil" count="1" level="20" enableGlobal1="true"/>
			</Skill>
		</SkillSet>
	</Skills>
	<Party destination="All" ShowAdvanceTools="false" append="false"/>
	<TreeView searchStr="" zoomY="0" zoomLevel="5" zoomX="0" showStatDifferences="true"/>
	<Items useSecondWeaponSet="false" activeItemSet="1" showStatDifferences="true">
		<Item id="1">
			Rarity: RARE
Spell Wand
Bone Wand
Crafted: true
Prefix: {range:0.5}SpellDamage5
Prefix: {range:0.5}AddedFireDamageToSpells3
Prefix: {range:0.5}AddedColdDamageToSpells3
Suffix: {range:0.5}CastSpeed3
Suffix: None
Suffix: None
Quality: 20
LevelReq: 60
Implicits: 0
60% increased Spell Damage
Adds 15 to 25 Fire Damage to Spells
Adds 13 to 21 Cold Damage to Spells
12% increased Cast Speed
		</Item>
		<Item id="2">
			Rarity: RARE
Resistance Ring
Prismatic Ring
Crafted: true
Prefix: None
Prefix: None
Prefix: None
Suffix: {range:0.5}FireResist5
Suffix: {range:0.5}ColdResist5
Suffix: {range:0.5}LightningResist5
LevelReq: 35
Implicits: 1
{tags:elemental,fire,cold,lightning,resistance}{range:0.5}+(7-10)% to all Elemental Resistances
+28% to Fire Resistance
+28% to Cold Resistance
+28% to Lightning Resistance
			<ModRange range="0.5" id="1"/>
			<ModRange range="0.5" id="2"/>
			<ModRange range="0.5" id="3"/>
			<ModRange range="0.5" id="4"/>
		</Item>
		<Item id="3">
			Rarity: RARE
Resistance Ring 2
Prismatic Ring
Crafted: true
Prefix: None
Prefix: None
Prefix: None
Suffix: {range:0.5}FireResist5
Suffix: {range:0.5}ColdResist5
Suffix: {range:0.5}LightningResist5
LevelReq: 35
Implicits: 1
{tags:elemental,fire,cold,lightning,resistance}{range:0.5}+(7-10)% to all Elemental Resistances
+28% to Fire Resistance
+28% to Cold Resistance
+28% to Lightning Resistance
			<ModRange range="0.5" id="1"/>
			<ModRange range="0.5" id="2"/>
			<ModRange range="0.5" id="3"/>
			<ModRange range="0.5" id="4"/>
		</Item>
		<Item id="4">
			Rarity: RARE
Life Belt
Heavy Belt
Charm Slots: 2
Crafted: true
Prefix: {range:0.5}IncreasedLife7
Prefix: None
Prefix: None
Suffix: {range:0.5}Strength7
Suffix: None
Suffix: None
LevelReq: 50
Implicits: 2
{range:0.5}(20-30)% increased Stun Threshold
{range:0.5}Has (1-3) Charm Slots
+92 to maximum Life
+29 to Strength
			<ModRange range="0.5" id="1"/>
			<ModRange range="0.5" id="2"/>
			<ModRange range="0.5" id="3"/>
			<ModRange range="0.5" id="4"/>
		</Item>
		<ItemSet useSecondWeaponSet="false" id="1">
			<Slot name="Weapon 1" itemPbURL="" itemId="1"/>
			<Slot name="Weapon 2" itemPbURL="" itemId="0"/>
			<Slot name="Helmet" itemPbURL="" itemId="0"/>
			<Slot name="Body Armour" itemPbURL="" itemId="0"/>
			<Slot name="Gloves" itemPbURL="" itemId="0"/>
			<Slot name="Boots" itemPbURL="" itemId="0"/>
			<Slot name="Amulet" itemPbURL="" itemId="0"/>
			<Slot name="Ring 1" itemPbURL="" itemId="2"/>
			<Slot name="Ring 2" itemPbURL="" itemId="3"/>
			<Slot name="Ring 3" itemPbURL="" itemId="0"/>
			<Slot name="Belt" itemPbURL="" itemId="4"/>
			<Slot name="Flask 1" itemPbURL="" itemId="0"/>
			<Slot name="Flask 2" itemPbURL="" itemId="0"/>
			<Slot name="Weapon 1 Swap" itemPbURL="" itemId="0"/>
			<Slot name="Weapon 2 Swap" itemPbURL="" itemId="0"/>
			<Slot name="Arm 1" itemPbURL="" itemId="0"/>
			<Slot name="Arm 2" itemPbURL="" itemId="0"/>
			<Slot name="Leg 1" itemPbURL="" itemId="0"/>
			<Slot name="Leg 2" itemPbURL="" itemId="0"/>
			<Slot name="Charm 1" itemPbURL="" itemId="0"/>
			<Slot name="Charm 2" itemPbURL="" itemId="0"/>
			<Slot name="Charm 3" itemPbURL="" itemId="0"/>
		</ItemSet>
		<TradeSearchWeights/>
	</Items>
</PathOfBuilding2>"#;