Skip to content

Commit

Permalink
FEAT: Add support for leveled item and creature lists
Browse files Browse the repository at this point in the history
  • Loading branch information
magicaldave committed Oct 23, 2024
1 parent e507cba commit ba264b1
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 15 deletions.
84 changes: 84 additions & 0 deletions resources/Morrowind.fgd
Original file line number Diff line number Diff line change
Expand Up @@ -2193,3 +2193,87 @@
Item8_Id(string) : "Ref Id of contained object"
Item8_Count(integer): "Number of contained instances" : 1
]


@PointClass = world_CreatureList : "Leveled Creature List" [

RefId(string) : "Ref Id for the object" : "agronian guy"
Chance_None(integer) : "Chance nothing will spawn" : 0
Spawn_From_All_Levels(choices) : "Use alpha blending for this brush" : 0 =
[
0 : "False"
1 : "True"
]

Creature_1_Id(string) : "Ref Id of contained creature"
Creature_1_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_2_Id(string) : "Ref Id of contained creature"
Creature_2_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_3_Id(string) : "Ref Id of contained creature"
Creature_3_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_4_Id(string) : "Ref Id of contained creature"
Creature_4_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_5_Id(string) : "Ref Id of contained creature"
Creature_5_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_6_Id(string) : "Ref Id of contained creature"
Creature_6_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_7_Id(string) : "Ref Id of contained creature"
Creature_7_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_8_Id(string) : "Ref Id of contained creature"
Creature_8_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_9_Id(string) : "Ref Id of contained creature"
Creature_9_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Creature_10_Id(string) : "Ref Id of contained creature"
Creature_10_PlayerLevel(integer): "Player level required to spawn this instance" : 1
]


@PointClass = world_ItemList : "Leveled Item List" [

RefId(string) : "Ref Id for the object" : "agronian guy"
Chance_None(integer) : "Chance nothing will spawn" : 0
Spawn_From_All_Levels(choices) : "Use alpha blending for this brush" : 0 =
[
0 : "False"
1 : "True"
]

Item_1_Id(string) : "Ref Id of contained item"
Item_1_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_2_Id(string) : "Ref Id of contained item"
Item_2_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_3_Id(string) : "Ref Id of contained item"
Item_3_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_4_Id(string) : "Ref Id of contained item"
Item_4_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_5_Id(string) : "Ref Id of contained item"
Item_5_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_6_Id(string) : "Ref Id of contained item"
Item_6_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_7_Id(string) : "Ref Id of contained item"
Item_7_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_8_Id(string) : "Ref Id of contained item"
Item_8_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_9_Id(string) : "Ref Id of contained item"
Item_9_PlayerLevel(integer): "Player level required to spawn this instance" : 1

Item_10_Id(string) : "Ref Id of contained item"
Item_10_PlayerLevel(integer): "Player level required to spawn this instance" : 1
]
77 changes: 76 additions & 1 deletion src/game_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use tes3::esp::{
Activator, Alchemy, AlchemyData, AlchemyFlags, Apparatus, ApparatusData, Armor, ArmorData,
AtmosphereData, AttributeId, AttributeId2, BipedObject, Book, BookData, BookType, Cell,
CellFlags, Container, ContainerFlags, Effect, EffectId, EffectId2, EffectRange, Ingredient,
IngredientData, Light, LightData, LightFlags, ObjectFlags, SkillId, SkillId2, TES3Object,
IngredientData, LeveledCreature, LeveledCreatureFlags, LeveledItem, LeveledItemFlags, Light,
LightData, LightFlags, ObjectFlags, SkillId, SkillId2, TES3Object,
};

pub fn activator(
Expand Down Expand Up @@ -200,6 +201,40 @@ pub fn container(
})
}

pub fn creature_list(entity_props: &HashMap<&String, &String>, ref_id: &str) -> TES3Object {
TES3Object::LeveledCreature(LeveledCreature {
flags: ObjectFlags::default(),
id: ref_id.to_owned(),
chance_none: entity_props
.get(&"Chance_None".to_string())
.map_or("0", |v| v)
.parse::<u8>()
.unwrap_or_default(),
leveled_creature_flags: match entity_props.get(&"Spawn_From_All_Levels".to_string()) {
Some(value) if *value == "1" => LeveledCreatureFlags::CALCULATE_FROM_ALL_LEVELS,
Some(_) | None => LeveledCreatureFlags::empty(),
},
creatures: collect_list_creatures(&entity_props),
})
}

pub fn item_list(entity_props: &HashMap<&String, &String>, ref_id: &str) -> TES3Object {
TES3Object::LeveledItem(LeveledItem {
flags: ObjectFlags::default(),
id: ref_id.to_owned(),
chance_none: entity_props
.get(&"Chance_None".to_string())
.map_or("0", |v| v)
.parse::<u8>()
.unwrap_or_default(),
leveled_item_flags: match entity_props.get(&"Spawn_From_All_Levels".to_string()) {
Some(value) if *value == "1" => LeveledItemFlags::CALCULATE_FROM_ALL_LEVELS,
Some(_) | None => LeveledItemFlags::empty(),
},
items: collect_list_items(&entity_props),
})
}

