Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions crates/world-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,14 @@ async fn main() -> Result<ExitCode> {
.await
.context("Failed to load AreaTable.db2 / hotfix rows")?,
);
let area_trigger_db2_store = Arc::new(
wow_data::AreaTriggerDb2Store::load(&data_dir, &locale)
.context("Failed to load AreaTrigger.db2 — check DataDir and DBC.Locale config")?,
);
info!(
"Loaded {} area trigger DB2 rows from AreaTrigger.db2",
area_trigger_db2_store.len()
);
let fishing_base_skill_store = Arc::new(
wow_data::FishingBaseSkillStoreLikeCpp::load(world_db.as_ref(), &area_table_store)
.await
Expand Down Expand Up @@ -2796,6 +2804,22 @@ async fn main() -> Result<ExitCode> {
.await
.context("Failed to load area triggers")?,
);
let tavern_area_trigger_outcome = wow_data::TavernAreaTriggerStoreLikeCpp::load_like_cpp(
world_db.as_ref(),
area_trigger_db2_store.as_ref(),
)
.await
.context("Failed to load C++ tavern area triggers")?;
let tavern_area_trigger_store = Arc::new(tavern_area_trigger_outcome.store);
info!(
"Loaded {} C++ tavern area triggers ({} rows seen; {} skipped missing AreaTrigger.db2)",
tavern_area_trigger_store.len(),
tavern_area_trigger_outcome.report.rows_seen,
tavern_area_trigger_outcome
.report
.skipped_missing_area_trigger
.len()
);

// Load quest store (templates + objectives + NPC relations)
let quest_store = Arc::new(
Expand Down Expand Up @@ -4545,7 +4569,9 @@ async fn main() -> Result<ExitCode> {
gameobject_template_lifecycle_store: Some(Arc::clone(&gameobject_template_lifecycle_store)),
area_table_store: Some(Arc::clone(&area_table_store)),
fishing_base_skill_store: Some(Arc::clone(&fishing_base_skill_store)),
area_trigger_db2_store: Some(Arc::clone(&area_trigger_db2_store)),
area_trigger_store: Some(Arc::clone(&area_trigger_store)),
tavern_area_trigger_store: Some(Arc::clone(&tavern_area_trigger_store)),
graveyard_store: Some(Arc::clone(&graveyard_store)),
area_trigger_template_store: Some(Arc::clone(&area_trigger_template_store)),
chr_specialization_store: Some(Arc::clone(&chr_specialization_store)),
Expand Down Expand Up @@ -4599,6 +4625,18 @@ async fn main() -> Result<ExitCode> {
player_xp_table: Some(Arc::clone(&player_xp_table)),
exploration_base_xp_store: Some(Arc::clone(&exploration_base_xp_store)),
exploration_xp_rate: world_config_f32(&world_configs, "RATE_XP_EXPLORE", 1.0),
max_player_level_config: world_config_u32(&world_configs, "CONFIG_MAX_PLAYER_LEVEL", 80),
rest_offline_wilderness_rate: world_config_f32(
&world_configs,
"Rate.Rest.Offline.InWilderness",
1.0,
),
rest_offline_tavern_or_city_rate: world_config_f32(
&world_configs,
"Rate.Rest.Offline.InTavernOrCity",
1.0,
),
rest_ingame_rate: world_config_f32(&world_configs, "Rate.Rest.InGame", 1.0),
min_quest_scaled_xp_ratio: world_config_u32(
&world_configs,
"CONFIG_MIN_QUEST_SCALED_XP_RATIO",
Expand Down Expand Up @@ -11639,9 +11677,15 @@ async fn create_session(
if let Some(ref store) = resources.fishing_base_skill_store {
session.set_fishing_base_skill_store(Arc::clone(store));
}
if let Some(ref store) = resources.area_trigger_db2_store {
session.set_area_trigger_db2_store(Arc::clone(store));
}
if let Some(ref store) = resources.area_trigger_store {
session.set_area_trigger_store(Arc::clone(store));
}
if let Some(ref store) = resources.tavern_area_trigger_store {
session.set_tavern_area_trigger_store(Arc::clone(store));
}
if let Some(ref store) = resources.graveyard_store {
session.set_graveyard_store(Arc::clone(store));
}
Expand Down Expand Up @@ -11796,6 +11840,12 @@ async fn create_session(
session.set_exploration_base_xp_store_like_cpp(Arc::clone(store));
}
session.set_exploration_xp_rate_like_cpp(resources.exploration_xp_rate);
session.set_rested_xp_config_like_cpp(
resources.max_player_level_config,
resources.rest_offline_wilderness_rate,
resources.rest_offline_tavern_or_city_rate,
resources.rest_ingame_rate,
);
session.set_min_quest_scaled_xp_ratio_like_cpp(resources.min_quest_scaled_xp_ratio);
session.set_min_discovered_scaled_xp_ratio_like_cpp(resources.min_discovered_scaled_xp_ratio);
if let Some(ref registry) = resources.player_registry {
Expand Down
20 changes: 20 additions & 0 deletions crates/wow-data/src/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ pub struct FishingBaseSkillStoreLikeCpp {
}

pub const AREA_FLAG_ALLOW_HEARTH_AND_RESURRECT_FROM_AREA_LIKE_CPP: u32 = 0x0800_0000;
pub const AREA_FLAG_LINKED_CHAT_LIKE_CPP: u32 = 0x0000_0100;
pub const AREA_FLAG_NO_PVP_LIKE_CPP: u32 = 0x0000_0800;
pub const AREA_FLAG_HORDE_RESTING_LIKE_CPP: u32 = 0x0040_0000;
pub const AREA_FLAG_ALLIANCE_RESTING_LIKE_CPP: u32 = 0x0080_0000;
pub const AREA_FLAG_IS_SUBZONE_LIKE_CPP: u32 = 0x4000_0000;

impl AreaTableEntry {
Expand All @@ -55,6 +59,22 @@ impl AreaTableEntry {
self.flags & AREA_FLAG_ALLOW_HEARTH_AND_RESURRECT_FROM_AREA_LIKE_CPP != 0
}

pub fn linked_chat_like_cpp(&self) -> bool {
self.flags & AREA_FLAG_LINKED_CHAT_LIKE_CPP != 0
}

pub fn is_sanctuary_like_cpp(&self) -> bool {
self.flags & AREA_FLAG_NO_PVP_LIKE_CPP != 0
}

pub fn alliance_resting_like_cpp(&self) -> bool {
self.flags & AREA_FLAG_ALLIANCE_RESTING_LIKE_CPP != 0
}

pub fn horde_resting_like_cpp(&self) -> bool {
self.flags & AREA_FLAG_HORDE_RESTING_LIKE_CPP != 0
}

pub fn is_subzone_like_cpp(&self) -> bool {
self.flags & AREA_FLAG_IS_SUBZONE_LIKE_CPP != 0
}
Expand Down
32 changes: 26 additions & 6 deletions crates/wow-data/src/area_trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use wow_database::{WorldDatabase, WorldStatements};
use crate::quest::{
QUEST_FLAGS_COMPLETION_AREA_TRIGGER_LIKE_CPP, QUEST_OBJECTIVE_AREATRIGGER_LIKE_CPP, QuestStore,
};
use crate::{ScriptIdLikeCpp, ScriptNameInternerLikeCpp, WorldSafeLocStore};
use crate::{AreaTriggerDb2Store, ScriptIdLikeCpp, ScriptNameInternerLikeCpp, WorldSafeLocStore};

/// Area trigger shape types (from AreaTriggerShapeType).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -725,7 +725,7 @@ impl TavernAreaTriggerStoreLikeCpp {
/// - stores a set consumed by `ObjectMgr::IsTavernAreaTrigger`
pub async fn load_like_cpp(
db: &WorldDatabase,
area_trigger_store: &AreaTriggerStore,
area_trigger_db2_store: &AreaTriggerDb2Store,
) -> Result<TavernAreaTriggerLoadOutcomeLikeCpp> {
let mut rows = Vec::new();
let mut result = db.direct_query("SELECT id FROM areatrigger_tavern").await?;
Expand All @@ -739,7 +739,7 @@ impl TavernAreaTriggerStoreLikeCpp {
}

Ok(Self::from_ids_like_cpp(rows, |trigger_id| {
area_trigger_store.contains_trigger_like_cpp(trigger_id)
area_trigger_db2_store.get(trigger_id).is_some()
}))
}

Expand Down Expand Up @@ -967,12 +967,32 @@ mod area_trigger_script_tests {

#[test]
fn tavern_area_trigger_store_validates_ids_like_cpp() {
let mut area_triggers = AreaTriggerStore::new();
area_triggers.insert(trigger(10));
let area_triggers = AreaTriggerDb2Store::from_entries([crate::AreaTriggerDb2Entry {
id: 10,
message: String::new(),
pos: crate::maps_world::Db2Position3 {
x: 0.0,
y: 0.0,
z: 0.0,
},
continent_id: 0,
phase_use_flags: 0,
phase_id: 0,
phase_group_id: 0,
radius: 0.0,
box_length: 0.0,
box_width: 0.0,
box_height: 0.0,
box_yaw: 0.0,
shape_type: 0,
shape_id: 0,
area_trigger_action_set_id: 0,
flags: 0,
}]);

let outcome =
TavernAreaTriggerStoreLikeCpp::from_ids_like_cpp([10, 11, 10], |trigger_id| {
area_triggers.contains_trigger_like_cpp(trigger_id)
area_triggers.get(trigger_id).is_some()
});

assert_eq!(outcome.report.rows_seen, 3);
Expand Down
5 changes: 3 additions & 2 deletions crates/wow-data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ pub use access_requirement::{
AccessRequirementLoadReportLikeCpp, AccessRequirementRowLikeCpp, AccessRequirementStoreLikeCpp,
};
pub use area::{
AREA_FLAG_ALLOW_HEARTH_AND_RESURRECT_FROM_AREA_LIKE_CPP, AreaTableEntry, AreaTableStore,
FishingBaseSkillStoreLikeCpp,
AREA_FLAG_ALLIANCE_RESTING_LIKE_CPP, AREA_FLAG_ALLOW_HEARTH_AND_RESURRECT_FROM_AREA_LIKE_CPP,
AREA_FLAG_HORDE_RESTING_LIKE_CPP, AREA_FLAG_LINKED_CHAT_LIKE_CPP, AREA_FLAG_NO_PVP_LIKE_CPP,
AreaTableEntry, AreaTableStore, FishingBaseSkillStoreLikeCpp,
};
pub use area_trigger::{
AreaTriggerData, AreaTriggerScriptLoadOutcomeLikeCpp, AreaTriggerScriptLoadReportLikeCpp,
Expand Down
2 changes: 2 additions & 0 deletions crates/wow-data/src/spell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ pub mod aura_types {
pub const SPELL_AURA_MOD_BATTLE_PET_XP_PCT: i32 = 420;
pub const SPELL_AURA_MOD_MINIMUM_SPEED_RATE: i32 = 437;
pub const SPELL_AURA_MOD_ROOT_2: i32 = 455;
pub const SPELL_AURA_MOD_RESTED_XP_CONSUMPTION: i32 = 499;
}

/// Selected `Targets` ids from C++ `SpellImplicitTargetInfo::_data`.
Expand Down Expand Up @@ -6837,6 +6838,7 @@ mod tests {
assert_eq!(aura_types::SPELL_AURA_MOD_SPEED_NO_CONTROL, 373);
assert_eq!(aura_types::SPELL_AURA_MOD_BATTLE_PET_XP_PCT, 420);
assert_eq!(aura_types::SPELL_AURA_MOD_MINIMUM_SPEED_RATE, 437);
assert_eq!(aura_types::SPELL_AURA_MOD_RESTED_XP_CONSUMPTION, 499);

// C++ `SharedDefines.h`: selected SpellAttr0 anchors.
assert_eq!(attributes::SPELL_ATTR0_ONLY_INDOORS, 0x0000_4000);
Expand Down
21 changes: 21 additions & 0 deletions crates/wow-database/src/statements/character.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,13 @@ pub enum CharStatements {
/// UPDATE characters SET power1 = ?, ..., power10 = ? WHERE guid = ?
UPD_CHAR_POWERS,
/// C++ `CHAR_UPD_CHARACTER` persists these fields in the full save.
/// UPDATE characters SET restState = ?, playerFlags = ?, rest_bonus = ?, logout_time = ?, is_logout_resting = ? WHERE guid = ?
UPD_CHAR_REST_STATE,
/// C++ `RestMgr::GetRestBonusFor` updates in-memory rest state during online XP gain; Rust
/// persists only those fields here and leaves logout_time/is_logout_resting untouched.
/// UPDATE characters SET restState = ?, playerFlags = ?, rest_bonus = ? WHERE guid = ?
UPD_CHAR_ONLINE_REST_STATE,
/// C++ `CHAR_UPD_CHARACTER` persists these fields in the full save.
/// UPDATE characters SET resettalents_cost = ?, resettalents_time = ? WHERE guid = ?
UPD_CHAR_TALENT_RESET_STATE,
/// UPDATE characters SET xp = ? WHERE guid = ?
Expand Down Expand Up @@ -2750,6 +2757,12 @@ impl StatementDef for CharStatements {
Self::UPD_CHAR_POWERS => {
"UPDATE characters SET power1 = ?, power2 = ?, power3 = ?, power4 = ?, power5 = ?, power6 = ?, power7 = ?, power8 = ?, power9 = ?, power10 = ? WHERE guid = ?"
}
Self::UPD_CHAR_REST_STATE => {
"UPDATE characters SET restState = ?, playerFlags = ?, rest_bonus = ?, logout_time = ?, is_logout_resting = ? WHERE guid = ?"
}
Self::UPD_CHAR_ONLINE_REST_STATE => {
"UPDATE characters SET restState = ?, playerFlags = ?, rest_bonus = ? WHERE guid = ?"
}
Self::UPD_CHAR_TALENT_RESET_STATE => {
"UPDATE characters SET resettalents_cost = ?, resettalents_time = ? WHERE guid = ?"
}
Expand Down Expand Up @@ -4597,6 +4610,14 @@ mod tests {
CharStatements::UPD_CHAR_POWERS.sql(),
"UPDATE characters SET power1 = ?, power2 = ?, power3 = ?, power4 = ?, power5 = ?, power6 = ?, power7 = ?, power8 = ?, power9 = ?, power10 = ? WHERE guid = ?"
);
assert_eq!(
CharStatements::UPD_CHAR_REST_STATE.sql(),
"UPDATE characters SET restState = ?, playerFlags = ?, rest_bonus = ?, logout_time = ?, is_logout_resting = ? WHERE guid = ?"
);
assert_eq!(
CharStatements::UPD_CHAR_ONLINE_REST_STATE.sql(),
"UPDATE characters SET restState = ?, playerFlags = ?, rest_bonus = ? WHERE guid = ?"
);
assert_eq!(
CharStatements::UPD_CHAR_DIFFICULTIES.sql(),
"UPDATE characters SET dungeonDifficulty = ?, raidDifficulty = ?, legacyRaidDifficulty = ? WHERE guid = ?"
Expand Down
28 changes: 28 additions & 0 deletions crates/wow-entities/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@ pub const ACTIVE_PLAYER_DATA_INV_SLOTS_PARENT_BIT: usize = 124;
pub const ACTIVE_PLAYER_DATA_INV_SLOTS_FIRST_BIT: usize = 125;
pub const ACTIVE_PLAYER_DATA_EXPLORED_ZONES_PARENT_BIT: usize = 298;
pub const ACTIVE_PLAYER_DATA_EXPLORED_ZONES_FIRST_BIT: usize = 299;
pub const ACTIVE_PLAYER_DATA_REST_INFO_PARENT_BIT: usize = 539;
pub const ACTIVE_PLAYER_DATA_REST_INFO_FIRST_BIT: usize = 540;
pub const ACTIVE_PLAYER_DATA_BUYBACK_PARENT_BIT: usize = 549;
pub const ACTIVE_PLAYER_DATA_BUYBACK_PRICE_FIRST_BIT: usize = 550;
pub const ACTIVE_PLAYER_DATA_BUYBACK_TIMESTAMP_FIRST_BIT: usize = 562;
Expand Down Expand Up @@ -3174,6 +3176,7 @@ pub struct ActivePlayerDataValues {
pub num_backpack_slots: u8,
pub inv_slots: [ObjectGuid; PLAYER_SLOT_END],
pub explored_zones: [u64; PLAYER_EXPLORED_ZONES_SIZE_LIKE_CPP],
pub rest_info: [PlayerRestInfoValueLikeCpp; 2],
pub buyback_price: [u32; BUYBACK_SLOT_COUNT],
pub buyback_timestamp: [i64; BUYBACK_SLOT_COUNT],
pub bank_bag_slot_flags: [u32; 7],
Expand All @@ -3190,6 +3193,12 @@ pub struct ActivePlayerDataValues {
pub quest_completed: [u64; QUESTS_COMPLETED_BITS_SIZE],
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PlayerRestInfoValueLikeCpp {
pub threshold: u32,
pub state_id: u8,
}

impl Default for ActivePlayerDataValues {
fn default() -> Self {
Self {
Expand All @@ -3205,6 +3214,7 @@ impl Default for ActivePlayerDataValues {
num_backpack_slots: 0,
inv_slots: [ObjectGuid::EMPTY; PLAYER_SLOT_END],
explored_zones: [0; PLAYER_EXPLORED_ZONES_SIZE_LIKE_CPP],
rest_info: [PlayerRestInfoValueLikeCpp::default(); 2],
buyback_price: [0; BUYBACK_SLOT_COUNT],
buyback_timestamp: [0; BUYBACK_SLOT_COUNT],
bank_bag_slot_flags: [0; 7],
Expand Down Expand Up @@ -3907,6 +3917,24 @@ impl Player {
});
}

pub fn set_xp_rest_info_like_cpp(&mut self, threshold: u32, state_id: u8) {
self.set_rest_info_like_cpp(0, threshold, state_id);
}

pub fn set_rest_info_like_cpp(&mut self, index: usize, threshold: u32, state_id: u8) {
let Some(rest_info) = self.active_data.rest_info.get_mut(index) else {
return;
};
if rest_info.threshold != threshold || rest_info.state_id != state_id {
rest_info.threshold = threshold;
rest_info.state_id = state_id;
self.mark_active_player_data_section(
ACTIVE_PLAYER_DATA_REST_INFO_PARENT_BIT,
ACTIVE_PLAYER_DATA_REST_INFO_FIRST_BIT + index,
);
}
}

pub fn set_honor_like_cpp(&mut self, honor: i32) {
self.set_active_i32_in_section(
ACTIVE_PLAYER_DATA_HONOR_PARENT_BIT,
Expand Down
10 changes: 10 additions & 0 deletions crates/wow-network/src/accept.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,9 @@ pub struct SessionResources {
Option<Arc<wow_data::GameObjectTemplateLifecycleStoreLikeCpp>>,
pub area_table_store: Option<Arc<wow_data::AreaTableStore>>,
pub fishing_base_skill_store: Option<Arc<wow_data::FishingBaseSkillStoreLikeCpp>>,
pub area_trigger_db2_store: Option<Arc<wow_data::AreaTriggerDb2Store>>,
pub area_trigger_store: Option<Arc<wow_data::AreaTriggerStore>>,
pub tavern_area_trigger_store: Option<Arc<wow_data::TavernAreaTriggerStoreLikeCpp>>,
pub graveyard_store: Option<Arc<wow_data::GraveyardStore>>,
pub area_trigger_template_store: Option<Arc<wow_data::AreaTriggerTemplateStore>>,
pub chr_specialization_store: Option<Arc<wow_data::ChrSpecializationStore>>,
Expand Down Expand Up @@ -369,6 +371,14 @@ pub struct SessionResources {
pub exploration_base_xp_store: Option<Arc<wow_data::ExplorationBaseXpStoreLikeCpp>>,
/// C++ `sWorld->getRate(RATE_XP_EXPLORE)`.
pub exploration_xp_rate: f32,
/// C++ `CONFIG_MAX_PLAYER_LEVEL`.
pub max_player_level_config: u32,
/// C++ `sWorld->getRate(RATE_REST_OFFLINE_IN_WILDERNESS)`.
pub rest_offline_wilderness_rate: f32,
/// C++ `sWorld->getRate(RATE_REST_OFFLINE_IN_TAVERN_OR_CITY)`.
pub rest_offline_tavern_or_city_rate: f32,
/// C++ `sWorld->getRate(RATE_REST_INGAME)`.
pub rest_ingame_rate: f32,
/// C++ `CONFIG_MIN_QUEST_SCALED_XP_RATIO`.
pub min_quest_scaled_xp_ratio: u32,
/// C++ `CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO`.
Expand Down
6 changes: 3 additions & 3 deletions crates/wow-packet/src/packets/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13035,12 +13035,12 @@ mod tests {
// ── SMSG_LOG_XP_GAIN ─────────────────────────────────────────────────────────

/// Floating text "+XP" on screen when player earns experience.
/// C# ref: LogXPGain
/// C++ `WorldPackets::Character::LogXPGain::Write`.
pub struct LogXpGain {
pub victim: ObjectGuid,
pub original: i32, // XP before bonuses
pub original: i32, // base XP plus represented bonuses
pub reason: u8, // 0=Kill, 1=NoKill(quest/explore)
pub amount: i32, // XP after bonuses (what actually counts)
pub amount: i32, // base XP amount
pub group_bonus: f32,
}

Expand Down
Loading
Loading