use crate::error::{PraedaError, Result};
use crate::models::*;
use rand::Rng;
use std::collections::HashMap;
use std::fs;
pub struct PraedaGenerator {
quality_data: HashMap<String, i32>,
item_types: Vec<ItemType>,
item_list: HashMap<(String, String), Vec<String>>,
item_attributes: HashMap<(String, String), Vec<ItemAttribute>>,
item_affixes: HashMap<(String, String), (Vec<Affix>, Vec<Affix>)>,
subtype_metadata: HashMap<(String, String), HashMap<String, serde_json::Value>>,
item_name_metadata: HashMap<(String, String, String), HashMap<String, serde_json::Value>>,
loot_list: HashMap<String, Vec<Item>>,
}
impl PraedaGenerator {
pub fn new() -> Self {
PraedaGenerator {
quality_data: HashMap::new(),
item_types: Vec::new(),
item_list: HashMap::new(),
item_attributes: HashMap::new(),
item_affixes: HashMap::new(),
subtype_metadata: HashMap::new(),
item_name_metadata: HashMap::new(),
loot_list: HashMap::new(),
}
}
pub fn set_quality_data(&mut self, quality: &str, weight: i32) {
self.quality_data.insert(quality.to_string(), weight);
}
pub fn get_quality_data(&self) -> &HashMap<String, i32> {
&self.quality_data
}
pub fn has_quality(&self, quality: &str) -> bool {
if quality.is_empty() {
return true;
}
self.quality_data.contains_key(quality)
}
pub fn set_item_type(&mut self, type_name: &str, weight: i32) {
if let Some(item_type) = self.item_types.iter_mut().find(|it| it.item_type == type_name) {
item_type.set_weight(weight);
} else {
self.item_types
.push(ItemType::new(type_name, HashMap::new(), weight));
}
}
pub fn get_item_type(&self, type_name: &str) -> Option<&ItemType> {
self.item_types.iter().find(|it| it.item_type == type_name)
}
pub fn get_item_types(&self) -> &[ItemType] {
&self.item_types
}
pub fn has_item_type(&self, type_name: &str) -> bool {
if type_name.is_empty() {
return true;
}
self.item_types.iter().any(|it| it.item_type == type_name)
}
pub fn set_item_subtype(&mut self, type_name: &str, subtype: &str, weight: i32) {
if let Some(item_type) = self.item_types.iter_mut().find(|it| it.item_type == type_name) {
item_type.add_subtype(subtype, weight);
} else {
let mut subtypes = HashMap::new();
subtypes.insert(subtype.to_string(), weight);
self.item_types
.push(ItemType::new(type_name, subtypes, 0));
}
}
pub fn has_item_subtype(&self, type_name: &str, subtype: &str) -> bool {
if type_name.is_empty() || subtype.is_empty() {
return true;
}
if let Some(item_type) = self.get_item_type(type_name) {
item_type.has_subtype(subtype)
} else {
#[cfg(not(tarpaulin_include))]
{
false
}
#[cfg(tarpaulin_include)]
{
false
}
}
}
pub fn get_subtypes_for_type(&self, item_type: &str) -> Vec<String> {
if let Some(type_obj) = self.get_item_type(item_type) {
type_obj.get_subtypes().keys().cloned().collect()
} else {
Vec::new()
}
}
pub fn get_weapon_subtypes(&self) -> Vec<String> {
self.get_subtypes_for_type("Weapon")
}
pub fn get_item_names(&self, item_type: &str, subtype: &str) -> Vec<String> {
let key = (item_type.to_string(), subtype.to_string());
self.item_list.get(&key).cloned().unwrap_or_default()
}
pub fn get_item_type_names(&self) -> Vec<String> {
self.item_types.iter().map(|it| it.item_type.clone()).collect()
}
pub fn set_subtype_metadata(
&mut self,
item_type: &str,
subtype: &str,
key: &str,
value: serde_json::Value,
) {
let type_key = (item_type.to_string(), subtype.to_string());
self.subtype_metadata
.entry(type_key)
.or_default()
.insert(key.to_string(), value);
}
pub fn get_subtype_metadata(
&self,
item_type: &str,
subtype: &str,
key: &str,
) -> Option<&serde_json::Value> {
let type_key = (item_type.to_string(), subtype.to_string());
self.subtype_metadata.get(&type_key).and_then(|m| m.get(key))
}
pub fn get_all_subtype_metadata(
&self,
item_type: &str,
subtype: &str,
) -> Option<&HashMap<String, serde_json::Value>> {
let type_key = (item_type.to_string(), subtype.to_string());
self.subtype_metadata.get(&type_key)
}
pub fn set_item_name_metadata(
&mut self,
item_type: &str,
subtype: &str,
item_name: &str,
key: &str,
value: serde_json::Value,
) {
let type_key = (item_type.to_string(), subtype.to_string(), item_name.to_string());
self.item_name_metadata
.entry(type_key)
.or_default()
.insert(key.to_string(), value);
}
pub fn get_item_name_metadata(
&self,
item_type: &str,
subtype: &str,
item_name: &str,
key: &str,
) -> Option<&serde_json::Value> {
let type_key = (item_type.to_string(), subtype.to_string(), item_name.to_string());
self.item_name_metadata
.get(&type_key)
.and_then(|m| m.get(key))
}
pub fn get_all_item_name_metadata(
&self,
item_type: &str,
subtype: &str,
item_name: &str,
) -> Option<&HashMap<String, serde_json::Value>> {
let type_key = (item_type.to_string(), subtype.to_string(), item_name.to_string());
self.item_name_metadata.get(&type_key)
}
pub fn set_attribute(
&mut self,
type_name: &str,
subtype: &str,
attribute: ItemAttribute,
) {
let key = (type_name.to_string(), subtype.to_string());
let attributes = self.item_attributes.entry(key).or_default();
if let Some(pos) = attributes.iter().position(|a| a.name == attribute.name) {
attributes[pos].initial_value += attribute.initial_value;
} else {
attributes.push(attribute);
}
}
pub fn has_attribute(&self, type_name: &str, subtype: &str, attr_name: &str) -> bool {
if !self.has_item_type(type_name) || !self.has_item_subtype(type_name, subtype) {
return false;
}
let key = (type_name.to_string(), subtype.to_string());
if let Some(attributes) = self.item_attributes.get(&key) {
attributes.iter().any(|a| a.name == attr_name)
} else {
false
}
}
pub fn set_item(&mut self, type_name: &str, subtype: &str, names: Vec<&str>) {
let names_owned = names.iter().map(|n| n.to_string()).collect();
self.item_list.insert((type_name.to_string(), subtype.to_string()), names_owned);
}
pub fn set_affix_attribute(
&mut self,
type_name: &str,
subtype: &str,
is_prefix: bool,
affix_name: &str,
attribute: ItemAttribute,
) {
let key = (type_name.to_string(), subtype.to_string());
let affix_data = self
.item_affixes
.entry(key)
.or_insert((Vec::new(), Vec::new()));
let affixes = if is_prefix {
&mut affix_data.0
} else {
&mut affix_data.1
};
if let Some(pos) = affixes.iter().position(|a| a.name == affix_name) {
affixes[pos].set_attribute(attribute);
} else {
let new_affix = Affix::new(affix_name, vec![attribute]);
affixes.push(new_affix);
}
}
pub fn set_prefix_attribute(
&mut self,
type_name: &str,
subtype: &str,
affix_name: &str,
attribute: ItemAttribute,
) {
self.set_affix_attribute(type_name, subtype, true, affix_name, attribute);
}
pub fn set_suffix_attribute(
&mut self,
type_name: &str,
subtype: &str,
affix_name: &str,
attribute: ItemAttribute,
) {
self.set_affix_attribute(type_name, subtype, false, affix_name, attribute);
}
pub fn get_prefixes(&self, type_name: &str, subtype: &str) -> Vec<Affix> {
if let Some((prefixes, _)) =
self.item_affixes.get(&(type_name.to_string(), subtype.to_string()))
{
prefixes.clone()
} else {
Vec::new()
}
}
pub fn get_suffixes(&self, type_name: &str, subtype: &str) -> Vec<Affix> {
if let Some((_, suffixes)) =
self.item_affixes.get(&(type_name.to_string(), subtype.to_string()))
{
suffixes.clone()
} else {
Vec::new()
}
}
pub fn load_data(&mut self, toml_data: &str) -> Result<()> {
let config: crate::models::TomlConfig = toml::from_str(toml_data)?;
self.quality_data = config.quality_data;
self.item_types = config.item_types;
for item_attrs in config.item_attributes {
let key = (item_attrs.item_type, item_attrs.subtype);
self.item_attributes.insert(key, item_attrs.attributes);
}
for item in config.item_list {
let key = (item.item_type.clone(), item.subtype.clone());
self.item_list.insert(key.clone(), item.names.clone());
for (item_name, metadata) in item.item_metadata {
for (meta_key, meta_value) in metadata {
self.set_item_name_metadata(
&key.0,
&key.1,
&item_name,
&meta_key,
meta_value,
);
}
}
}
for affixes in config.item_affixes {
let key = (affixes.item_type.clone(), affixes.subtype.clone());
self.item_affixes.insert(key.clone(), (affixes.prefixes, affixes.suffixes));
if !affixes.metadata.is_empty() {
self.subtype_metadata.insert(key, affixes.metadata);
}
}
Ok(())
}
#[cfg(not(tarpaulin_include))]
pub fn load_data_from_file(&mut self, path: &str) -> Result<()> {
let toml_str = fs::read_to_string(path)?;
self.load_data(&toml_str)
}
pub fn generate_loot(
&mut self,
options: &GeneratorOptions,
overrides: &GeneratorOverrides,
key: &str,
) -> Result<Vec<Item>> {
let mut items = Vec::new();
for _ in 0..options.number_of_items {
let item = self.generate_item(options, overrides)?;
items.push(item);
}
self.loot_list.insert(key.to_string(), items.clone());
Ok(items)
}
pub fn generate_loot_json(
&mut self,
options: &GeneratorOptions,
overrides: &GeneratorOverrides,
key: &str,
) -> Result<String> {
let items = self.generate_loot(options, overrides, key)?;
Ok(serde_json::to_string(&items)?)
}
pub fn get_loot(&self, key: &str) -> Vec<Item> {
self.loot_list
.get(key)
.cloned()
.unwrap_or_default()
}
pub fn get_loot_json(&self, key: &str) -> Result<String> {
let loot = self.get_loot(key);
Ok(serde_json::to_string(&loot)?)
}
fn generate_item(
&self,
options: &GeneratorOptions,
overrides: &GeneratorOverrides,
) -> Result<Item> {
let mut rng = rand::rng();
let item_quality = if !overrides.quality_override.is_empty() {
overrides.quality_override.clone()
} else {
self.weighted_random_select(&self.quality_data, &mut rng)?
};
let item_type = if !overrides.type_override.is_empty() {
overrides.type_override.clone()
} else {
let weights: HashMap<String, i32> = self
.item_types
.iter()
.map(|it| (it.item_type.clone(), it.weight))
.collect();
self.weighted_random_select(&weights, &mut rng)?
};
let subtype = if !overrides.subtype_override.is_empty() {
overrides.subtype_override.clone()
} else {
if let Some(item_type_obj) = self.get_item_type(&item_type) {
self.weighted_random_select(item_type_obj.get_subtypes(), &mut rng)?
} else {
String::new()
}
};
let item_name = if let Some(names) = self.item_list.get(&(item_type.clone(), subtype.clone())) {
if names.is_empty() {
subtype.clone()
} else {
names[rng.random_range(0..names.len())].clone()
}
} else {
subtype.clone()
};
let will_have_prefix = rng.random::<f64>() < options.affix_chance;
let will_have_suffix = rng.random::<f64>() < options.affix_chance;
let mut prefix = Affix::empty();
let mut suffix = Affix::empty();
if will_have_prefix || will_have_suffix {
let valid_keys = vec![
("".to_string(), "".to_string()),
(item_type.clone(), "".to_string()),
("".to_string(), subtype.clone()),
(item_type.clone(), subtype.clone()),
];
let mut valid_prefixes = Vec::new();
let mut valid_suffixes = Vec::new();
for key in valid_keys {
if let Some((prefixes, suffixes)) = self.item_affixes.get(&key) {
if will_have_prefix {
valid_prefixes.extend(prefixes.clone());
}
if will_have_suffix {
valid_suffixes.extend(suffixes.clone());
}
}
}
if will_have_prefix && !valid_prefixes.is_empty() {
prefix = valid_prefixes[rng.random_range(0..valid_prefixes.len())].clone();
}
if will_have_suffix && !valid_suffixes.is_empty() {
suffix = valid_suffixes[rng.random_range(0..valid_suffixes.len())].clone();
}
}
let mut item = Item::new(
&item_name,
&item_quality,
&item_type,
&subtype,
prefix,
suffix,
HashMap::new(),
);
self.calculate_attributes(&mut item, options, &mut rng)?;
if let Some(metadata) = self.get_all_subtype_metadata(&item_type, &subtype) {
for (key, value) in metadata {
item.set_metadata(key, value.clone());
}
}
if let Some(metadata) = self.get_all_item_name_metadata(&item_type, &subtype, item.get_name()) {
for (key, value) in metadata {
item.set_metadata(key, value.clone());
}
}
Ok(item)
}
fn calculate_attributes(
&self,
item: &mut Item,
options: &GeneratorOptions,
rng: &mut rand::rngs::ThreadRng,
) -> Result<()> {
let level_range = options.level_variance;
let generated_level = rng.random_range(
(options.base_level - level_range) as i32..=(options.base_level + level_range) as i32,
) as f64;
item.set_attribute(
"level",
ItemAttribute::new(
"level",
generated_level,
0.0,
0.0,
false,
),
);
let attribute_keys = vec![
("".to_string(), "".to_string()),
(item.get_type().to_string(), "".to_string()),
("".to_string(), item.get_subtype().to_string()),
(item.get_type().to_string(), item.get_subtype().to_string()),
];
let mut optional_attributes = Vec::new();
for key in &attribute_keys {
if let Some(attributes) = self.item_attributes.get(key) {
for attr in attributes {
if attr.get_required() {
let mut new_attr = attr.clone();
if attr.get_name().contains("_requirement") {
new_attr.set_initial_value(generated_level);
} else {
new_attr.generate_value(
generated_level,
options.linear,
options.scaling_factor,
);
}
item.set_attribute(&attr.name, new_attr);
} else {
optional_attributes.push(attr.clone());
}
}
}
}
#[cfg(not(tarpaulin_include))]
{
for attr in optional_attributes {
if rng.random::<f64>() <= options.affix_chance {
let mut final_attr = if let Some(existing) = item.get_attribute(&attr.name) {
let mut new_attr = existing.clone();
new_attr.initial_value += attr.initial_value;
new_attr
} else {
let mut new_attr = attr.clone();
if !new_attr.get_name().contains("_requirement") {
new_attr.generate_value(
generated_level,
options.linear,
options.scaling_factor,
);
}
new_attr
};
if final_attr.get_name().contains("_requirement") {
final_attr.set_initial_value(generated_level);
}
item.set_attribute(&attr.name, final_attr);
}
}
}
#[cfg(not(tarpaulin_include))]
{
let prefix_attributes = item.get_prefix().get_attributes().to_vec();
for prefix_attr in prefix_attributes {
let mut final_attr = if let Some(existing) = item.get_attribute(&prefix_attr.name) {
let mut new_attr = existing.clone();
new_attr.initial_value += prefix_attr.initial_value;
new_attr
} else {
prefix_attr.clone()
};
if final_attr.get_name().contains("_requirement") {
final_attr.set_initial_value(generated_level);
}
item.set_attribute(&prefix_attr.name, final_attr);
}
let suffix_attributes = item.get_suffix().get_attributes().to_vec();
for suffix_attr in suffix_attributes {
let mut final_attr = if let Some(existing) = item.get_attribute(&suffix_attr.name) {
let mut new_attr = existing.clone();
new_attr.initial_value += suffix_attr.initial_value;
new_attr
} else {
suffix_attr.clone()
};
if final_attr.get_name().contains("_requirement") {
final_attr.set_initial_value(generated_level);
}
item.set_attribute(&suffix_attr.name, final_attr);
}
}
Ok(())
}
fn weighted_random_select(
&self,
weights: &HashMap<String, i32>,
rng: &mut rand::rngs::ThreadRng,
) -> Result<String> {
if weights.is_empty() {
return Err(PraedaError::InvalidData("No items to select from".to_string()));
}
let total_weight: i32 = weights.values().sum();
let mut roll = rng.random_range(0..total_weight);
let mut sorted_keys: Vec<_> = weights.keys().collect();
sorted_keys.sort();
for key in sorted_keys {
roll -= weights[key];
if roll < 0 {
return Ok(key.clone());
}
}
Err(PraedaError::InvalidData("Failed to select from weights".to_string()))
}
}
impl Default for PraedaGenerator {
fn default() -> Self {
Self::new()
}
}