pub fn ingredient(
entity_props: &HashMap<&String, &String>,
ref_id: &str,
Expand Down Expand Up @@ -490,6 +525,46 @@ fn collect_contained_objects(
contained_objects
}

fn collect_list_creatures(prop_map: &HashMap<&String, &String>) -> Vec<(String, u16)> {
let mut contained_objects = Vec::new();

for count in 1..25 {
match prop_map.get(&format!("Creature_{count}_Id")) {
Some(contained_id) => {
let level_required: u16 = prop_map
.get(&format!("Creature_{count}_PlayerLevel"))
.map_or("1", |v| v)
.parse()
.expect("Cannot fail parsing a default-initialized integer");
contained_objects.push((contained_id.to_string(), level_required))
}
None => continue,
}
}

contained_objects
}

fn collect_list_items(prop_map: &HashMap<&String, &String>) -> Vec<(String, u16)> {
let mut contained_objects = Vec::new();

for count in 1..25 {
match prop_map.get(&format!("Item_{count}_Id")) {
Some(contained_id) => {
let level_required: u16 = prop_map
.get(&format!("Item_{count}_PlayerLevel"))
.map_or("1", |v| v)
.parse()
.expect("Cannot fail parsing a default-initialized integer");
contained_objects.push((contained_id.to_string(), level_required))
}
None => continue,
}
}

contained_objects
}

fn get_color(color_str: &String) -> [u8; 4] {
let mut array = [0; 4];
let colors: Vec<&str> = color_str.split_whitespace().collect();
Expand Down
64 changes: 50 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,19 +277,6 @@ fn main() {
let mut ref_id = format!("{map_dir}-PL-{lowest_available_index}");
ref_id = ref_id[..min(ref_id.len(), 32)].to_string();

let translation: SV3 = {
let coords: Vec<f32> = prop_map
.get(&"origin".to_string())
.expect("All point entities must have an origin")
.split_whitespace()
.map(|s| s.parse::<f32>().expect("Invalid coordinate"))
.collect();

assert_eq!(coords.len(), 3, "Origin must have exactly 3 coordinates");

SV3::new(coords[0], coords[1], coords[2]) * (*scale_mode)
};

let radius: u32 = light
.chars()
.skip_while(|c| !c.is_digit(10))
Expand All @@ -311,10 +298,46 @@ fn main() {
&mut used_indices,
&mut cell,
ref_id,
translation,
point_entity_position(scale_mode, &prop_map),
[0.0, 0.0, 0.0],
);
}
"world_CreatureList" => {
let ref_id = match prop_map.get(&"RefId".to_string()) {
Some(ref_id) => ref_id[..min(ref_id.len(), 32)].to_string(),
None => panic!(
"RefIds are mandatory for all point entities, failed on creature list, entity ID: {}",
entity_id
),
};

if !processed_base_objects.contains(&ref_id) {
created_objects.push(game_object::creature_list(&prop_map, ref_id.as_str()));
processed_base_objects.insert(ref_id.to_string());
}

append_cell_reference(
&mut used_indices,
&mut cell,
ref_id,
point_entity_position(scale_mode, &prop_map),
[0.0, 0.0, 0.0],
);
}
"world_ItemList" => {
let ref_id = match prop_map.get(&"RefId".to_string()) {
Some(ref_id) => ref_id[..min(ref_id.len(), 32)].to_string(),
None => panic!(
"RefIds are mandatory for all point entities, failed on item list, entity ID: {}",
entity_id
),
};

if !processed_base_objects.contains(&ref_id) {
created_objects.push(game_object::item_list(&prop_map, ref_id.as_str()));
processed_base_objects.insert(ref_id.to_string());
}
}
class => {
println!("Unidentified point entity class: {class}")
}
Expand Down Expand Up @@ -342,6 +365,19 @@ fn main() {
println!("Wrote {plugin_name} to disk successfully.");
}

fn point_entity_position(scale_mode: &f32, prop_map: &HashMap<&String, &String>) -> SV3 {
let coords: Vec<f32> = prop_map
.get(&"origin".to_string())
.expect("All point entities must have an origin")
.split_whitespace()
.map(|s| s.parse::<f32>().expect("Invalid coordinate"))
.collect();

assert_eq!(coords.len(), 3, "Origin must have exactly 3 coordinates");

SV3::new(coords[0], coords[1], coords[2]) * (*scale_mode)
}

fn lowest_available_index(used_indices: &HashSet<u32>) -> u32 {
(1..).find(|&n| !used_indices.contains(&n)).unwrap_or(1)
}
Expand Down

0 comments on commit ba264b1

Please sign in to comment.