From 02ae28de7e5ed6fb11c95d619d321a9feef76f3e Mon Sep 17 00:00:00 2001 From: soir20 <71418127+soir20@users.noreply.github.com> Date: Thu, 14 May 2026 01:35:18 -0400 Subject: [PATCH 1/2] Assign a members single cost expression per item --- config/item_groups.yaml | 203 --------------------- config/sales.yaml | 16 -- src/game_server/handlers/item.rs | 25 ++- src/game_server/handlers/reference_data.rs | 58 +++++- src/game_server/handlers/store.rs | 117 +++--------- src/game_server/mod.rs | 6 +- src/game_server/packets/reference_data.rs | 3 +- 7 files changed, 105 insertions(+), 323 deletions(-) delete mode 100644 config/sales.yaml diff --git a/config/item_groups.yaml b/config/item_groups.yaml index 80fb47a6..8025b94d 100644 --- a/config/item_groups.yaml +++ b/config/item_groups.yaml @@ -1,21 +1,6 @@ --- # Primary Hilts - guid: 3 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 - page: 0 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 63 unknown: 0 @@ -145,21 +130,6 @@ unknown: 0 # Secondary Hilts - guid: 8 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 - page: 0 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 300 unknown: 0 @@ -267,21 +237,6 @@ unknown: 0 # Primary Colors - guid: 4 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 - page: 0 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 100000 unknown: 0 @@ -311,21 +266,6 @@ unknown: 0 # Secondary Colors - guid: 149 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 - page: 0 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 130000 unknown: 0 @@ -355,21 +295,6 @@ unknown: 0 # Primary Shapes - guid: 5 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 - page: 0 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 90000 unknown: 0 @@ -413,21 +338,6 @@ unknown: 0 # Secondary Shapes - guid: 9 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 - page: 0 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 120000 unknown: 0 @@ -471,21 +381,7 @@ unknown: 0 # Hair Style - guid: 122 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 page: 8 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 40118 unknown: 0 @@ -727,21 +623,7 @@ unknown: 0 # Hair Color - guid: 123 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 page: 8 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 30021 unknown: 0 @@ -789,21 +671,7 @@ unknown: 0 # Eye Color - guid: 124 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 page: 8 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 10012 unknown: 0 @@ -833,21 +701,7 @@ unknown: 0 # Skin Tone - guid: 125 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 page: 8 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 60029 unknown: 0 @@ -911,21 +765,7 @@ unknown: 0 # Face Design - guid: 126 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 page: 8 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 50062 unknown: 0 @@ -1055,21 +895,7 @@ unknown: 0 # Facial Hair - guid: 139 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 page: 8 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 20014 unknown: 0 @@ -1103,21 +929,7 @@ unknown: 0 # Species - guid: 127 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 page: 9 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 70012 unknown: 0 @@ -1147,21 +959,6 @@ unknown: 0 # Boosts - guid: 10000 - unknown2: 0 - name_id: 0 - description_id: 0 - sort_order: 0 - icon_set_id: 0 - category: 0 - page: 0 - preview_model_id: 0 - preview_animation_id: 0 - is_new: false - unknown12: 0 - unknown13: 0 - unknown14: 0 - unknown16: '' - members_only: false items: - guid: 2240 unknown: 0 diff --git a/config/sales.yaml b/config/sales.yaml deleted file mode 100644 index 0c4b0d16..00000000 --- a/config/sales.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- item_group_guid: 122 - members_cost_expression: 'max(0.9*x - 1, 0.0)' -- item_group_guid: 123 - members_cost_expression: 'max(0.9*x - 1, 0.0)' -- item_group_guid: 124 - members_cost_expression: 'max(0.9*x - 1, 0.0)' -- item_group_guid: 125 - members_cost_expression: 'max(0.9*x - 1, 0.0)' -- item_group_guid: 126 - members_cost_expression: 'max(0.9*x - 1, 0.0)' -- item_group_guid: 127 - members_cost_expression: 'max(0.9*x - 1, 0.0)' -- item_group_guid: 139 - members_cost_expression: 'max(0.9*x - 1, 0.0)' -- item_group_guid: 10000 diff --git a/src/game_server/handlers/item.rs b/src/game_server/handlers/item.rs index ece6724f..eef67cb9 100644 --- a/src/game_server/handlers/item.rs +++ b/src/game_server/handlers/item.rs @@ -1,9 +1,12 @@ use std::{collections::BTreeMap, fs::File, path::Path}; use crate::{ - game_server::packets::{ - item::{ItemAbility, ItemDefinition, ItemType}, - player_update::CustomizationSlot, + game_server::{ + handlers::store::{compute_costs, ItemCostMap}, + packets::{ + item::{ItemAbility, ItemDefinition, ItemType}, + player_update::CustomizationSlot, + }, }, ConfigError, }; @@ -13,6 +16,12 @@ use walkdir::WalkDir; pub const SABER_ITEM_TYPE: u32 = 25; +const DEFAULT_COST_EXPRESSION: &str = "max(0.9*x - 1, 0.0)"; + +fn default_members_cost_expression() -> String { + DEFAULT_COST_EXPRESSION.to_string() +} + const fn default_item_class() -> i32 { -1 } @@ -41,6 +50,8 @@ pub struct ItemConfig { pub tint: u32, #[serde(default)] pub cost: u32, + #[serde(default = "default_members_cost_expression")] + pub members_cost_expression: String, #[serde(default = "default_item_class")] pub item_class: i32, #[serde(default)] @@ -152,9 +163,11 @@ impl From for ItemDefinition { } } +pub type ItemDefinitionMap = BTreeMap; + pub fn load_item_definitions( config_dir: &Path, -) -> Result, ConfigError> { +) -> Result<(ItemDefinitionMap, ItemCostMap), ConfigError> { let items_dir = config_dir.join("items"); let yaml_files = WalkDir::new(&items_dir) @@ -167,10 +180,12 @@ pub fn load_item_definitions( .map(|entry| entry.into_path()); let mut items = BTreeMap::new(); + let mut costs = BTreeMap::new(); for file_path in yaml_files { let file = File::open(&file_path)?; let configs: Vec = serde_yaml::from_reader(file)?; + costs.extend(compute_costs(&configs)?); for cfg in configs { let def: ItemDefinition = cfg.into(); @@ -184,5 +199,5 @@ pub fn load_item_definitions( } } - Ok(items) + Ok((items, costs)) } diff --git a/src/game_server/handlers/reference_data.rs b/src/game_server/handlers/reference_data.rs index d940b874..c91e3afb 100644 --- a/src/game_server/handlers/reference_data.rs +++ b/src/game_server/handlers/reference_data.rs @@ -1,8 +1,11 @@ use std::{fs::File, path::Path}; +use serde::Deserialize; + use crate::{ game_server::packets::reference_data::{ CategoryDefinitions, ItemClassDefinition, ItemClassDefinitions, ItemGroupDefinition, + ItemGroupItem, }, ConfigError, }; @@ -23,7 +26,60 @@ pub fn load_categories(config_dir: &Path) -> Result, +} + +impl From for ItemGroupDefinition { + fn from(value: ItemGroupConfig) -> Self { + ItemGroupDefinition { + guid: value.guid, + unknown2: 0, + name_id: value.name_id, + description_id: value.description_id, + sort_order: value.sort_order, + icon_set_id: value.icon_set_id, + category: value.category, + page: value.page, + preview_model_id: value.preview_model_id, + preview_animation_id: value.preview_animation_id, + is_new: value.is_new, + unknown12: 0, + unknown13: 0, + unknown14: 0, + unknown16: "".to_string(), + members_only: value.members_only, + items: value.items, + } + } +} + pub fn load_item_groups(config_dir: &Path) -> Result, ConfigError> { let mut file = File::open(config_dir.join("item_groups.yaml"))?; - Ok(serde_yaml::from_reader(&mut file)?) + let groups: Vec = serde_yaml::from_reader(&mut file)?; + Ok(groups.into_iter().map(|group| group.into()).collect()) } diff --git a/src/game_server/handlers/store.rs b/src/game_server/handlers/store.rs index 3699e006..f75c104f 100644 --- a/src/game_server/handlers/store.rs +++ b/src/game_server/handlers/store.rs @@ -1,51 +1,43 @@ use std::{ collections::BTreeMap, - fs::File, io::{Error, ErrorKind}, - path::Path, }; use evalexpr::{context_map, eval_with_context, Value}; -use serde::Deserialize; use crate::{ - game_server::packets::{ - item::ItemDefinition, - reference_data::ItemGroupDefinition, - store::{StoreItem, StoreItemList}, + game_server::{ + handlers::item::ItemConfig, + packets::store::{StoreItem, StoreItemList}, }, - info, ConfigError, + ConfigError, }; -const DEFAULT_COST_EXPRESSION: &str = "x"; - -fn default_cost_expression() -> String { - DEFAULT_COST_EXPRESSION.to_string() -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct Sale { - item_group_guid: i32, - #[serde(default = "default_cost_expression")] - base_cost_expression: String, - #[serde(default = "default_cost_expression")] - members_cost_expression: String, -} - pub struct CostEntry { pub base: u32, pub members: u32, } -pub fn load_cost_map( - config_dir: &Path, - items: &BTreeMap, - item_groups: &[ItemGroupDefinition], -) -> Result, ConfigError> { - let mut file = File::open(config_dir.join("sales.yaml"))?; - let sales: Vec = serde_yaml::from_reader(&mut file)?; - cost_map_from_sales(items, item_groups, sales) +pub type ItemCostMap = BTreeMap; + +pub fn compute_costs(items: &[ItemConfig]) -> Result, ConfigError> { + let mut costs = BTreeMap::new(); + + for item_config in items.iter() { + let cost_entry = costs.entry(item_config.guid).or_insert_with(|| CostEntry { + base: item_config.cost, + members: item_config.cost, + }); + + cost_entry.base = item_config.cost; + cost_entry.members = evaluate_cost_expression( + &item_config.members_cost_expression, + cost_entry.members, + item_config.guid, + )?; + } + + Ok(costs) } impl From<&BTreeMap> for StoreItemList { @@ -74,67 +66,6 @@ impl From<&BTreeMap> for StoreItemList { } } -fn cost_map_from_sales( - items: &BTreeMap, - item_groups: &[ItemGroupDefinition], - sales: Vec, -) -> Result, ConfigError> { - let items_by_group = items_by_group(item_groups); - let mut costs = BTreeMap::new(); - - for sale in sales { - let Some(items_in_group) = items_by_group.get(&sale.item_group_guid) else { - info!( - "Skipping sale for unknown item group {}", - sale.item_group_guid - ); - continue; - }; - - for item_guid in items_in_group { - let cost_entry = costs.entry(*item_guid).or_insert_with(|| { - if let Some(item_definition) = items.get(item_guid) { - CostEntry { - base: item_definition.cost, - members: item_definition.cost, - } - } else { - info!("Defaulting to 0 cost for unknown item {}", item_guid); - CostEntry { - base: 0, - members: 0, - } - } - }); - - cost_entry.base = - evaluate_cost_expression(&sale.base_cost_expression, cost_entry.base, *item_guid)?; - cost_entry.members = evaluate_cost_expression( - &sale.members_cost_expression, - cost_entry.members, - *item_guid, - )?; - } - } - - Ok(costs) -} - -fn items_by_group(item_groups: &[ItemGroupDefinition]) -> BTreeMap> { - let mut items_by_group = BTreeMap::new(); - - for definition in item_groups { - for item in &definition.items { - items_by_group - .entry(definition.guid) - .or_insert_with(Vec::new) - .push(item.guid); - } - } - - items_by_group -} - fn evaluate_cost_expression( cost_expression: &str, cost: u32, diff --git a/src/game_server/mod.rs b/src/game_server/mod.rs index 9bc582f0..20079f9b 100644 --- a/src/game_server/mod.rs +++ b/src/game_server/mod.rs @@ -35,7 +35,7 @@ use handlers::minigame::{ }; use handlers::mount::{load_mounts, process_mount_packet, MountConfig}; use handlers::reference_data::{load_categories, load_item_classes, load_item_groups}; -use handlers::store::{load_cost_map, CostEntry}; +use handlers::store::CostEntry; use handlers::test_data::make_test_nameplate_image; use handlers::tick::{ enqueue_tickable_chunks, enqueue_tickable_minigames, tick_matchmaking_groups, tick_minigame, @@ -207,11 +207,11 @@ impl GameServer { pub fn new(config_dir: &Path) -> Result { let characters = GuidTable::new(); let (templates, zones, points_of_interest) = load_zones(config_dir)?; - let item_definitions = load_item_definitions(config_dir)?; + let (item_definitions, costs) = load_item_definitions(config_dir)?; let item_groups = load_item_groups(config_dir)?; Ok(GameServer { categories: load_categories(config_dir)?, - costs: load_cost_map(config_dir, &item_definitions, &item_groups)?, + costs, customizations: load_customizations(config_dir)?, customization_item_mappings: load_customization_item_mappings(config_dir)?, default_sabers: load_default_sabers(config_dir)?, diff --git a/src/game_server/packets/reference_data.rs b/src/game_server/packets/reference_data.rs index 7864983e..f4ee1773 100644 --- a/src/game_server/packets/reference_data.rs +++ b/src/game_server/packets/reference_data.rs @@ -117,8 +117,7 @@ impl SerializePacket for ItemGroupItem { } } -#[derive(Deserialize, SerializePacket)] -#[serde(deny_unknown_fields)] +#[derive(SerializePacket)] pub struct ItemGroupDefinition { pub guid: i32, pub unknown2: i32, From 5e463dcaa1b95709861d720f109042e31aeba5cf Mon Sep 17 00:00:00 2001 From: soir20 <71418127+soir20@users.noreply.github.com> Date: Thu, 14 May 2026 02:34:03 -0400 Subject: [PATCH 2/2] Only put certain item groups for sale --- config/item_groups.yaml | 8 +++++++ src/game_server/handlers/reference_data.rs | 26 +++++++++++++++++----- src/game_server/mod.rs | 4 ++-- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/config/item_groups.yaml b/config/item_groups.yaml index 8025b94d..f7549771 100644 --- a/config/item_groups.yaml +++ b/config/item_groups.yaml @@ -382,6 +382,7 @@ # Hair Style - guid: 122 page: 8 + for_sale: true items: - guid: 40118 unknown: 0 @@ -624,6 +625,7 @@ # Hair Color - guid: 123 page: 8 + for_sale: true items: - guid: 30021 unknown: 0 @@ -672,6 +674,7 @@ # Eye Color - guid: 124 page: 8 + for_sale: true items: - guid: 10012 unknown: 0 @@ -702,6 +705,7 @@ # Skin Tone - guid: 125 page: 8 + for_sale: true items: - guid: 60029 unknown: 0 @@ -766,6 +770,7 @@ # Face Design - guid: 126 page: 8 + for_sale: true items: - guid: 50062 unknown: 0 @@ -896,6 +901,7 @@ # Facial Hair - guid: 139 page: 8 + for_sale: true items: - guid: 20014 unknown: 0 @@ -930,6 +936,7 @@ # Species - guid: 127 page: 9 + for_sale: true items: - guid: 70012 unknown: 0 @@ -959,6 +966,7 @@ unknown: 0 # Boosts - guid: 10000 + for_sale: true items: - guid: 2240 unknown: 0 diff --git a/src/game_server/handlers/reference_data.rs b/src/game_server/handlers/reference_data.rs index c91e3afb..bc0ddf08 100644 --- a/src/game_server/handlers/reference_data.rs +++ b/src/game_server/handlers/reference_data.rs @@ -1,11 +1,14 @@ -use std::{fs::File, path::Path}; +use std::{collections::HashSet, fs::File, path::Path}; use serde::Deserialize; use crate::{ - game_server::packets::reference_data::{ - CategoryDefinitions, ItemClassDefinition, ItemClassDefinitions, ItemGroupDefinition, - ItemGroupItem, + game_server::{ + handlers::store::ItemCostMap, + packets::reference_data::{ + CategoryDefinitions, ItemClassDefinition, ItemClassDefinitions, ItemGroupDefinition, + ItemGroupItem, + }, }, ConfigError, }; @@ -51,6 +54,8 @@ pub struct ItemGroupConfig { #[serde(default)] pub members_only: bool, #[serde(default)] + pub for_sale: bool, + #[serde(default)] pub items: Vec, } @@ -78,8 +83,19 @@ impl From for ItemGroupDefinition { } } -pub fn load_item_groups(config_dir: &Path) -> Result, ConfigError> { +pub fn load_item_groups( + config_dir: &Path, + costs: &mut ItemCostMap, +) -> Result, ConfigError> { let mut file = File::open(config_dir.join("item_groups.yaml"))?; let groups: Vec = serde_yaml::from_reader(&mut file)?; + + let items_for_sale: HashSet = groups + .iter() + .filter(|group| group.for_sale) + .flat_map(|group| group.items.iter().map(|item| item.guid)) + .collect(); + costs.retain(|item_guid, _| items_for_sale.contains(item_guid)); + Ok(groups.into_iter().map(|group| group.into()).collect()) } diff --git a/src/game_server/mod.rs b/src/game_server/mod.rs index 20079f9b..c244e037 100644 --- a/src/game_server/mod.rs +++ b/src/game_server/mod.rs @@ -207,8 +207,8 @@ impl GameServer { pub fn new(config_dir: &Path) -> Result { let characters = GuidTable::new(); let (templates, zones, points_of_interest) = load_zones(config_dir)?; - let (item_definitions, costs) = load_item_definitions(config_dir)?; - let item_groups = load_item_groups(config_dir)?; + let (item_definitions, mut costs) = load_item_definitions(config_dir)?; + let item_groups = load_item_groups(config_dir, &mut costs)?; Ok(GameServer { categories: load_categories(config_dir)?, costs,