...
 
Commits (2)
......@@ -84,7 +84,7 @@ dependencies = [
[[package]]
name = "powers"
version = "2.2.0"
version = "2.2.1"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)",
......
[package]
name = "powers"
version = "2.2.0"
version = "2.2.1"
authors = ["Ruby <[email protected]>"]
edition = "2018"
......
......@@ -11,6 +11,7 @@ use std::rc::Rc;
/// * `reader` - An open `Read` + `Seek`
/// * `strings` - The `StringPool` for archetypes
/// * `messages` - The global `MessageStore` containing client messages
/// * `is_villain` - Are we loading villain defs?
///
/// # Returns:
///
......@@ -20,6 +21,7 @@ pub fn serialized_read_archetypes<T>(
reader: &mut T,
strings: &StringPool,
messages: &MessageStore,
is_villain: bool,
) -> ParseResult<Keyed<Archetype>>
where
T: Read + Seek,
......@@ -30,7 +32,8 @@ where
let mut archetypes = Keyed::<_>::new();
let at_size: usize = bin_read(reader)?;
for _ in 0..at_size {
let archetype = read_archetype(reader, strings, messages)?;
let mut archetype = read_archetype(reader, strings, messages)?;
archetype.is_villain = is_villain;
if let Some(class_key) = &archetype.class_key {
archetypes.insert(class_key.clone(), Rc::new(archetype));
}
......
......@@ -541,7 +541,7 @@ fn read_classes_bin(
.map_err(|e| ecxt!("Unable to open classes!", e))?;
let strings = bin_parse::serialized_read_string_pool(&mut reader)
.map_err(|e| ecxt!("Unable to parse string pool!", e))?;
let archetypes = bin_parse::serialized_read_archetypes(&mut reader, &strings, messages)
let archetypes = bin_parse::serialized_read_archetypes(&mut reader, &strings, messages, false)
.map_err(|e| ecxt!("Unable to parse classes table.", e))?;
println!("Read {} archetypes.", archetypes.len());
Ok(archetypes)
......@@ -635,7 +635,7 @@ fn read_villain_classes_bin(
.map_err(|e| ecxt!("Unable to open classes!", e))?;
let strings = bin_parse::serialized_read_string_pool(&mut reader)
.map_err(|e| ecxt!("Unable to parse string pool!", e))?;
let archetypes = bin_parse::serialized_read_archetypes(&mut reader, &strings, messages)
let archetypes = bin_parse::serialized_read_archetypes(&mut reader, &strings, messages, true)
.map_err(|e| ecxt!("Unable to parse classes table.", e))?;
println!("Read {} villain archetypes.", archetypes.len());
Ok(archetypes)
......
......@@ -630,10 +630,9 @@ fn filter_archetypes_eg(
archetypes: &Vec<Rc<Archetype>>,
) -> Vec<Rc<Archetype>> {
// filter out the MLCrit and BossCrit effects, they use arch to test for NPC archetypes
if !effect
.ppch_tags
.iter()
.any(|tag| matches!(&tag[..], "MLCrit" | "BossCrit"))
if !is_critical_by_tags(&effect.ppch_tags)
// pets sometimes check for the "owner" which can confuse this rule
&& !archetypes.iter().any(|at| at.is_villain)
&& effect.ppch_requires.iter().any(|rule| rule == "arch")
{
// second form of this rule compares to the latter half of the class key
......@@ -641,9 +640,15 @@ fn filter_archetypes_eg(
.iter()
.filter(|at| {
if let Some(class_key) = &at.class_key {
effect.ppch_requires.iter().any(|rule| {
rule.to_ascii_lowercase() == &class_key.get()[Archetype::CLASS_PREFIX_LEN..]
})
if let Some(class_name) = &at.pch_name {
effect.ppch_requires.iter().any(|rule| {
rule.to_ascii_lowercase()
== &class_key.get()[Archetype::CLASS_PREFIX_LEN..]
|| rule.to_ascii_lowercase() == *class_name
})
} else {
false
}
} else {
false
}
......@@ -672,10 +677,7 @@ fn get_pve_or_pvp(
// check for the MLCrit and BossCrit effects, they use player to test for non-pvp in most cases
// but may have an explicit expression
if tags
.iter()
.any(|tag| matches!(&tag[..], "MLCrit" | "BossCrit"))
{
if is_critical_by_tags(tags) {
match requires_str[..] {
["enttype", "target>", "critter", "eq"] => return Some(PVE_TAG),
["enttype", "target>", "player", "eq"] => return Some(PVP_TAG),
......@@ -725,11 +727,13 @@ fn check_special_requires(effect_group: &mut EffectGroupOutput, requires: &Vec<S
/// Modifies `effect_group` based on the content of `tags`.
fn check_tags_group(effect_group: &mut EffectGroupOutput, tags: &Vec<String>) {
let tags_str = tags.iter().map(|s| &**s).collect::<Vec<_>>();
for tag in tags_str {
if is_critical_by_tags(&tags) {
effect_group.tags.insert("Critical");
}
for tag in tags {
// several tags modify the chance of an effect, these refer to "global chance mods"
// that are handled in code
match tag {
match &tag[..] {
"FieryEmbrace" => {
effect_group.tags.insert("FieryEmbrace");
effect_group.chance_percent = 100.0;
......@@ -752,10 +756,7 @@ fn check_tags_group(effect_group: &mut EffectGroupOutput, tags: &Vec<String>) {
_ => (),
}
// gather certain tags together and promote them to effect tags
match tag {
"MLCrit" | "BossCrit" | "PlayerCrit" => {
effect_group.tags.insert("Critical");
}
match &tag[..] {
"Lethal"
| "LethalKB10"
| "LethalKB25"
......@@ -781,15 +782,26 @@ fn check_tags_group(effect_group: &mut EffectGroupOutput, tags: &Vec<String>) {
/// Modifies `effect` based on the content of `tags`.
fn check_tags_effect(effect: &mut AttribModOutput, tags: &Vec<String>) {
let tags_str = tags.iter().map(|s| &**s).collect::<Vec<_>>();
for tag in tags_str {
match tag {
for tag in tags {
match &tag[..] {
"FireDamageDoT" => effect.tick_chance_percent = Some(80.0),
_ => (),
}
}
}
/// Searches `tags` for any of the known critical hit tags.
fn is_critical_by_tags(tags: &Vec<String>) -> bool {
for tag in tags {
match &tag[..] {
"MLCrit" | "BossCrit" | "PlayerCrit" | "ECCritModPlayer" | "ECCritModSmall"
| "ECCritModLarge" => return true,
_ => (),
}
}
false
}
/// Converts the offset of the character attributes to a type
/// which indicates what we're modifying.
/// See Common/entity/character_attribs.h CharacterAttribSet
......
......@@ -134,6 +134,9 @@ pub struct Archetype {
/// Used for lookup table purposes.
#[serde(skip)]
pub class_key: Option<NameKey>,
/// Marks if this was loaded from the villain def rather than player.
#[serde(skip)]
pub is_villain: bool,
}
impl Archetype {
......