Skip to main content

edit_character/
edit_character.rs

1use halbu::quests::QuestFlag;
2use halbu::{CompatibilityChecks, Save, Strictness};
3
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    let input_path = "assets/test/Warlock_v105.d2s";
6    let output_name = "WarlockDemo";
7    let output_dir = "target/example-output";
8    let output_path = format!("{output_dir}/{output_name}.d2s");
9
10    let bytes = std::fs::read(input_path)?;
11    let parsed = Save::parse(&bytes, Strictness::Strict)?;
12    if !parsed.issues.is_empty() {
13        return Err(format!("fixture parsed with issues: {:?}", parsed.issues).into());
14    }
15
16    let mut save = parsed.save;
17    let target_format = save.format();
18
19    println!(
20        "Loaded {:?} / {} / lvl {}",
21        target_format,
22        save.character.name,
23        save.character.level()
24    );
25
26    // Keep the file name and in-game name aligned.
27    save.character.name = output_name.to_string();
28
29    // Keep character.level and attributes.level in sync.
30    save.set_level(75);
31
32    // Main stats.
33    save.attributes.strength.value = 220;
34    save.attributes.dexterity.value = 175;
35    save.attributes.vitality.value = 260;
36    save.attributes.energy.value = 110;
37    save.attributes.statpts.value = 25;
38    save.attributes.newskills.value = 10;
39    save.attributes.gold.value = 200_000;
40    save.attributes.goldbank.value = 2_500_000;
41
42    // These stats are fixed-point in the save file (Q8), so the helpers take in-game values.
43    save.attributes.set_max_hp(2200);
44    save.attributes.set_hp(2200);
45    save.attributes.set_max_mana(900);
46    save.attributes.set_mana(900);
47    save.attributes.set_max_stamina(1200);
48    save.attributes.set_stamina(1200);
49
50    // Use the D2R name lookup when it exists.
51    // Fall back to raw slot indexes when it does not.
52    if save.skills.set_by_name_d2r(save.character.class, "Bash", 20).is_err() {
53        save.skills.set(0, 20);
54    }
55    if save.skills.set_by_name_d2r(save.character.class, "Battle Orders", 20).is_err() {
56        save.skills.set(23, 20);
57    }
58    if save.skills.set_by_name_d2r(save.character.class, "Whirlwind", 20).is_err() {
59        save.skills.set(25, 20);
60    }
61
62    // Give Act I all waypoints.
63    save.waypoints.normal.act1.set_all(true);
64
65    // Edit one quest state.
66    save.quests.normal.act1.q1.state.insert(QuestFlag::Started);
67
68    let act1_catacombs = save.waypoints.normal.act1.get_by_index(8)?;
69    let quest_started = save.quests.normal.act1.q1.state.contains(&QuestFlag::Started);
70
71    // Write back to the same detected format.
72    let output_bytes = save.encode_for(target_format, CompatibilityChecks::Enforce)?;
73    std::fs::create_dir_all(output_dir)?;
74    std::fs::write(&output_path, output_bytes)?;
75
76    println!("Wrote {output_path}");
77    println!("Now: {} / lvl {}", save.character.name, save.character.level());
78    println!(
79        "Stats: str={} dex={} vit={} ene={}",
80        save.attributes.strength.value,
81        save.attributes.dexterity.value,
82        save.attributes.vitality.value,
83        save.attributes.energy.value
84    );
85    println!(
86        "Resources: hp={} mana={} stamina={}",
87        save.attributes.get_max_hp(),
88        save.attributes.get_max_mana(),
89        save.attributes.get_max_stamina()
90    );
91    println!("Act I / Catacombs waypoint: {act1_catacombs}");
92    println!("Quest q1 started: {quest_started}");
93
94    Ok(())
95}