diff --git a/.github/actions/core-ref/action.yml b/.github/actions/core-ref/action.yml new file mode 100644 index 0000000..aec174b --- /dev/null +++ b/.github/actions/core-ref/action.yml @@ -0,0 +1,30 @@ +name: Paired Core revision +description: Select a same-name Core branch when present, otherwise use the required revision. +inputs: + fallback: + description: Core revision required by this collection. + required: true +outputs: + ref: + description: Exact paired commit or required Core revision. + value: ${{ steps.select.outputs.ref }} +runs: + using: composite + steps: + - id: select + shell: bash + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + FALLBACK: ${{ inputs.fallback }} + run: | + if [ "$BRANCH" = main ]; then + echo "ref=$FALLBACK" >> "$GITHUB_OUTPUT" + elif paired=$(git ls-remote --exit-code --heads https://github.com/LyraCoreProject/LyraCore "refs/heads/$BRANCH"); then + echo "ref=${paired%%[[:space:]]*}" >> "$GITHUB_OUTPUT" + else + status=$? + if [ "$status" -ne 2 ]; then + exit "$status" + fi + echo "ref=$FALLBACK" >> "$GITHUB_OUTPUT" + fi diff --git a/.github/check-core-tip.sh b/.github/check-core-tip.sh index 6b2bf4d..a49ecac 100755 --- a/.github/check-core-tip.sh +++ b/.github/check-core-tip.sh @@ -83,6 +83,9 @@ for package_dir in "${package_dirs[@]}"; do installed_links+=("$link") done +# Check the linked Packages before any caller-selected verification command. +cargo +1.93.0 test --manifest-path "$core_root/Cargo.toml" -p lyracore-module --test package_api_lint + # The default check: every collection Package compiles into the module. A caller that needs the # same linked tree for something else (e.g. the datascripts job's typegen/typecheck chain) passes # its own command instead — the link/displace/cleanup dance above is identical either way. diff --git a/.github/workflows/core-tip.yml b/.github/workflows/core-tip.yml index b645698..c6d980b 100644 --- a/.github/workflows/core-tip.yml +++ b/.github/workflows/core-tip.yml @@ -67,11 +67,16 @@ jobs: path: collection persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - name: Check out the required Core revision uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false @@ -132,11 +137,16 @@ jobs: path: collection persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - name: Check out the required Core revision uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false @@ -198,11 +208,16 @@ jobs: with: path: collection persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - name: Check out Core with bounded population checks uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target @@ -749,11 +764,16 @@ jobs: with: path: collection persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - name: Check out Core with the additive Transfer migration check uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target @@ -971,11 +991,16 @@ jobs: with: path: collection persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - name: Check out Core with the movement Gate uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target @@ -1170,11 +1195,16 @@ jobs: ref: 50e2cd10870d2cfb4a36018bbbd559f9d07c2062 path: comparison-collection persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - name: Check out Core with recorded recovery deadlines uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target diff --git a/.github/workflows/playerbots-capacity.yml b/.github/workflows/playerbots-capacity.yml index a96f4bf..5815025 100644 --- a/.github/workflows/playerbots-capacity.yml +++ b/.github/workflows/playerbots-capacity.yml @@ -38,11 +38,16 @@ jobs: ref: e134b135099a7d358a0f0fab58654c200602b9b1 path: baseline persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore # Core supplies the shared private integration fixtures. - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/playerbots-class-behavior.yml b/.github/workflows/playerbots-class-behavior.yml index 1ec0189..e260a4b 100644 --- a/.github/workflows/playerbots-class-behavior.yml +++ b/.github/workflows/playerbots-class-behavior.yml @@ -32,10 +32,15 @@ jobs: with: path: collection persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false - name: Install pinned Rust and SpacetimeDB diff --git a/.github/workflows/playerbots-target-claims.yml b/.github/workflows/playerbots-target-claims.yml index ac5d077..aea1b37 100644 --- a/.github/workflows/playerbots-target-claims.yml +++ b/.github/workflows/playerbots-target-claims.yml @@ -45,10 +45,15 @@ jobs: ref: 0cae690301552430d713a582b0036e7096c215d2 path: baseline-core persist-credentials: false + - name: Select the paired Core revision + id: core-ref + uses: ./collection/.github/actions/core-ref + with: + fallback: 6e73c274d7b084c7c0b1842d4a7ae82316952b4a - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: f91066df4aef5c8d753dbe1d2203c6a669f485fa + ref: ${{ steps.core-ref.outputs.ref }} path: lyracore persist-credentials: false - name: Install pinned Rust and SpacetimeDB diff --git a/README.md b/README.md index 327e52b..42be4a2 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ This repository is LyraCore's Official Package Collection. Each visible top-level directory is one independently installable Package. There is no separate registry or index file. -The typed Runtime Script examples require Package API version 2. Each Package API version has a -tag, such as `api-v2`. After the compatibility checks pass on -`main`, CI moves the tag for the checked Core revision's Package API version to that collection -commit. Tags for older API versions stay at their last compatible commit. +This collection requires Package API version 3. Rust Packages use `actor`, `hooks`, `package`, +`tables`, and `script_binding`. Core checkouts on version 2 can use `api-v2`. Each Package API version has +a tag, such as `api-v3`. After the compatibility checks pass on `main`, CI moves the tag for the +checked Core revision's Package API version to that collection commit. Tags for older API versions stay at their last compatible commit. `lyracore packages add ` and `lyracore packages update` select the tag from the version in the checkout's `docs/package-api.md`. A missing tag refuses the operation. Each installed Package's diff --git a/dungeons/src/deadmines.rs b/dungeons/src/deadmines.rs index 5258938..00435c8 100644 --- a/dungeons/src/deadmines.rs +++ b/dungeons/src/deadmines.rs @@ -19,8 +19,8 @@ use spacetimedb::ReducerContext; -use crate::encounter::{self, ENCOUNTER_DONE}; -use crate::{game_gameobject, game_world_entity}; +use crate::package::encounter::{self, ENCOUNTER_DONE}; +use crate::tables::{game_gameobject, game_world_entity}; pub(crate) const MAP_ID: u32 = 36; @@ -79,10 +79,10 @@ crate::game_hook!(on_aggro, fn smite_calls_to_arms(ctx, payload) { if creature.entry != MR_SMITE || creature.instance_id == 0 { return; } - let _ = crate::chat::apply_send_chat( + let _ = crate::actor::send_chat( ctx, creature, - crate::chat::CHAT_YELL, + lyracore_shared::chat::broadcast_chat::YELL, 0, SMITE_AGGRO_YELL.to_string(), ); @@ -130,10 +130,10 @@ crate::game_hook!(on_hp_threshold, fn smite_changes_weapons(ctx, payload) { _ => return, }; if let Some(smite) = ctx.db.game_world_entity().guid().find(payload.creature_guid) { - let _ = crate::chat::apply_send_chat( + let _ = crate::actor::send_chat( ctx, smite, - crate::chat::CHAT_YELL, + lyracore_shared::chat::broadcast_chat::YELL, 0, yell.to_string(), ); @@ -192,7 +192,9 @@ fn open_door_logged(ctx: &ReducerContext, go_entry: u32, instance_id: u64) { } fn mark_done(ctx: &ReducerContext, instance_id: u64, encounter_id: u32) { - if let Err(error) = encounter::set_encounter_state(ctx, instance_id, encounter_id, ENCOUNTER_DONE) { + if let Err(error) = + encounter::set_encounter_state(ctx, instance_id, encounter_id, ENCOUNTER_DONE) + { spacetimedb::log::warn!("deadmines: encounter {encounter_id} state write failed: {error}"); } } diff --git a/dungeons/src/deadmines_verify.rs b/dungeons/src/deadmines_verify.rs index ae2f08a..6612c58 100644 --- a/dungeons/src/deadmines_verify.rs +++ b/dungeons/src/deadmines_verify.rs @@ -14,8 +14,8 @@ use super::deadmines::{ MR_SMITE, RHAHKZOR, SMITES_CHEST, SMITES_MIGHTY_HAMMER, SMITES_REAVER, SMITE_FIRST_STAND_PCT, SMITE_SECOND_STAND_PCT, SNEED, SNEEDS_SHREDDER, }; -use crate::encounter::{self, DOOR_OPEN_STATE, ENCOUNTER_DONE}; -use crate::{ +use crate::package::encounter::{self, DOOR_OPEN_STATE, ENCOUNTER_DONE}; +use crate::tables::{ game_chat_event, game_creature_spline, game_creature_template, game_encounter_equip, game_gameobject, game_gameobject_template, game_instance, game_item_template, game_world_entity, GameInstance, @@ -31,11 +31,27 @@ const GO_FIXTURE_BAND: u64 = 0xF11D << 48; pub fn debug_deadmines_begin(ctx: &ReducerContext) -> Result<(), String> { install_instance(ctx, MAP_ID, DEADMINES_INSTANCE)?; for (entry, type_id, display_id, x, y, z, orientation) in [ - (FACTORY_DOOR, 0u8, 394u32, -191.414f32, -457.446f32, 54.4391f32, 1.69297f32), + ( + FACTORY_DOOR, + 0u8, + 394u32, + -191.414f32, + -457.446f32, + 54.4391f32, + 1.69297f32, + ), (MAST_ROOM_DOOR, 0, 394, -290.294, -536.96, 49.4353, 1.55334), (FOUNDRY_DOOR, 0, 394, -168.514, -579.861, 19.3159, 3.12412), (IRON_CLAD_DOOR, 0, 394, -100.502, -668.771, 7.41049, 1.81514), - (DEFIAS_CANNON, 10, 245, -107.562, -659.674, 7.21211, -0.890294), + ( + DEFIAS_CANNON, + 10, + 245, + -107.562, + -659.674, + 7.21211, + -0.890294, + ), (SMITES_CHEST, 5, 259, 2.69086, -781.633, 9.76985, 2.33874), ] { seed_gameobject(ctx, entry, type_id, display_id, x, y, z, orientation)?; @@ -66,15 +82,24 @@ pub fn debug_deadmines_begin(ctx: &ReducerContext) -> Result<(), String> { #[reducer] pub fn debug_deadmines_rhahkzor_falls(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, RHAHKZOR)?; - require_door_open(ctx, FACTORY_DOOR, "Rhahk'Zor's death did not open the Factory Door")?; - require_done(ctx, ENCOUNTER_RHAHKZOR, "Rhahk'Zor encounter state is not Done") + require_door_open( + ctx, + FACTORY_DOOR, + "Rhahk'Zor's death did not open the Factory Door", + )?; + require_done( + ctx, + ENCOUNTER_RHAHKZOR, + "Rhahk'Zor encounter state is not Done", + ) } /// Destroying Sneed's Shredder ejects Sneed; the Mast Room Door stays shut until Sneed dies. #[reducer] pub fn debug_deadmines_shredder_ejects_sneed(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, SNEEDS_SHREDDER)?; - let sneed = live_boss(ctx, SNEED).ok_or_else(|| "no Sneed came out of the wreck".to_string())?; + let sneed = + live_boss(ctx, SNEED).ok_or_else(|| "no Sneed came out of the wreck".to_string())?; let wreck = fixture_guid(SNEEDS_SHREDDER, 41); let wreck = ctx .db @@ -98,10 +123,14 @@ pub fn debug_deadmines_sneed_falls(ctx: &ReducerContext) -> Result<(), String> { let sneed = live_boss(ctx, SNEED).ok_or_else(|| "Sneed is not alive to kill".to_string())?; let player = fixture_guid(0, 44); require( - crate::combat::kill_creature(ctx, sneed.guid, Some(player)), + crate::package::fixture::kill_creature(ctx, sneed.guid, Some(player)), "Sneed did not die", )?; - require_door_open(ctx, MAST_ROOM_DOOR, "Sneed's death did not open the Mast Room Door")?; + require_door_open( + ctx, + MAST_ROOM_DOOR, + "Sneed's death did not open the Mast Room Door", + )?; require_done(ctx, ENCOUNTER_SNEED, "Sneed encounter state is not Done") } @@ -109,7 +138,11 @@ pub fn debug_deadmines_sneed_falls(ctx: &ReducerContext) -> Result<(), String> { #[reducer] pub fn debug_deadmines_gilnid_falls(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, GILNID)?; - require_door_open(ctx, FOUNDRY_DOOR, "Gilnid's death did not open the Foundry Door")?; + require_door_open( + ctx, + FOUNDRY_DOOR, + "Gilnid's death did not open the Foundry Door", + )?; require_done(ctx, ENCOUNTER_GILNID, "Gilnid encounter state is not Done") } @@ -136,8 +169,12 @@ pub fn debug_deadmines_cannon_breaches(ctx: &ReducerContext) -> Result<(), Strin let player = fixture_guid(0, 44); set_fixture_position(ctx, player, -106.5, -659.0, 7.2)?; let cannon_guid = GO_FIXTURE_BAND | u64::from(DEFIAS_CANNON); - crate::gameobject::apply_use_gameobject(ctx, player, cannon_guid)?; - require_door_open(ctx, IRON_CLAD_DOOR, "the cannon did not breach the Iron Clad Door")?; + crate::actor::use_gameobject(ctx, player, cannon_guid)?; + require_door_open( + ctx, + IRON_CLAD_DOOR, + "the cannon did not breach the Iron Clad Door", + )?; require_done(ctx, ENCOUNTER_CANNON, "cannon encounter state is not Done") } @@ -145,7 +182,11 @@ pub fn debug_deadmines_cannon_breaches(ctx: &ReducerContext) -> Result<(), Strin #[reducer] pub fn debug_deadmines_smite_falls(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, MR_SMITE)?; - require_done(ctx, ENCOUNTER_SMITE, "Mr. Smite encounter state is not Done") + require_done( + ctx, + ENCOUNTER_SMITE, + "Mr. Smite encounter state is not Done", + ) } // ------------------------------------------------------------------------------------------- @@ -164,7 +205,7 @@ fn seed_gameobject( ) -> Result<(), String> { let templates = ctx.db.game_gameobject_template(); if templates.entry().find(entry).is_none() { - templates.insert(crate::gameobject::GameObjectTemplate { + templates.insert(crate::tables::GameObjectTemplate { entry, type_id, display_id, @@ -182,7 +223,7 @@ fn seed_gameobject( let gos = ctx.db.game_gameobject(); gos.guid().delete(guid); let (grid_x, grid_y) = lyracore_shared::spatial::grid_cell(x, y); - gos.insert(crate::gameobject::GameObject { + gos.insert(crate::tables::GameObject { guid, template_entry: entry, map_id: MAP_ID, @@ -249,12 +290,12 @@ fn kill_fixture_boss(ctx: &ReducerContext, entry: u32) -> Result<(), String> { let boss = live_boss(ctx, entry).ok_or_else(|| format!("boss {entry} is not alive"))?; let player = fixture_guid(0, 44); require( - crate::combat::kill_creature(ctx, boss.guid, Some(player)), + crate::package::fixture::kill_creature(ctx, boss.guid, Some(player)), "the killing blow did nothing", ) } -fn live_boss(ctx: &ReducerContext, entry: u32) -> Option { +fn live_boss(ctx: &ReducerContext, entry: u32) -> Option { ctx.db .game_world_entity() .by_map() @@ -267,7 +308,7 @@ fn damage_smite_to_pct(ctx: &ReducerContext, target_pct: u32) -> Result<(), Stri let target_health = smite.max_health * target_pct / 100; let amount = smite.health.saturating_sub(target_health); require(amount > 0, "Smite is already below the target health")?; - crate::package_fixture::apply_damage(ctx, smite.guid, amount, fixture_guid(0, 44)) + crate::package::fixture::apply_damage(ctx, smite.guid, amount, fixture_guid(0, 44)) } fn door_state(ctx: &ReducerContext, entry: u32) -> Result { @@ -320,7 +361,11 @@ fn require_smite_at_chest(ctx: &ReducerContext) -> Result<(), String> { "Smite did not run to his chest", )?; require( - ctx.db.game_creature_spline().guid().find(smite_guid).is_some(), + ctx.db + .game_creature_spline() + .guid() + .find(smite_guid) + .is_some(), "Smite's chest run emitted no movement spline", ) } diff --git a/example-all/src/mod.rs b/example-all/src/mod.rs index f96be84..1266c84 100644 --- a/example-all/src/mod.rs +++ b/example-all/src/mod.rs @@ -1,10 +1,11 @@ -use crate::package_config::{ensure_package_config_default, game_package_config}; +use crate::package::ensure_config_default; +use crate::tables::game_package_config; const PACKAGE: &str = "example-all"; const WELCOME_EVENT: &str = "example-all.welcome"; crate::game_hook!(on_login, fn example_all_on_login(ctx, payload) { - ensure_package_config_default(ctx, PACKAGE, "welcome", "true"); + ensure_config_default(ctx, PACKAGE, "welcome", "true"); let fallback = ctx.db.game_package_config().by_package_key() .filter((PACKAGE, "welcome")).next().map(|row| row.value); let answer = crate::script_binding::ask(ctx, WELCOME_EVENT, payload.character_guid, 0); diff --git a/example-all/src/tests.rs b/example-all/src/tests.rs index 232c77c..acb9b88 100644 --- a/example-all/src/tests.rs +++ b/example-all/src/tests.rs @@ -1,6 +1,6 @@ #![cfg(test)] -use crate::package_test::{ask_artifact_offline, ask_offline}; +use crate::package::test::{ask_artifact_offline, ask_offline}; #[test] fn the_script_answer_overrides_config() { diff --git a/example-rust/src/mod.rs b/example-rust/src/mod.rs index 3710f1d..ca5f6c7 100644 --- a/example-rust/src/mod.rs +++ b/example-rust/src/mod.rs @@ -1,10 +1,11 @@ -use crate::package_config::{ensure_package_config_default, game_package_config}; +use crate::package::ensure_config_default; +use crate::tables::game_package_config; const PACKAGE: &str = "example-rust"; const DEFAULT_GREETING: &str = "Welcome from example-rust!"; crate::game_hook!(on_login, fn example_rust_on_login(ctx, payload) { - ensure_package_config_default(ctx, PACKAGE, "greeting", DEFAULT_GREETING); + ensure_config_default(ctx, PACKAGE, "greeting", DEFAULT_GREETING); let configured = ctx.db.game_package_config().by_package_key() .filter((PACKAGE, "greeting")).next().map(|row| row.value); let message = greeting(configured.as_deref()).to_string(); diff --git a/playerbots/src/acceptance_fixture.rs b/playerbots/src/acceptance_fixture.rs index 955264a..e5bf138 100644 --- a/playerbots/src/acceptance_fixture.rs +++ b/playerbots/src/acceptance_fixture.rs @@ -16,11 +16,11 @@ use super::{ pkg_playerbots_bot, pkg_playerbots_runner, ActionKind, ActionOutcome, Controller, Failure, ObjectiveKind, PlayerbotsBot, RunnerOutcome, }; -use crate::nav::{game_nav_chunk, game_navigation_revision}; -use crate::{ +use crate::tables::{ game_character_quest, game_corpse_loot, game_creature_spawn, game_creature_template, game_graveyard, game_item_instance, game_quest_template, game_world_entity, }; +use crate::tables::{game_nav_chunk, game_navigation_revision}; use spacetimedb::{reducer, table, ReducerContext, Table, Timestamp}; const PLAN_REVISION: &str = "playerbots-acceptance-seed-plan-v1"; @@ -546,7 +546,7 @@ fn apply_incoming_fault( row.incoming_destination = Some(quest.destination); let amount = before.health.saturating_sub(1).min(25); let accepted = - crate::package_fixture::apply_damage(ctx, row.character_guid, amount, attacker_guid) + crate::package::fixture::apply_damage(ctx, row.character_guid, amount, attacker_guid) .is_ok(); let Some(after) = ctx.db.game_world_entity().guid().find(row.character_guid) else { return; @@ -585,15 +585,19 @@ fn apply_lethal_fault( row.lethal_objective_identity_before = Some(quest.objective_identity); row.lethal_purpose = Some(quest.purpose); row.lethal_destination = Some(quest.destination); - let (amount, _) = - crate::combat::fold_incoming_damage(ctx, attacker_guid, row.character_guid, 10_000); - let damage = crate::combat::final_damage(ctx, row.character_guid, amount); - let outcome = crate::combat::apply_hit( + let (amount, _) = crate::package::fixture::fold_incoming_damage( + ctx, + attacker_guid, + row.character_guid, + 10_000, + ); + let damage = crate::package::fixture::final_damage(ctx, row.character_guid, amount); + let outcome = crate::package::fixture::apply_hit( ctx, attacker_guid, row.character_guid, damage, - crate::combat::Hit::weapon(crate::combat::HitSource::MainHand, false), + crate::package::fixture::Hit::weapon(crate::package::fixture::HitSource::MainHand, false), ); let Some(after) = ctx.db.game_world_entity().guid().find(row.character_guid) else { return; @@ -696,12 +700,12 @@ fn fill_inventory_fault( row.purpose_before_fill = Some(quest.purpose); row.destination_before_fill = Some(quest.destination); for _ in 0..64 { - if !crate::items::has_free_slot(ctx, row.character_guid) { + if !crate::actor::has_free_slot(ctx, row.character_guid) { row.fill_applied_micros = Some(now); return; } if let Err(error) = - crate::items::grant_item(ctx, row.character_guid, INVENTORY_FAULT_FILLER, 1) + crate::package::fixture::grant_item(ctx, row.character_guid, INVENTORY_FAULT_FILLER, 1) { row.fill_error = Some(error); return; @@ -745,7 +749,7 @@ fn record_inventory_refusal( if loot.len() == 1 { row.refused_loot_slot = Some(loot[0].slot); } - row.item_count_at_refusal = Some(crate::items::item_count( + row.item_count_at_refusal = Some(crate::actor::item_count( ctx, row.character_guid, row.item_entry, @@ -782,7 +786,7 @@ fn release_inventory_space(ctx: &ReducerContext, row: &mut AcceptanceInventoryFa return; }; ctx.db.game_item_instance().guid().delete(filler.guid); - if crate::items::has_free_slot(ctx, row.character_guid) { + if crate::actor::has_free_slot(ctx, row.character_guid) { row.space_released_micros = Some(now); } else { row.space_release_error = Some("one inventory slot did not become available".to_string()); @@ -798,7 +802,7 @@ fn record_inventory_resume( else { return false; }; - let item_count = crate::items::item_count(ctx, row.character_guid, row.item_entry); + let item_count = crate::actor::item_count(ctx, row.character_guid, row.item_entry); if action.quest_entry != row.quest_entry || !matches!(action.outcome, ActionOutcome::Completed) || item_count == 0 @@ -821,7 +825,7 @@ fn record_inventory_resume( fn advance_inventory_fault(ctx: &ReducerContext, mut row: AcceptanceInventoryFault, now: i64) { if row.fill_attempted_micros.is_none() { - let item_count = crate::items::item_count(ctx, row.character_guid, row.item_entry); + let item_count = crate::actor::item_count(ctx, row.character_guid, row.item_entry); if item_count == 0 { if let Some(quest) = acceptance_quest_state(ctx, row.character_guid, row.quest_entry) { fill_inventory_fault(ctx, &mut row, now, item_count, quest); @@ -940,7 +944,7 @@ fn arm_missing_target_fault( row.respawn_due_micros = Some(respawn_due); spawn.respawn_at = Timestamp::from_micros_since_unix_epoch(respawn_due); spawns.guid().update(spawn); - crate::creatures::despawn_creature_entity(ctx, row.target_guid); + crate::package::fixture::despawn_creature_entity(ctx, row.target_guid); if ctx .db .game_world_entity() @@ -1147,7 +1151,7 @@ fn replace_local_navigation( for y in cell_y.saturating_sub(1)..=cell_y.saturating_add(1).min(1023) { let key = lyracore_shared::terrain::cell_key(entity.map_id, x, y); chunks.key().delete(key); - chunks.insert(crate::nav::NavChunk { + chunks.insert(crate::tables::NavChunk { key, map_id: entity.map_id, cell_x: x, @@ -1166,7 +1170,7 @@ fn replace_local_navigation( keys.push(key); } } - crate::nav::record_change(ctx)?; + crate::package::fixture::record_navigation_change(ctx)?; Ok(keys) } @@ -1662,7 +1666,7 @@ pub fn playerbots_acceptance_resolve_seed_plan( ctx: &ReducerContext, seed: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let plan = seed_plan(seed)?; let rows = ctx.db.pkg_playerbots_acceptance_seed_plan(); match rows.seed().find(seed) { @@ -1704,7 +1708,7 @@ fn prepare_inventory_fault(ctx: &ReducerContext, character_guid: u64) -> Result< for filler in fillers { items.guid().delete(filler.guid); } - if !crate::items::has_free_slot(ctx, character_guid) { + if !crate::actor::has_free_slot(ctx, character_guid) { return Err("acceptance inventory setup did not restore a free slot".to_string()); } Ok(()) @@ -2086,7 +2090,7 @@ fn stage_journey_for( /// transaction. The caller selects Cohort control only after it reads the saved manifest. #[reducer] pub fn playerbots_acceptance_stage_journey(ctx: &ReducerContext, seed: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let plan = seed_plan(seed)?; let journeys = ctx.db.pkg_playerbots_acceptance_journey(); let mut existing = journeys.iter().take(2); @@ -2128,7 +2132,7 @@ pub fn playerbots_acceptance_begin_journey( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let journeys = ctx.db.pkg_playerbots_acceptance_journey(); let mut journey = journeys .character_guid() @@ -2168,7 +2172,7 @@ fn stage_level_gap_sources( ctx: &ReducerContext, character_guid: u64, ) -> Result<(Vec, Vec), String> { - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let templates = ctx.db.game_creature_template(); let mut template = templates .entry() @@ -2206,7 +2210,7 @@ fn stage_level_gap_sources( character.y - 25.0 + (offset / 11) as f32 * 10.0, ) }; - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid, entry: LEVEL_GAP_SOURCE_ENTRY, map_id: character.map_id, @@ -2214,13 +2218,13 @@ fn stage_level_gap_sources( y, z: character.z, orientation: 0.0, - respawn_at: crate::creatures::timer_never(ctx), - despawn_at: crate::creatures::timer_never(ctx), + respawn_at: crate::package::fixture::timer_never(ctx), + despawn_at: crate::package::fixture::timer_never(ctx), movement_type: 0, respawn_secs: 60, life_seq: 1, }; - let mut entity = crate::creatures::build_creature_entity(&spawn, &template, 0, 0); + let mut entity = crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0); if offset < LEVEL_GAP_LONG_SOURCE_COUNT { entity.health = 20; entity.max_health = 20; @@ -2229,14 +2233,14 @@ fn stage_level_gap_sources( spawns.guid().delete(guid); spawns.insert(spawn); entities.guid().delete(guid); - crate::creatures::insert_creature_entity(ctx, entity); + crate::package::fixture::insert_creature_entity(ctx, entity); source_guids.push(guid); } Ok((source_guids, long_source_guids)) } fn stage_level_gap_history(ctx: &ReducerContext, character_guid: u64) -> Result<(), String> { - let owner_identity = crate::helpers::live_entity(ctx, character_guid)?.owner_identity; + let owner_identity = crate::actor::live_entity(ctx, character_guid)?.owner_identity; let quests = ctx.db.game_character_quest(); for quest_entry in [783, 7, 5261, 33, 18, 3903, 3904, 3905] { for row in quests @@ -2246,7 +2250,7 @@ fn stage_level_gap_history(ctx: &ReducerContext, character_guid: u64) -> Result< { quests.id().delete(row.id); } - quests.insert(crate::CharacterQuest { + quests.insert(crate::tables::CharacterQuest { id: 0, character_guid, owner_identity, @@ -2261,7 +2265,7 @@ fn stage_level_gap_history(ctx: &ReducerContext, character_guid: u64) -> Result< } fn stage_level_gap_graveyard(ctx: &ReducerContext, character_guid: u64) -> Result<(), String> { - let entity = crate::helpers::live_entity(ctx, character_guid)?; + let entity = crate::actor::live_entity(ctx, character_guid)?; let graveyards = ctx.db.game_graveyard(); let mut graveyard = graveyards .id() @@ -2279,7 +2283,7 @@ fn stage_level_gap_graveyard(ctx: &ReducerContext, character_guid: u64) -> Resul /// Stage the source-derived level-seven boundary without granting XP or changing level after start. #[reducer] pub fn playerbots_acceptance_stage_level_gap(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if ctx.db.pkg_playerbots_bot().iter().next().is_some() || ctx .db @@ -2321,7 +2325,7 @@ pub fn playerbots_acceptance_stage_level_gap(ctx: &ReducerContext) -> Result<(), if catalog.min_level != LEVEL_GAP_REQUIRED_LEVEL { return Err("level-gap catalog did not retain the source requirement".to_string()); } - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; super::runner::playerbots_select_controller(ctx, character_guid, Controller::Frozen)?; ctx.db .pkg_playerbots_acceptance_level_gap() @@ -2460,7 +2464,7 @@ pub fn playerbots_acceptance_begin_level_gap( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let rows = ctx.db.pkg_playerbots_acceptance_level_gap(); let mut fixture = rows .character_guid() diff --git a/playerbots/src/action_lifecycle_fixture.rs b/playerbots/src/action_lifecycle_fixture.rs index 83f6354..c89c72f 100644 --- a/playerbots/src/action_lifecycle_fixture.rs +++ b/playerbots/src/action_lifecycle_fixture.rs @@ -2,7 +2,9 @@ //! Declared Core facts for the action-lifecycle acceptance cases. -use crate::{game_creature_spawn, game_creature_spline, game_gameobject, game_world_entity}; +use crate::tables::{ + game_creature_spawn, game_creature_spline, game_gameobject, game_world_entity, +}; use spacetimedb::{reducer, ReducerContext}; fn place_entity(ctx: &ReducerContext, guid: u64, x: f32) -> Result<(), String> { @@ -43,7 +45,7 @@ pub fn playerbots_action_lifecycle_stage_quest_plan( kind: u8, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; match kind { 0 => {} 1 => super::quest_catalog_fixture::playerbots_quest_fixture_admit_accept( @@ -93,7 +95,7 @@ pub fn playerbots_action_lifecycle_stage_quest_plan( )?, _ => return Err("unknown action lifecycle quest plan".to_string()), } - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let x = character.x + if kind == 6 { 80.0 } else { 40.0 }; if matches!(kind, 3 | 4) { place_gameobject(ctx, target_guid, x)?; @@ -113,7 +115,7 @@ pub fn playerbots_action_lifecycle_expire_movement( expected_move: u8, expected_target: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; super::fixture::playerbots_fixture_runner_pass_once(ctx, character_guid)?; use super::decision::{Action, MoveTarget}; diff --git a/playerbots/src/actions.rs b/playerbots/src/actions.rs index ff6fbf0..d461924 100644 --- a/playerbots/src/actions.rs +++ b/playerbots/src/actions.rs @@ -1,6 +1,6 @@ //! Bounded action observations. Acceptance never implies damage or quest credit. -use crate::spell::{CastFinish, CastHandle, CastRefusal, CastStart}; +use crate::actor::{CastFinish, CastHandle, CastRefusal, CastStart}; use spacetimedb::{table, ReducerContext, Table}; #[derive(spacetimedb::SpacetimeType, Clone, Copy, Debug, PartialEq, Eq)] @@ -18,7 +18,7 @@ pub enum ActionKind { #[derive(spacetimedb::SpacetimeType, Clone, Debug)] pub enum ActionOutcome { - AttackAccepted(crate::combat::AttackStart), + AttackAccepted(crate::actor::AttackStart), Completed, Movement(MovementObservation), CastResolved, @@ -85,7 +85,7 @@ pub(super) fn attack( ctx: &ReducerContext, guid: u64, target_guid: u64, -) -> Result { +) -> Result { let result = crate::actor::request_attack(ctx, guid, target_guid); let outcome = match &result { Ok(accepted) => ActionOutcome::AttackAccepted(*accepted), @@ -340,16 +340,16 @@ pub(super) fn take_loot( pub struct MovementObservation { pub map_id: u32, pub instance_id: u64, - pub destination: crate::nav::RoutePoint, - pub route: crate::nav::RouteStep, + pub destination: crate::actor::movement::RoutePoint, + pub route: crate::actor::movement::RouteStep, pub arrived: bool, pub last_advance_micros: Option, } pub(super) fn advanced_on_leg( - route: &crate::nav::RouteStep, - previous: crate::nav::RoutePoint, - current: crate::nav::RoutePoint, + route: &crate::actor::movement::RouteStep, + previous: crate::actor::movement::RoutePoint, + current: crate::actor::movement::RoutePoint, ) -> bool { let dx = route.endpoint.x - route.from.x; let dy = route.endpoint.y - route.from.y; @@ -371,9 +371,9 @@ pub(super) fn movement( guid: u64, map_id: u32, instance_id: u64, - destination: crate::nav::RoutePoint, + destination: crate::actor::movement::RoutePoint, arrived: bool, - route: crate::nav::RouteStep, + route: crate::actor::movement::RouteStep, ) { let now = ctx.timestamp.to_micros_since_unix_epoch(); let mut last_advance_micros = None; diff --git a/playerbots/src/class_behavior.rs b/playerbots/src/class_behavior.rs index f756665..422a1db 100644 --- a/playerbots/src/class_behavior.rs +++ b/playerbots/src/class_behavior.rs @@ -6,7 +6,7 @@ use super::{ cond, pkg_playerbots_personality, pkg_playerbots_rotation, PlayerbotsBot, PlayerbotsRotation, ROLE_HEALER, }; -use crate::{game_spell, game_spell_effect, game_world_entity}; +use crate::tables::{game_spell, game_spell_effect, game_world_entity}; use spacetimedb::ReducerContext; const ROTATION_LIMIT: usize = 12; @@ -26,7 +26,7 @@ fn node(action: Action, reason: Reason, priority: i32, objective: u64) -> Action } fn wounded_member( - member: &crate::group::PartyMemberFacts, + member: &crate::actor::PartyMemberFacts, partition: (u32, u64), heal_at_pct: u8, ) -> Option<(u32, u32, u64)> { @@ -70,15 +70,15 @@ enum CastPreparation { /// completion leeway instead of consuming it before the cast starts. fn cast_preparation( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, spell_id: u32, target_guid: u64, ) -> CastPreparation { match crate::actor::cast_readiness(ctx, me.guid, spell_id, target_guid) { - Err(refusal) if refusal.kind == crate::spell::CastRefusalKind::OutOfRange => { + Err(refusal) if refusal.kind == crate::actor::CastRefusalKind::OutOfRange => { return CastPreparation::MoveForRange; } - Err(refusal) if refusal.kind == crate::spell::CastRefusalKind::NoLineOfSight => { + Err(refusal) if refusal.kind == crate::actor::CastRefusalKind::NoLineOfSight => { return CastPreparation::MoveForLineOfSight; } Err(_) => return CastPreparation::Refused, @@ -110,7 +110,7 @@ fn cast_preparation( fn cast_node( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, cast: CastAction, reason: Reason, position_reason: Reason, @@ -118,7 +118,7 @@ fn cast_node( objective: u64, ) -> ActionNode { let mut candidate = node(Action::Cast(cast), reason, priority, objective); - if crate::spell::pending_cast(ctx, me.guid) + if crate::actor::pending_cast(ctx, me.guid) .is_some_and(|pending| pending.spell_id == cast.spell && pending.target_guid == cast.target) { return candidate; @@ -188,13 +188,13 @@ fn buff_missing( spell_id: u32, caster_level: u8, ) -> Result { - match crate::spell::buff_status(ctx, target_guid, spell_id, caster_level) { - crate::spell::BuffStatus::Missing => Ok(true), - crate::spell::BuffStatus::Satisfied => Ok(false), - crate::spell::BuffStatus::Unavailable(crate::spell::BuffUnavailableReason::AuraLimit) => { + match crate::actor::buff_status(ctx, target_guid, spell_id, caster_level) { + crate::actor::BuffStatus::Missing => Ok(true), + crate::actor::BuffStatus::Satisfied => Ok(false), + crate::actor::BuffStatus::Unavailable(crate::actor::BuffUnavailableReason::AuraLimit) => { Err(RoleReadError::BuffAuraLimit) } - crate::spell::BuffStatus::Unavailable(crate::spell::BuffUnavailableReason::Family) => { + crate::actor::BuffStatus::Unavailable(crate::actor::BuffUnavailableReason::Family) => { Err(RoleReadError::BuffFamilyUnavailable) } } @@ -202,7 +202,7 @@ fn buff_missing( fn buff_target( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, row: &PlayerbotsRotation, retained: Option, @@ -216,7 +216,7 @@ fn buff_target( let Some(party) = party else { return Ok(buff_missing(ctx, me.guid, row.spell_id, me.level as u8)?.then_some(me.guid)); }; - let eligible = |unit: &crate::group::PartyUnitFacts| { + let eligible = |unit: &crate::actor::PartyUnitFacts| { !unit.dead && (unit.map_id, unit.instance_id) == (me.map_id, me.instance_id) }; if let Some(retained) = retained { @@ -248,7 +248,7 @@ fn buff_target( fn maintenance( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, in_combat: bool, objective: u64, @@ -305,11 +305,11 @@ fn periodic_heal_only(ctx: &ReducerContext, spell: u32) -> bool { effects.len() <= 3 && effects .iter() - .any(|effect| effect.kind == crate::spell::A_PERIODIC_HEAL) + .any(|effect| effect.kind == crate::actor::A_PERIODIC_HEAL) && !effects.iter().any(|effect| { matches!( effect.kind, - crate::spell::E_HEAL | crate::spell::E_HEAL_MAX_HEALTH + crate::actor::E_HEAL | crate::actor::E_HEAL_MAX_HEALTH ) }) } @@ -317,7 +317,7 @@ fn periodic_heal_only(ctx: &ReducerContext, spell: u32) -> bool { fn healing( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, personality_heal_at: u8, objective: u64, @@ -399,7 +399,7 @@ pub(super) fn combat_buff(ctx: &ReducerContext, spell: u32) -> bool { spell.cast_time_ms == 0 && spell.power_type == lyracore_shared::packing::power_type::RAGE && !spell.is_negative - && spell.cast_flags & crate::spell::SPELL_ATTR_CHANNELED == 0 + && spell.cast_flags & crate::actor::SPELL_ATTR_CHANNELED == 0 }) } @@ -409,12 +409,12 @@ pub(super) fn queues_swing(ctx: &ReducerContext, spell: u32) -> bool { .by_spell() .filter(spell) .take(3) - .any(|effect| effect.kind == crate::spell::E_NEXT_SWING) + .any(|effect| effect.kind == crate::actor::E_NEXT_SWING) } fn preserves_queued_swing_resource( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, spell: u32, ) -> bool { if me.next_swing_spell == 0 { @@ -448,7 +448,7 @@ pub(super) struct Fight { pub(super) fn combat( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, fight: Fight, objective: u64, reason: Reason, @@ -538,7 +538,7 @@ pub(super) struct SupportContext<'a> { pub(super) fn support( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, context: SupportContext<'_>, objective: u64, fallback: &ActionNode, @@ -603,10 +603,10 @@ pub(super) fn support( mod tests { use super::*; - fn member(guid: u64, health: u32, max_health: u32) -> crate::group::PartyMemberFacts { - crate::group::PartyMemberFacts { + fn member(guid: u64, health: u32, max_health: u32) -> crate::actor::PartyMemberFacts { + crate::actor::PartyMemberFacts { character_guid: guid, - unit: Some(crate::group::PartyUnitFacts { + unit: Some(crate::actor::PartyUnitFacts { map_id: 0, instance_id: 0, x: 0.0, diff --git a/playerbots/src/companion.rs b/playerbots/src/companion.rs index 1bea004..1b70ddc 100644 --- a/playerbots/src/companion.rs +++ b/playerbots/src/companion.rs @@ -10,14 +10,14 @@ pub use super::class_behavior::RoleReadError; pub(super) struct Party { pub group_id: u64, pub leader_guid: u64, - pub leader: Option, - pub members: Vec, - pub enemies: Vec, + pub leader: Option, + pub members: Vec, + pub enemies: Vec, pub fight_constraint: Option, } impl Party { - pub fn leader_partition(&self) -> Option { + pub fn leader_partition(&self) -> Option { self.members .iter() .find(|member| member.character_guid == self.leader_guid) @@ -26,7 +26,7 @@ impl Party { member .unit .as_ref() - .map(|unit| crate::group::PartyPartitionFacts { + .map(|unit| crate::actor::PartyPartitionFacts { map_id: unit.map_id, instance_id: unit.instance_id, locator_revision: 0, @@ -47,7 +47,7 @@ impl Party { pub(super) fn human_led_party( ctx: &ReducerContext, character_guid: u64, -) -> Result, crate::group::PartyFactsUnavailable> { +) -> Result, crate::actor::PartyFactsUnavailable> { party(ctx, character_guid, true) } @@ -55,8 +55,8 @@ pub(super) fn party( ctx: &ReducerContext, character_guid: u64, require_human_leader: bool, -) -> Result, crate::group::PartyFactsUnavailable> { - let Some(facts) = crate::group::party_facts(ctx, character_guid)? else { +) -> Result, crate::actor::PartyFactsUnavailable> { + let Some(facts) = crate::actor::party_facts(ctx, character_guid)? else { return Ok(None); }; if require_human_leader @@ -85,7 +85,7 @@ pub(super) fn party( })) } -fn distance_sq(me: &crate::WorldEntity, unit: &crate::group::PartyUnitFacts) -> f32 { +fn distance_sq(me: &crate::tables::WorldEntity, unit: &crate::actor::PartyUnitFacts) -> f32 { (me.x - unit.x).powi(2) + (me.y - unit.y).powi(2) + (me.z - unit.z).powi(2) } @@ -98,7 +98,7 @@ fn node(action: Action, reason: Reason, priority: i32, objective: u64) -> Action fn follow( party: &Party, member_guid: Option, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, objective: u64, ) -> ActionNode { let Some((member_guid, member)) = member_guid.and_then(|guid| { @@ -126,11 +126,11 @@ fn follow( fn fight_target<'a>( party: &'a Party, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, role: u8, retained: Option, -) -> Option<&'a crate::group::PartyEnemyFacts> { - let eligible = |enemy: &&crate::group::PartyEnemyFacts| { +) -> Option<&'a crate::actor::PartyEnemyFacts> { + let eligible = |enemy: &&crate::actor::PartyEnemyFacts| { enemy.control.is_none() && (enemy.map_id, enemy.instance_id) == (me.map_id, me.instance_id) && enemy.health > 0 @@ -174,7 +174,7 @@ pub(super) struct CompanionSelection { pub(super) fn strategy( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: &Party, follow_member_guid: Option, survival_permits_healing: bool, diff --git a/playerbots/src/companion_acceptance_fixture.rs b/playerbots/src/companion_acceptance_fixture.rs index 9f84be6..7cb8c49 100644 --- a/playerbots/src/companion_acceptance_fixture.rs +++ b/playerbots/src/companion_acceptance_fixture.rs @@ -7,9 +7,9 @@ //! orders, combat, recovery, AreaTriggers, and Transfer remain on their ordinary owners. use super::{pkg_playerbots_bot, pkg_playerbots_provisioning, Controller}; -use crate::nav::game_nav_chunk; -use crate::package_fixture::top_threat_target; -use crate::{ +use crate::package::fixture::top_threat_target; +use crate::tables::game_nav_chunk; +use crate::tables::{ game_area_trigger, game_areatrigger_teleport, game_aura, game_character, game_character_quest, game_character_shard, game_creature_move_schedule, game_creature_quest, game_creature_spawn, game_group, game_group_member, game_group_member_partition, game_group_roster_revision, @@ -287,7 +287,7 @@ crate::game_hook!(on_cast_resolved, fn playerbots_companion_acceptance_observe_r }); crate::game_hook!(on_cast_finished, fn playerbots_companion_acceptance_observe_finished_cast(ctx, payload) { - if !matches!(&payload.outcome, crate::spell::CastFinish::Resolved) { + if !matches!(&payload.outcome, crate::actor::CastFinish::Resolved) { return; } let now = ctx.timestamp.to_micros_since_unix_epoch(); @@ -475,7 +475,7 @@ fn plan(ctx: &ReducerContext) -> Result { } fn wound_without_moving(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - let mut entity = crate::helpers::live_entity(ctx, guid)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; if entity.dead { return Err(format!( "companion acceptance cannot wound dead Character {guid}" @@ -496,7 +496,7 @@ fn wound_without_moving(ctx: &ReducerContext, guid: u64) -> Result<(), String> { let health = (entity.max_health.saturating_mul(10) / 100).max(1); entity.health = health; ctx.db.game_world_entity().guid().update(entity); - let wounded = crate::helpers::live_entity(ctx, guid)?; + let wounded = crate::actor::live_entity(ctx, guid)?; if wounded.health != health || ( wounded.map_id, @@ -525,7 +525,7 @@ fn clear_fortitude_if_present(ctx: &ReducerContext, guid: u64) -> Result<(), Str .any(|aura| aura.spell_id == 1243) }; if has_fortitude() { - crate::spell::do_cancel_aura(ctx, guid, 1243)?; + crate::package::fixture::do_cancel_aura(ctx, guid, 1243)?; } if has_fortitude() { return Err(format!( @@ -562,21 +562,23 @@ fn declare_exit_route(ctx: &ReducerContext) -> Result<(), String> { { return Err("companion acceptance refuses an existing Deadmines exit route".to_string()); } - ctx.db.game_area_trigger().insert(crate::GameAreaTrigger { - id: EXIT_TRIGGER, - map_id: 36, - x: EXIT_SOURCE.0, - y: EXIT_SOURCE.1, - z: EXIT_SOURCE.2, - radius: 6.0, - box_length: 0.0, - box_width: 0.0, - box_height: 0.0, - box_yaw: 0.0, - }); + ctx.db + .game_area_trigger() + .insert(crate::tables::GameAreaTrigger { + id: EXIT_TRIGGER, + map_id: 36, + x: EXIT_SOURCE.0, + y: EXIT_SOURCE.1, + z: EXIT_SOURCE.2, + radius: 6.0, + box_length: 0.0, + box_width: 0.0, + box_height: 0.0, + box_yaw: 0.0, + }); ctx.db .game_areatrigger_teleport() - .insert(crate::AreatriggerTeleport { + .insert(crate::tables::AreatriggerTeleport { trigger_id: EXIT_TRIGGER, target_map: 0, x: EXIT_LANDING.0, @@ -588,8 +590,11 @@ fn declare_exit_route(ctx: &ReducerContext) -> Result<(), String> { Ok(()) } -fn realm_partition(character_guid: u64, membership_revision: u64) -> crate::GroupMemberPartition { - crate::GroupMemberPartition { +fn realm_partition( + character_guid: u64, + membership_revision: u64, +) -> crate::tables::GroupMemberPartition { + crate::tables::GroupMemberPartition { character_guid, group_id: GROUP, membership_revision, @@ -597,12 +602,12 @@ fn realm_partition(character_guid: u64, membership_revision: u64) -> crate::Grou map_id: 0, instance_id: 0, locator_revision: 1, - state: crate::PartyPartitionState::Known, + state: crate::tables::PartyPartitionState::Known, } } -fn realm_locator(character_guid: u64, now: i64) -> crate::CharacterShard { - crate::CharacterShard { +fn realm_locator(character_guid: u64, now: i64) -> crate::tables::CharacterShard { + crate::tables::CharacterShard { character_guid, map_id: 0, instance_id: 0, @@ -628,7 +633,7 @@ pub fn playerbots_companion_acceptance_realm_stage( mage_one_guid: u64, mage_two_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let guids = [ leader_guid, warrior_guid, @@ -672,7 +677,7 @@ pub fn playerbots_companion_acceptance_realm_stage( { return Err("companion acceptance Realm party requires fresh authority rows".to_string()); } - ctx.db.game_group().insert(crate::Group { + ctx.db.game_group().insert(crate::tables::Group { group_id: GROUP, leader_guid, loot_method: 0, @@ -686,7 +691,7 @@ pub fn playerbots_companion_acceptance_realm_stage( let locators = ctx.db.game_character_shard(); let now = ctx.timestamp.to_micros_since_unix_epoch(); for (guid, membership_revision) in guids.into_iter().zip(MEMBER_IDS) { - members.insert(crate::GroupMember { + members.insert(crate::tables::GroupMember { id: membership_revision, group_id: GROUP, character_guid: guid, @@ -698,7 +703,7 @@ pub fn playerbots_companion_acceptance_realm_stage( } ctx.db .game_group_roster_revision() - .insert(crate::GroupRosterRevision { + .insert(crate::tables::GroupRosterRevision { group_id: GROUP, revision: 2, active: true, @@ -711,7 +716,7 @@ pub fn playerbots_companion_acceptance_realm_stage( pub fn playerbots_companion_acceptance_destination_stage( ctx: &ReducerContext, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if ctx.db.game_group().group_id().find(GROUP).is_some() || ctx .db @@ -737,7 +742,7 @@ pub fn playerbots_companion_acceptance_destination_stage( pub fn playerbots_companion_acceptance_accept_retained_quest( ctx: &ReducerContext, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let plan = plan(ctx)?; if plan.begun_micros != 0 { return Err("companion acceptance refuses Quest setup after begin".to_string()); @@ -768,28 +773,32 @@ pub fn playerbots_companion_acceptance_accept_retained_quest( template.src_item = 0; template.src_item_count = 0; ctx.db.game_quest_template().insert(template); - ctx.db.game_quest_objective().insert(crate::QuestObjective { - id: RETAINED_OBJECTIVE, - quest_entry: RETAINED_QUEST, - obj_index: 0, - kind: crate::quest::objective_kind::KILL_CREATURE, - target_entry: 5_098_003, - required_count: 2, - }); - for role in [ - crate::quest::quest_role::START, - crate::quest::quest_role::END, - ] { - ctx.db.game_creature_quest().insert(crate::CreatureQuest { - id: 0, - creature_entry: INTERACTION_GIVER_ENTRY, + ctx.db + .game_quest_objective() + .insert(crate::tables::QuestObjective { + id: RETAINED_OBJECTIVE, quest_entry: RETAINED_QUEST, - role, + obj_index: 0, + kind: crate::actor::objective_kind::KILL_CREATURE, + target_entry: 5_098_003, + required_count: 2, }); + for role in [ + crate::actor::quest_role::START, + crate::actor::quest_role::END, + ] { + ctx.db + .game_creature_quest() + .insert(crate::tables::CreatureQuest { + id: 0, + creature_entry: INTERACTION_GIVER_ENTRY, + quest_entry: RETAINED_QUEST, + role, + }); } let giver = (0xF130u64 << 48) | (u64::from(INTERACTION_GIVER_ENTRY) << 24) | 1; - let bot = crate::helpers::live_entity(ctx, plan.warrior_guid)?; - let mut giver_body = crate::helpers::live_entity(ctx, giver)?; + let bot = crate::actor::live_entity(ctx, plan.warrior_guid)?; + let mut giver_body = crate::actor::live_entity(ctx, giver)?; giver_body.map_id = bot.map_id; giver_body.instance_id = bot.instance_id; giver_body.x = bot.x + 1.0; @@ -834,8 +843,8 @@ fn verify_bot(ctx: &ReducerContext, guid: u64, class: u8, role: u8) -> Result<() fn retain_staged_positions(ctx: &ReducerContext, guids: [u64; 5]) -> Result<(), String> { for guid in guids { - let body = crate::helpers::live_entity(ctx, guid)?; - let mut character = crate::helpers::character_by_guid(ctx, guid) + let body = crate::actor::live_entity(ctx, guid)?; + let mut character = crate::actor::character_by_guid(ctx, guid) .ok_or("companion acceptance Character missing")?; character.map_id = body.map_id; character.pending_instance_id = body.instance_id; @@ -849,8 +858,8 @@ fn retain_staged_positions(ctx: &ReducerContext, guids: [u64; 5]) -> Result<(), } fn stage_repair_wall(ctx: &ReducerContext, priest_guid: u64, mage_guid: u64) -> Result<(), String> { - let priest = crate::helpers::live_entity(ctx, priest_guid)?; - let mut mage = crate::helpers::live_entity(ctx, mage_guid)?; + let priest = crate::actor::live_entity(ctx, priest_guid)?; + let mut mage = crate::actor::live_entity(ctx, mage_guid)?; mage.x = priest.x + 100.0; mage.y = priest.y; mage.z = priest.z; @@ -916,7 +925,7 @@ fn stage_repair_wall(ctx: &ReducerContext, priest_guid: u64, mage_guid: u64) -> ); } ctx.db.game_nav_chunk().key().update(chunk); - if crate::nav::has_los( + if crate::actor::movement::has_los( ctx, priest.map_id, priest.instance_id, @@ -940,7 +949,7 @@ pub fn playerbots_companion_acceptance_stage( mage_two_guid: u64, reward_quest: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if ctx.db.pkg_playerbots_companion_acceptance().count() != 0 || ctx.db.pkg_playerbots_companion_fault().count() != 0 || ctx.db.pkg_playerbots_companion_combat_receipt().count() != 0 @@ -1022,7 +1031,7 @@ pub fn playerbots_companion_acceptance_stage( let mut enemies = Vec::with_capacity(3); for entry in 5_098_001u32..=5_098_003 { let guid = (0xF130u64 << 48) | (u64::from(entry) << 24) | 1; - let enemy = crate::helpers::live_entity(ctx, guid)?; + let enemy = crate::actor::live_entity(ctx, guid)?; if enemy.entry != entry { return Err("companion acceptance pull target identity changed".to_string()); } @@ -1094,7 +1103,7 @@ pub fn playerbots_companion_acceptance_stage( /// Start the route only after ordinary provisioning and one real Quest reward are durable. #[reducer] pub fn playerbots_companion_acceptance_begin(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut plan = plan(ctx)?; if plan.begun_micros != 0 { return Err("companion acceptance route already began".to_string()); @@ -1195,7 +1204,7 @@ pub fn playerbots_companion_acceptance_apply_fault( ctx: &ReducerContext, fault_id: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let plan = plan(ctx)?; if plan.begun_micros == 0 { return Err("companion acceptance route has not begun".to_string()); @@ -1230,19 +1239,23 @@ pub fn playerbots_companion_acceptance_apply_fault( CONTROL_SPELL, )?, FAULT_DEATH => { - let victim = crate::helpers::live_entity(ctx, fault.subject_guid)?; - let damage = crate::combat::final_damage(ctx, fault.subject_guid, victim.health); - let outcome = crate::combat::apply_hit( + let victim = crate::actor::live_entity(ctx, fault.subject_guid)?; + let damage = + crate::package::fixture::final_damage(ctx, fault.subject_guid, victim.health); + let outcome = crate::package::fixture::apply_hit( ctx, fault.target_guid, fault.subject_guid, damage, - crate::combat::Hit::weapon(crate::combat::HitSource::MainHand, false), + crate::package::fixture::Hit::weapon( + crate::package::fixture::HitSource::MainHand, + false, + ), ); if !outcome.killed { return Err("declared companion fault was not lethal".to_string()); } - let dead = crate::helpers::live_entity(ctx, fault.subject_guid)?; + let dead = crate::actor::live_entity(ctx, fault.subject_guid)?; if !dead.dead || dead.health != 0 || dead.player_flags & lyracore_shared::constants::player_flags::GHOST != 0 diff --git a/playerbots/src/controller_transition_fixture.rs b/playerbots/src/controller_transition_fixture.rs index add398e..bdb4fa7 100644 --- a/playerbots/src/controller_transition_fixture.rs +++ b/playerbots/src/controller_transition_fixture.rs @@ -1,7 +1,7 @@ #![cfg(feature = "debug_reducers")] use super::{pkg_playerbots_bot, pkg_playerbots_runner, Controller}; -use crate::transfer::game_bot_transfer_intent; +use crate::tables::game_bot_transfer_intent; use spacetimedb::{reducer, ReducerContext}; /// Model a populated Legacy roster row on the current schema without using the retired selector. @@ -11,7 +11,7 @@ pub fn playerbots_controller_transition_fixture_stage_legacy( character_guid: u64, stale_in_transit: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -35,7 +35,7 @@ pub fn playerbots_controller_transition_fixture_stage_legacy( .filter(character_guid) .next() .is_some() - || crate::helpers::character_by_guid(ctx, character_guid).is_none() + || crate::actor::character_by_guid(ctx, character_guid).is_none() { return Err("controller transition fixture refuses Transfer-owned state".to_string()); } @@ -43,9 +43,9 @@ pub fn playerbots_controller_transition_fixture_stage_legacy( if stale_in_transit { // The preceding event transfer removed the live body before leaving this compatibility // marker. Use the Core logout path to model that persisted boundary. - let body = crate::helpers::live_entity(ctx, character_guid) + crate::actor::live_entity(ctx, character_guid) .map_err(|_| "controller transition fixture requires a live source body")?; - crate::world::remove_live_character(ctx, body); + crate::package::fixture::remove_live_character(ctx, character_guid)?; super::goals::record_legacy_transfer( ctx, character_guid, diff --git a/playerbots/src/decision.rs b/playerbots/src/decision.rs index 9b459be..cc0d92e 100644 --- a/playerbots/src/decision.rs +++ b/playerbots/src/decision.rs @@ -1,6 +1,6 @@ //! Pure, bounded selection from typed Strategies and explicit facts. -use crate::nav::LEG_MAX_EXPANSIONS; +use crate::actor::movement::LEG_MAX_EXPANSIONS; #[derive(spacetimedb::SpacetimeType, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Action { diff --git a/playerbots/src/fixture.rs b/playerbots/src/fixture.rs index 04e677f..03aaafc 100644 --- a/playerbots/src/fixture.rs +++ b/playerbots/src/fixture.rs @@ -10,17 +10,17 @@ use super::{ PlayerbotsBot, }; use super::{pkg_playerbots_personality, pkg_playerbots_rotation}; -use crate::nav::game_nav_chunk; -use crate::spell::stacking::{game_spell_group, SpellGroup}; -use crate::{ +use crate::tables::game_nav_chunk; +use crate::tables::{ game_account, game_character, game_creature_spawn, game_creature_template, game_group, game_group_member, game_quest_objective, game_quest_template, }; -use crate::{ +use crate::tables::{ game_aura, game_creature_spline, game_melee_attack, game_spell, game_spell_cooldown, game_spell_effect, game_threat, game_world_entity, }; -use crate::{game_group_member_partition, game_group_roster_revision}; +use crate::tables::{game_group_member_partition, game_group_roster_revision}; +use crate::tables::{game_spell_group, SpellGroup}; use spacetimedb::{reducer, ReducerContext, Table, TimeDuration}; const HEAL: u32 = 5_090_100; @@ -42,8 +42,8 @@ pub fn playerbots_fixture_class_stage( grouped: bool, fighting: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; playerbots_fixture_prepare(ctx)?; playerbots_fixture_runner_stage(ctx, guid, false)?; let mut strike = ctx @@ -70,9 +70,9 @@ pub fn playerbots_fixture_class_stage( effect.id = 78u64 << 2; effect.spell_id = 78; effect.effect_index = 0; - effect.kind = crate::spell::E_NEXT_SWING; + effect.kind = crate::actor::E_NEXT_SWING; effect.base_points = 11; - effect.target = crate::spell::T_TARGET_ENEMY; + effect.target = crate::actor::T_TARGET_ENEMY; ctx.db.game_spell_effect().id().delete(effect.id); ctx.db.game_spell_effect().insert(effect); let mut shout = ctx @@ -84,7 +84,7 @@ pub fn playerbots_fixture_class_stage( shout.cost = 100; shout.duration_ms = 120_000; ctx.db.game_spell().spell_id().update(shout); - let mut me = crate::helpers::live_entity(ctx, guid)?; + let mut me = crate::actor::live_entity(ctx, guid)?; me.level = 1; me.health = me.max_health; me.max_power = 1000; @@ -106,7 +106,7 @@ pub fn playerbots_fixture_class_stage( super::class::MAGE => vec![133, 168], _ => return Err("unsupported fixture class".into()), } { - crate::spell::learn_spell(ctx, guid, spacetimedb::Identity::ZERO, spell); + crate::package::fixture::learn_spell(ctx, guid, spacetimedb::Identity::ZERO, spell); } let leader = ctx .db @@ -122,7 +122,7 @@ pub fn playerbots_fixture_class_stage( let members = vec![leader_guid, guid]; let partitions = fixture_group_partitions(ctx, ROLES_GROUP, &members)?; let raid_slots = vec![0; members.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, ROLES_GROUP, leader_guid, @@ -130,7 +130,7 @@ pub fn playerbots_fixture_class_stage( 2, 0, members, - crate::SessionActor { + crate::package::fixture::SessionActor { guid: leader_guid, ownership: None, }, @@ -141,7 +141,7 @@ pub fn playerbots_fixture_class_stage( )?; } let enemy = companion_creature(ctx, 5_098_090, 1203.0, 1200.0, 50.0, None)?; - let mut target = crate::helpers::live_entity(ctx, enemy)?; + let mut target = crate::actor::live_entity(ctx, enemy)?; target.level = 1; target.health = 10_000; target.max_health = 10_000; @@ -163,7 +163,7 @@ pub fn playerbots_fixture_class_stage( .update(profile); } if fighting { - crate::combat::enter_combat(ctx, guid); + crate::package::fixture::enter_combat(ctx, guid); playerbots_fixture_runner_damage_and_park(ctx, guid, enemy, 1)?; if grouped { playerbots_fixture_roles_select(ctx, leader_guid, enemy)?; @@ -175,11 +175,11 @@ pub fn playerbots_fixture_class_stage( /// Hold a queued strike until the next explicit decision has been inspected. #[reducer] pub fn playerbots_fixture_class_hold_queue(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let enemy = (0xF130u64 << 48) | (5_098_090u64 << 24) | 1; crate::actor::request_attack(ctx, guid, enemy).map_err(|refusal| format!("{refusal:?}"))?; - crate::combat::enter_combat(ctx, guid); - let mut me = crate::helpers::live_entity(ctx, guid)?; + crate::package::fixture::enter_combat(ctx, guid); + let mut me = crate::actor::live_entity(ctx, guid)?; me.next_swing_spell = 78; me.power = 200; me.base_attack_time_ms = 60_000; @@ -198,10 +198,10 @@ pub fn playerbots_fixture_class_hold_queue(ctx: &ReducerContext, guid: u64) -> R /// Observe the buff's cost inside its transaction, before regeneration or incoming hits can run. #[reducer] pub fn playerbots_fixture_class_shout_pass(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let before = crate::helpers::live_entity(ctx, guid)?.power; + crate::package::require_operator(ctx)?; + let before = crate::actor::live_entity(ctx, guid)?.power; playerbots_fixture_runner_pass_once(ctx, guid)?; - let after = crate::helpers::live_entity(ctx, guid)?.power; + let after = crate::actor::live_entity(ctx, guid)?.power; if before.checked_sub(100) != Some(after) || !ctx .db @@ -226,12 +226,12 @@ pub fn playerbots_fixture_orders_account( leader_guid: u64, account_id: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if ctx.db.game_account().id().find(account_id).is_none() { return Err("order fixture account missing".to_string()); } - let mut leader = crate::helpers::character_by_guid(ctx, leader_guid) - .ok_or("order fixture leader missing")?; + let mut leader = + crate::actor::character_by_guid(ctx, leader_guid).ok_or("order fixture leader missing")?; let party_members: Vec<_> = ctx .db .game_group_member() @@ -246,7 +246,7 @@ pub fn playerbots_fixture_orders_account( .into_iter() .filter(|member| member.character_guid != leader_guid) .map(|member| { - crate::helpers::character_by_guid(ctx, member.character_guid) + crate::actor::character_by_guid(ctx, member.character_guid) .ok_or("order fixture party Character missing") }) .collect::, _>>()? @@ -271,7 +271,7 @@ pub fn playerbots_fixture_orders_party( leader_guid: u64, mode: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; orders_party( ctx, warrior_guid, @@ -279,7 +279,7 @@ pub fn playerbots_fixture_orders_party( mage_guid, leader_guid, mode, - crate::SessionActor { + crate::package::fixture::SessionActor { guid: leader_guid, ownership: None, }, @@ -296,9 +296,9 @@ pub fn playerbots_fixture_orders_party_as( mage_guid: u64, leader_guid: u64, mode: u8, - request_actor: crate::SessionActor, + request_actor: crate::package::fixture::SessionActor, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if ctx .db .game_group() @@ -326,7 +326,7 @@ fn orders_party( mage_guid: u64, leader_guid: u64, mode: u8, - request_actor: crate::SessionActor, + request_actor: crate::package::fixture::SessionActor, ) -> Result<(), String> { let (leader, members) = match mode { 0 => ( @@ -369,14 +369,14 @@ fn orders_party( let included = members.contains(&partition.character_guid); if partition.member_active && !included { partition.member_active = false; - partition.state = crate::group::PartyPartitionState::Unknown; + partition.state = crate::actor::PartyPartitionState::Unknown; } else if !partition.member_active && included { next_membership = next_membership .checked_add(1) .ok_or("order fixture membership revision exhausted")?; partition.membership_revision = next_membership; partition.member_active = true; - partition.state = crate::group::PartyPartitionState::Known; + partition.state = crate::actor::PartyPartitionState::Known; } } if members.iter().any(|guid| { @@ -395,7 +395,7 @@ fn orders_party( .map(|(_, guid)| guid) .collect(); let raid_slots = vec![0; effective_members.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, ROLES_GROUP, leader, @@ -421,7 +421,7 @@ pub fn playerbots_fixture_orders_names( leader_guid: u64, namespace: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let names = match namespace { 0 => ["Cmdsrcwar", "Cmdsrcpri", "Cmdsrcmag", "Cmdsrclead"], 1 => ["Cmdtgtwar", "Cmdtgtpri", "Cmdtgtmag", "Cmdtgtlead"], @@ -460,10 +460,9 @@ pub fn playerbots_fixture_orders_names( if !(is_bot || is_fixture_leader) { return Err("order fixture Character is outside the private role party".to_string()); } - let character = crate::helpers::character_by_guid(ctx, *guid) + let character = crate::actor::character_by_guid(ctx, *guid) .ok_or_else(|| "order fixture Character missing".to_string())?; - if crate::helpers::character_by_name(ctx, &name).is_some_and(|held| held.guid != *guid) - { + if crate::actor::character_by_name(ctx, &name).is_some_and(|held| held.guid != *guid) { return Err(format!("order fixture name '{name}' is already in use")); } Ok((character, name)) @@ -482,7 +481,7 @@ pub fn playerbots_fixture_orders_remove_member_body( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if !ctx .db .game_group_member() @@ -492,7 +491,7 @@ pub fn playerbots_fixture_orders_remove_member_body( { return Err("order fixture Character is outside the private role party".to_string()); } - crate::package_fixture::remove_live_character(ctx, character_guid) + crate::package::fixture::remove_live_character(ctx, character_guid) } /// Change only one private role-fixture enemy to exercise exact-target death and partition Gates. @@ -502,7 +501,7 @@ pub fn playerbots_fixture_orders_target_state( target_guid: u64, mode: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let entities = ctx.db.game_world_entity(); let mut target = entities .guid() @@ -519,8 +518,8 @@ pub fn playerbots_fixture_orders_target_state( .guid() .find(target_guid) .ok_or("order fixture target spawn missing")?; - spawn.respawn_at = crate::creatures::timer_never(ctx); - spawn.despawn_at = crate::creatures::timer_never(ctx); + spawn.respawn_at = crate::package::fixture::timer_never(ctx); + spawn.despawn_at = crate::package::fixture::timer_never(ctx); ctx.db.game_creature_spawn().guid().update(spawn); } 1 => target.map_id = target.map_id.saturating_add(1), @@ -542,7 +541,7 @@ pub fn playerbots_fixture_orders_restore_target( ctx: &ReducerContext, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let entry = ((target_guid >> 24) & 0x00ff_ffff) as u32; let (x, y) = match entry { 5_098_001 => (1208.0, 1200.0), @@ -573,8 +572,8 @@ pub fn playerbots_fixture_orders_partition( map_id: u32, instance_id: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - if crate::helpers::character_by_guid(ctx, character_guid).is_none() { + crate::package::require_operator(ctx)?; + if crate::actor::character_by_guid(ctx, character_guid).is_none() { return Err("order fixture Character missing".to_string()); } let entities = ctx.db.game_world_entity(); @@ -598,7 +597,7 @@ pub fn playerbots_fixture_orders_drive( bot_guid: u64, run_once: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; crate::actor::fixture_command_drive(ctx, intent_id, claim_token)?; let _applied = ctx .db @@ -623,15 +622,15 @@ fn fixture_group_partitions( ctx: &ReducerContext, group_id: u64, members: &[u64], -) -> Result, String> { +) -> Result, String> { let partitions = ctx.db.game_group_member_partition(); members .iter() .enumerate() .map(|(index, character_guid)| { partitions.character_guid().delete(*character_guid); - let entity = crate::helpers::live_entity(ctx, *character_guid)?; - Ok(crate::group::GroupMemberPartition { + let entity = crate::actor::live_entity(ctx, *character_guid)?; + Ok(crate::tables::GroupMemberPartition { character_guid: *character_guid, group_id, membership_revision: index as u64 + 1, @@ -639,7 +638,7 @@ fn fixture_group_partitions( map_id: entity.map_id, instance_id: entity.instance_id, locator_revision: 1, - state: crate::group::PartyPartitionState::Known, + state: crate::actor::PartyPartitionState::Known, }) }) .collect() @@ -647,7 +646,7 @@ fn fixture_group_partitions( #[reducer] pub fn playerbots_fixture_prepare(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bots: Vec = ctx.db.pkg_playerbots_bot().iter().collect(); let bot_guids: Vec<_> = bots.iter().map(|bot| bot.character_guid).collect(); for mut bot in bots { @@ -656,7 +655,7 @@ pub fn playerbots_fixture_prepare(ctx: &ReducerContext) -> Result<(), String> { .game_creature_spline() .guid() .delete(bot.character_guid); - let mut entity = crate::helpers::live_entity(ctx, bot.character_guid)?; + let mut entity = crate::actor::live_entity(ctx, bot.character_guid)?; entity.health = 1; entity.x = 1200.0; entity.y = 1200.0; @@ -689,12 +688,12 @@ pub fn playerbots_fixture_prepare(ctx: &ReducerContext) -> Result<(), String> { { effect.spell_id = HEAL; effect.id = ((HEAL as u64) << 2) | effect.effect_index as u64; - effect.target = crate::spell::T_TARGET_ALLY; + effect.target = crate::actor::T_TARGET_ALLY; ctx.db.game_spell_effect().id().delete(effect.id); ctx.db.game_spell_effect().insert(effect); } for guid in bot_guids { - crate::spell::learn_spell(ctx, guid, spacetimedb::Identity::ZERO, HEAL); + crate::package::fixture::learn_spell(ctx, guid, spacetimedb::Identity::ZERO, HEAL); } Ok(()) } @@ -705,14 +704,14 @@ pub fn playerbots_fixture_cast( caster: u64, target: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let _ = super::actions::cast(ctx, caster, HEAL, target); Ok(()) } #[reducer] pub fn playerbots_fixture_resume(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut bot = ctx .db .pkg_playerbots_bot() @@ -740,17 +739,17 @@ pub fn playerbots_fixture_cancel( guid: u64, expired: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - if let Some(cast) = crate::spell::pending_cast(ctx, guid) { + crate::package::require_operator(ctx)?; + if let Some(cast) = crate::actor::pending_cast(ctx, guid) { if expired { - crate::spell::expire_cast_attempt( + crate::actor::expire_cast_attempt( ctx, guid, cast.scheduled_id, ctx.timestamp.to_micros_since_unix_epoch(), ); } else { - crate::spell::cancel_cast_attempt(ctx, guid, cast.scheduled_id); + crate::actor::cancel_cast_attempt(ctx, guid, cast.scheduled_id); } } Ok(()) @@ -758,8 +757,8 @@ pub fn playerbots_fixture_cancel( #[reducer] pub fn playerbots_fixture_position(ctx: &ReducerContext, guid: u64, x: f32) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let mut entity = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; entity.x = x; let (gx, gy) = lyracore_shared::spatial::grid_cell(entity.x, entity.y); entity.grid_x = gx; @@ -778,7 +777,7 @@ fn companion_unit( y: f32, health_pct: u32, ) -> Result<(), String> { - let mut entity = crate::helpers::live_entity(ctx, guid)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; entity.x = x; entity.y = y; entity.z = 50.0; @@ -817,7 +816,7 @@ fn companion_creature( template.aggro_range = 0; ctx.db.game_creature_template().entry().delete(entry); let template = ctx.db.game_creature_template().insert(template); - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid, entry, map_id: 0, @@ -833,10 +832,10 @@ fn companion_creature( }; ctx.db.game_creature_spawn().guid().delete(guid); let spawn = ctx.db.game_creature_spawn().insert(spawn); - crate::creatures::despawn_creature_entity(ctx, guid); - crate::creatures::insert_creature_entity( + crate::package::fixture::despawn_creature_entity(ctx, guid); + crate::package::fixture::insert_creature_entity( ctx, - crate::creatures::build_creature_entity(&spawn, &template, 0, 0), + crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0), ); Ok(guid) } @@ -849,7 +848,7 @@ pub fn playerbots_fixture_companion_stage( leader_guid: u64, ally_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut companion = ctx .db .pkg_playerbots_bot() @@ -902,7 +901,7 @@ pub fn playerbots_fixture_companion_stage( let members = vec![leader_guid, companion_guid, ally_guid]; let partitions = fixture_group_partitions(ctx, COMPANION_GROUP, &members)?; let raid_slots = vec![0; members.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, COMPANION_GROUP, leader_guid, @@ -910,7 +909,7 @@ pub fn playerbots_fixture_companion_stage( 2, 0, members, - crate::SessionActor { + crate::package::fixture::SessionActor { guid: leader_guid, ownership: None, }, @@ -934,9 +933,9 @@ pub fn playerbots_fixture_raid_mirror( members: Vec, raid_slots: Vec, fillers: Vec, - request_actor: crate::SessionActor, + request_actor: crate::package::fixture::SessionActor, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if ![COMPANION_GROUP, ROLES_GROUP].contains(&group_id) { return Err("raid fixture names a Group outside the private fixtures".to_string()); } @@ -984,7 +983,7 @@ pub fn playerbots_fixture_raid_mirror( companion_unit(ctx, *guid, 1240.0 + index as f32, 1230.0, 100)?; } let partitions = fixture_group_partitions(ctx, group_id, &members)?; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, group_id, leader_guid, @@ -1010,7 +1009,7 @@ pub fn playerbots_fixture_roles_stage( mage_guid: u64, leader_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; for (guid, class, role) in [ (warrior_guid, super::class::WARRIOR, super::ROLE_TANK), (priest_guid, super::class::PRIEST, super::ROLE_HEALER), @@ -1028,7 +1027,7 @@ pub fn playerbots_fixture_roles_stage( "role fixture bot {guid} has the wrong class or role" )); } - let entity = crate::helpers::live_entity(ctx, guid)?; + let entity = crate::actor::live_entity(ctx, guid)?; if entity.level != 5 { return Err(format!( "role fixture supports level 5, got {}", @@ -1067,7 +1066,7 @@ pub fn playerbots_fixture_roles_stage( (5_098_003, 1212.0, 1197.0), ] { let guid = companion_creature(ctx, entry, x, y, 50.0, None)?; - let mut entity = crate::helpers::live_entity(ctx, guid)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; entity.max_health = 1_000; entity.health = 1_000; ctx.db.game_world_entity().guid().update(entity); @@ -1075,7 +1074,7 @@ pub fn playerbots_fixture_roles_stage( let members = vec![leader_guid, warrior_guid, priest_guid, mage_guid]; let partitions = fixture_group_partitions(ctx, ROLES_GROUP, &members)?; let raid_slots = vec![0; members.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, ROLES_GROUP, leader_guid, @@ -1083,7 +1082,7 @@ pub fn playerbots_fixture_roles_stage( 2, 0, members, - crate::SessionActor { + crate::package::fixture::SessionActor { guid: leader_guid, ownership: None, }, @@ -1106,8 +1105,8 @@ pub fn playerbots_fixture_roles_select( leader_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let mut leader = crate::helpers::live_entity(ctx, leader_guid)?; + crate::package::require_operator(ctx)?; + let mut leader = crate::actor::live_entity(ctx, leader_guid)?; leader.target_guid = target_guid; ctx.db.game_world_entity().guid().update(leader); Ok(()) @@ -1120,14 +1119,14 @@ pub fn playerbots_fixture_roles_move( x: f32, y: f32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; companion_unit(ctx, guid, x, y, 100) } /// Narrow Taunt's curated header for the short-range movement repair verification. #[reducer] pub fn playerbots_fixture_roles_short_taunt(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut taunt = ctx .db .game_spell() @@ -1156,7 +1155,7 @@ pub fn playerbots_fixture_roles_overflow( guid: u64, kind: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -1187,7 +1186,7 @@ pub fn playerbots_fixture_roles_overflow( .checked_add(TimeDuration::from_micros(3_600_000_000)) .unwrap_or(now); for index in 0..65 { - ctx.db.game_aura().insert(crate::Aura { + ctx.db.game_aura().insert(crate::tables::Aura { id: 0, target_guid: guid, caster_guid: guid, @@ -1233,7 +1232,7 @@ pub fn playerbots_fixture_roles_overflow( } 3 => { for index in 0..25 { - ctx.db.game_threat().insert(crate::ThreatEntry { + ctx.db.game_threat().insert(crate::tables::ThreatEntry { id: 0, creature_guid: 5_098_800 + index, source_guid: guid, @@ -1244,14 +1243,14 @@ pub fn playerbots_fixture_roles_overflow( 4 | 5 => { let enemy = companion_creature(ctx, OVERFLOW_ENEMY_ENTRY, 1216.0, 1200.0, 50.0, None)?; let sources = if kind == 4 { 80 } else { 81 }; - ctx.db.game_threat().insert(crate::ThreatEntry { + ctx.db.game_threat().insert(crate::tables::ThreatEntry { id: 0, creature_guid: enemy, source_guid: guid, threat: 1, }); for index in 1..sources { - ctx.db.game_threat().insert(crate::ThreatEntry { + ctx.db.game_threat().insert(crate::tables::ThreatEntry { id: 0, creature_guid: enemy, source_guid: 5_098_900 + index, @@ -1266,7 +1265,7 @@ pub fn playerbots_fixture_roles_overflow( #[reducer] pub fn playerbots_fixture_roles_clear_overflow(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let rotations = ctx.db.pkg_playerbots_rotation(); for row in rotations .iter() @@ -1303,7 +1302,7 @@ pub fn playerbots_fixture_roles_clear_overflow(ctx: &ReducerContext) -> Result<( for row in threats.by_creature().filter(&enemy).collect::>() { threats.id().delete(row.id); } - crate::creatures::despawn_creature_entity(ctx, enemy); + crate::package::fixture::despawn_creature_entity(ctx, enemy); ctx.db.game_creature_spawn().guid().delete(enemy); ctx.db .game_creature_template() @@ -1318,7 +1317,7 @@ pub fn playerbots_fixture_roles_engage( leader_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; crate::actor::request_attack(ctx, leader_guid, target_guid) .map(|_| ()) .map_err(|refusal| format!("leader attack refused: {refusal:?}")) @@ -1330,8 +1329,8 @@ pub fn playerbots_fixture_roles_enemy_engage( enemy_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::combat::request_attack(ctx, enemy_guid, target_guid) + crate::package::require_operator(ctx)?; + crate::actor::request_attack(ctx, enemy_guid, target_guid) .map(|_| ()) .map_err(|refusal| format!("enemy attack refused: {refusal:?}")) } @@ -1343,7 +1342,7 @@ pub fn playerbots_fixture_roles_record_only_attack( guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -1356,7 +1355,7 @@ pub fn playerbots_fixture_roles_record_only_attack( } let melee = ctx.db.game_melee_attack(); melee.attacker_guid().delete(guid); - melee.insert(crate::combat::MeleeAttack { + melee.insert(crate::tables::MeleeAttack { attacker_guid: guid, target_guid, last_swing_ms: 4_242, @@ -1377,8 +1376,8 @@ pub fn playerbots_fixture_roles_despawn( ctx: &ReducerContext, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::creatures::despawn_creature_entity(ctx, target_guid); + crate::package::require_operator(ctx)?; + crate::package::fixture::despawn_creature_entity(ctx, target_guid); Ok(()) } @@ -1390,12 +1389,18 @@ pub fn playerbots_fixture_roles_control( target_guid: u64, spell_id: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if !(50_020..=50_023).contains(&spell_id) { return Err("role fixture control spell must be 50020 through 50023".to_string()); } - let caster = crate::helpers::live_entity(ctx, caster_guid)?; - crate::spell::cast_triggered(ctx, caster_guid, spell_id, caster.level as u8, target_guid) + let caster = crate::actor::live_entity(ctx, caster_guid)?; + crate::package::fixture::cast_triggered( + ctx, + caster_guid, + spell_id, + caster.level as u8, + target_guid, + ) } /// Begin a real cast bar for the seeded control verification. This catches the interval before the @@ -1407,7 +1412,7 @@ pub fn playerbots_fixture_roles_begin_control( target_guid: u64, spell_id: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if !(50_020..=50_023).contains(&spell_id) { return Err("role fixture control spell must be 50020 through 50023".to_string()); } @@ -1419,7 +1424,7 @@ pub fn playerbots_fixture_roles_begin_control( .ok_or("control fixture spell missing")?; spell.cast_time_ms = 5_000; ctx.db.game_spell().spell_id().update(spell); - crate::spell::learn_spell(ctx, caster_guid, spacetimedb::Identity::ZERO, spell_id); + crate::package::fixture::learn_spell(ctx, caster_guid, spacetimedb::Identity::ZERO, spell_id); crate::actor::request_cast(ctx, caster_guid, spell_id, target_guid) .map(|_| ()) .map_err(|refusal| format!("control cast refused: {refusal:?}")) @@ -1431,13 +1436,13 @@ pub fn playerbots_fixture_roles_clear_control( caster_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - if let Some(cast) = crate::spell::pending_cast(ctx, caster_guid) { - crate::spell::cancel_cast_attempt(ctx, caster_guid, cast.scheduled_id); + crate::package::require_operator(ctx)?; + if let Some(cast) = crate::actor::pending_cast(ctx, caster_guid) { + crate::actor::cancel_cast_attempt(ctx, caster_guid, cast.scheduled_id); } let auras = ctx.db.game_aura(); for aura in auras.by_target().filter(&target_guid).collect::>() { - if aura.eff_kind == crate::spell::A_CONTROL { + if aura.eff_kind == crate::actor::A_CONTROL { auras.id().delete(aura.id); } } @@ -1452,9 +1457,15 @@ pub fn playerbots_fixture_roles_stronger_fortitude( caster_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let caster = crate::helpers::live_entity(ctx, caster_guid)?; - crate::spell::cast_triggered(ctx, caster_guid, 21_562, caster.level as u8, target_guid) + crate::package::require_operator(ctx)?; + let caster = crate::actor::live_entity(ctx, caster_guid)?; + crate::package::fixture::cast_triggered( + ctx, + caster_guid, + 21_562, + caster.level as u8, + target_guid, + ) } /// Add the level-valid Warrior trainer offering used by the companion route and arm the normal @@ -1464,7 +1475,7 @@ pub fn playerbots_fixture_roles_prepare_taunt( ctx: &ReducerContext, warrior_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -1475,7 +1486,7 @@ pub fn playerbots_fixture_roles_prepare_taunt( if (bot.class, bot.role) != (super::class::WARRIOR, super::ROLE_TANK) { return Err("Taunt fixture requires a Warrior tank".to_string()); } - let warrior = crate::helpers::live_entity(ctx, warrior_guid)?; + let warrior = crate::actor::live_entity(ctx, warrior_guid)?; let taunt = ctx .db .game_spell() @@ -1504,7 +1515,7 @@ pub fn playerbots_fixture_roles_prepare_fortitude( ctx: &ReducerContext, priest_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -1529,7 +1540,7 @@ pub fn playerbots_fixture_roles_prepare_fortitude( /// Give the private role fixture enough cast time to interrupt Fortitude with an urgent heal. #[reducer] pub fn playerbots_fixture_roles_slow_fortitude(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut fortitude = ctx .db .game_spell() @@ -1548,7 +1559,7 @@ pub fn playerbots_fixture_roles_priest_mana( ctx: &ReducerContext, priest_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; set_fixture_priest_power(ctx, priest_guid, 100) } @@ -1567,7 +1578,7 @@ fn set_fixture_priest_power( if bot.class != super::class::PRIEST { return Err("mana fixture requires a Priest".to_string()); } - let mut priest = crate::helpers::live_entity(ctx, priest_guid)?; + let mut priest = crate::actor::live_entity(ctx, priest_guid)?; priest.max_power = 100; priest.power = power; ctx.db.game_world_entity().guid().update(priest); @@ -1580,8 +1591,8 @@ pub fn playerbots_fixture_roles_cancel_renew( ctx: &ReducerContext, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::spell::do_cancel_aura(ctx, target_guid, 139) + crate::package::require_operator(ctx)?; + crate::package::fixture::do_cancel_aura(ctx, target_guid, 139) } #[reducer] @@ -1591,10 +1602,10 @@ pub fn playerbots_fixture_companion_move( x: f32, y: f32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; companion_unit(ctx, guid, x, y, 100)?; if let Some(mut spawn) = ctx.db.game_creature_spawn().guid().find(guid) { - let entity = crate::helpers::live_entity(ctx, guid)?; + let entity = crate::actor::live_entity(ctx, guid)?; spawn.x = entity.x; spawn.y = entity.y; spawn.z = entity.z; @@ -1610,14 +1621,14 @@ pub fn playerbots_fixture_companion_health( guid: u64, health_pct: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let entity = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let entity = crate::actor::live_entity(ctx, guid)?; companion_unit(ctx, guid, entity.x, entity.y, health_pct) } #[reducer] pub fn playerbots_fixture_companion_remove_group(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; ctx.db.game_group().group_id().delete(COMPANION_GROUP); Ok(()) } @@ -1627,8 +1638,8 @@ pub fn playerbots_fixture_companion_forget_heal( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - use crate::game_player_spell; + crate::package::require_operator(ctx)?; + use crate::tables::game_player_spell; if let Some(spell) = ctx .db .game_player_spell() @@ -1646,7 +1657,7 @@ pub fn playerbots_fixture_companion_lesser_heal_target( ctx: &ReducerContext, target: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let effects = ctx.db.game_spell_effect(); let mut effect = effects .id() @@ -1662,7 +1673,7 @@ pub fn playerbots_fixture_companion_extra_lesser_heal_effect( ctx: &ReducerContext, present: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let effects = ctx.db.game_spell_effect(); let id = (2050u64 << 2) | 1; effects.id().delete(id); @@ -1683,7 +1694,7 @@ pub fn playerbots_fixture_companion_mixed_heals( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut channel = ctx .db .game_spell() @@ -1692,7 +1703,7 @@ pub fn playerbots_fixture_companion_mixed_heals( .ok_or("fixture heal missing")?; channel.spell_id = CHANNEL_HEAL; channel.name = "Unsupported channel heal".to_string(); - channel.cast_flags = crate::spell::SPELL_ATTR_CHANNELED; + channel.cast_flags = crate::actor::SPELL_ATTR_CHANNELED; ctx.db.game_spell().spell_id().delete(CHANNEL_HEAL); ctx.db.game_spell().insert(channel); let effects = ctx.db.game_spell_effect(); @@ -1704,7 +1715,7 @@ pub fn playerbots_fixture_companion_mixed_heals( effect.id = (u64::from(CHANNEL_HEAL) << 2) | u64::from(effect.effect_index); effects.insert(effect); } - crate::spell::learn_spell( + crate::package::fixture::learn_spell( ctx, character_guid, spacetimedb::Identity::ZERO, @@ -1749,7 +1760,7 @@ pub fn playerbots_fixture_companion_client_cast( caster_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::package_fixture::client_cast(ctx, caster_guid, HEAL, target_guid) + crate::package::fixture::client_cast(ctx, caster_guid, HEAL, target_guid) } #[reducer] @@ -1758,9 +1769,9 @@ pub fn playerbots_fixture_companion_triggered_cast( caster_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let caster = crate::helpers::live_entity(ctx, caster_guid)?; - crate::spell::cast_triggered(ctx, caster_guid, HEAL, caster.level as u8, target_guid) + crate::package::require_operator(ctx)?; + let caster = crate::actor::live_entity(ctx, caster_guid)?; + crate::package::fixture::cast_triggered(ctx, caster_guid, HEAL, caster.level as u8, target_guid) } #[reducer] @@ -1768,8 +1779,8 @@ pub fn playerbots_fixture_companion_creature_cast( ctx: &ReducerContext, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let target = crate::helpers::live_entity(ctx, target_guid)?; + crate::package::require_operator(ctx)?; + let target = crate::actor::live_entity(ctx, target_guid)?; let guid = companion_creature( ctx, 5_090_301, @@ -1778,16 +1789,16 @@ pub fn playerbots_fixture_companion_creature_cast( target.z, Some(target.faction_template), )?; - crate::spell::start_creature_spell( + crate::package::fixture::start_creature_spell( ctx, - crate::spell::CreatureSpellStart { + crate::package::fixture::CreatureSpellStart { caster_guid: guid, caster_level: target.level as u8, spell_id: HEAL, - mode: crate::spell::CreatureSpellStartMode::Direct, - target: crate::spell::CreatureSpellTarget::Unit(target_guid), + mode: crate::package::fixture::CreatureSpellStartMode::Direct, + target: crate::package::fixture::CreatureSpellTarget::Unit(target_guid), interrupt_previous: false, - admission: crate::spell::CreatureSpellCasterAdmission::Living, + admission: crate::package::fixture::CreatureSpellCasterAdmission::Living, }, ) } @@ -1798,10 +1809,10 @@ pub fn playerbots_fixture_companion_wall( caster_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; companion_unit(ctx, caster_guid, 1200.0, 1200.0, 100)?; companion_unit(ctx, target_guid, 1210.0, 1200.0, 25)?; - let caster = crate::helpers::live_entity(ctx, caster_guid)?; + let caster = crate::actor::live_entity(ctx, caster_guid)?; let wall_x = 1205.0; let cx = lyracore_shared::terrain::cell_index(wall_x).ok_or("wall off grid")?; let cy = lyracore_shared::terrain::cell_index(caster.y).ok_or("wall off grid")?; @@ -1820,7 +1831,7 @@ pub fn playerbots_fixture_companion_wall( lyracore_shared::nav::obs_raise(&mut obs, caster.z, obs_sub, sub_y, caster.z + 4.0); } ctx.db.game_nav_chunk().key().delete(key); - ctx.db.game_nav_chunk().insert(crate::nav::NavChunk { + ctx.db.game_nav_chunk().insert(crate::tables::NavChunk { key, map_id: caster.map_id, cell_x: cx, @@ -1830,7 +1841,7 @@ pub fn playerbots_fixture_companion_wall( obs, }); } - if crate::nav::has_los( + if crate::actor::movement::has_los( ctx, caster.map_id, caster.instance_id, @@ -1849,7 +1860,7 @@ pub fn playerbots_fixture_roles_buff_wall( caster_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_companion_wall(ctx, caster_guid, target_guid)?; companion_unit(ctx, target_guid, 1210.0, 1200.0, 100)?; runner_park_for(ctx, caster_guid) @@ -1857,11 +1868,11 @@ pub fn playerbots_fixture_roles_buff_wall( #[reducer] pub fn playerbots_fixture_blocked_quest(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let mut me = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let mut me = crate::actor::live_entity(ctx, guid)?; me.health = me.max_health; ctx.db.game_world_entity().guid().update(me); - let me = crate::helpers::live_entity(ctx, guid)?; + let me = crate::actor::live_entity(ctx, guid)?; let entry = 5_090_101; let target_guid = (0xF130u64 << 48) | ((entry as u64) << 24) | 1; let mut template = ctx @@ -1873,7 +1884,7 @@ pub fn playerbots_fixture_blocked_quest(ctx: &ReducerContext, guid: u64) -> Resu template.entry = entry; ctx.db.game_creature_template().entry().delete(entry); let template = ctx.db.game_creature_template().insert(template); - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid: target_guid, entry, map_id: me.map_id, @@ -1889,44 +1900,48 @@ pub fn playerbots_fixture_blocked_quest(ctx: &ReducerContext, guid: u64) -> Resu }; ctx.db.game_creature_spawn().guid().delete(target_guid); let spawn = ctx.db.game_creature_spawn().insert(spawn); - crate::creatures::despawn_creature_entity(ctx, target_guid); - crate::creatures::insert_creature_entity( + crate::package::fixture::despawn_creature_entity(ctx, target_guid); + crate::package::fixture::insert_creature_entity( ctx, - crate::creatures::build_creature_entity(&spawn, &template, 0, 0), + crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0), ); ctx.db.game_quest_template().entry().delete(50909); - ctx.db.game_quest_template().insert(crate::QuestTemplate { - entry: 50909, - min_level: 1, - quest_level: 1, - title: "Blocked fixture hunt".to_string(), - reward_money: 0, - reward_xp: 0, - prev_quest_id: 0, - required_races: 0, - required_classes: 0, - zone_or_sort: 0, - rew_rep_faction_1: 0, - rew_rep_value_1: 0, - rew_rep_faction_2: 0, - rew_rep_value_2: 0, - src_item: 0, - src_item_count: 0, - repeatable: false, - next_quest_id: 0, - limit_time: 0, - reward_money_max_level: 0, - quest_type: 0, - }); + ctx.db + .game_quest_template() + .insert(crate::tables::QuestTemplate { + entry: 50909, + min_level: 1, + quest_level: 1, + title: "Blocked fixture hunt".to_string(), + reward_money: 0, + reward_xp: 0, + prev_quest_id: 0, + required_races: 0, + required_classes: 0, + zone_or_sort: 0, + rew_rep_faction_1: 0, + rew_rep_value_1: 0, + rew_rep_faction_2: 0, + rew_rep_value_2: 0, + src_item: 0, + src_item_count: 0, + repeatable: false, + next_quest_id: 0, + limit_time: 0, + reward_money_max_level: 0, + quest_type: 0, + }); ctx.db.game_quest_objective().id().delete(5_090_101); - ctx.db.game_quest_objective().insert(crate::QuestObjective { - id: 5_090_101, - quest_entry: 50909, - obj_index: 0, - kind: crate::quest::objective_kind::KILL_CREATURE, - target_entry: entry, - required_count: 2, - }); + ctx.db + .game_quest_objective() + .insert(crate::tables::QuestObjective { + id: 5_090_101, + quest_entry: 50909, + obj_index: 0, + kind: crate::actor::objective_kind::KILL_CREATURE, + target_entry: entry, + required_count: 2, + }); crate::actor::stage_quest(ctx, guid, 50909)?; let cx = lyracore_shared::terrain::cell_index(me.x).ok_or("fixture off grid")?; let cy = lyracore_shared::terrain::cell_index(me.y).ok_or("fixture off grid")?; @@ -1934,7 +1949,7 @@ pub fn playerbots_fixture_blocked_quest(ctx: &ReducerContext, guid: u64) -> Resu for y in cy.saturating_sub(1)..=cy.saturating_add(1).min(1023) { let key = lyracore_shared::terrain::cell_key(me.map_id, x, y); ctx.db.game_nav_chunk().key().delete(key); - ctx.db.game_nav_chunk().insert(crate::nav::NavChunk { + ctx.db.game_nav_chunk().insert(crate::tables::NavChunk { key, map_id: me.map_id, cell_x: x, @@ -1945,7 +1960,7 @@ pub fn playerbots_fixture_blocked_quest(ctx: &ReducerContext, guid: u64) -> Resu }); } } - if crate::nav::has_los( + if crate::actor::movement::has_los( ctx, me.map_id, me.instance_id, @@ -1959,8 +1974,8 @@ pub fn playerbots_fixture_blocked_quest(ctx: &ReducerContext, guid: u64) -> Resu #[reducer] pub fn playerbots_fixture_move(ctx: &ReducerContext, guid: u64, x: f32) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let me = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let me = crate::actor::live_entity(ctx, guid)?; super::goals::walk_toward(ctx, &me, (x, me.y, me.z), 0.0, true); Ok(()) } @@ -1972,7 +1987,7 @@ pub fn playerbots_fixture_cast_mode( target: u64, channel: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut spell = ctx .db .game_spell() @@ -1981,7 +1996,7 @@ pub fn playerbots_fixture_cast_mode( .ok_or("fixture heal missing")?; spell.cast_time_ms = 0; spell.cast_flags = if channel { - crate::spell::SPELL_ATTR_CHANNELED + crate::actor::SPELL_ATTR_CHANNELED } else { 0 }; @@ -1997,11 +2012,11 @@ pub fn playerbots_fixture_cancel_handle( scheduled_id: u64, deadline: i64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if deadline == 0 { - crate::spell::cancel_cast_attempt(ctx, guid, scheduled_id); + crate::actor::cancel_cast_attempt(ctx, guid, scheduled_id); } else { - crate::spell::expire_cast_attempt(ctx, guid, scheduled_id, deadline); + crate::actor::expire_cast_attempt(ctx, guid, scheduled_id, deadline); } Ok(()) } @@ -2012,15 +2027,15 @@ pub fn playerbots_fixture_attack( guid: u64, target: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let _ = super::actions::attack(ctx, guid, target); Ok(()) } #[reducer] pub fn playerbots_fixture_partial_route(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let me = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let me = crate::actor::live_entity(ctx, guid)?; let wall_x = me.x + 10.0; let cx = lyracore_shared::terrain::cell_index(wall_x).ok_or("fixture off grid")?; let cy = lyracore_shared::terrain::cell_index(me.y).ok_or("fixture off grid")?; @@ -2033,7 +2048,7 @@ pub fn playerbots_fixture_partial_route(ctx: &ReducerContext, guid: u64) -> Resu lyracore_shared::nav::walk_set(&mut walk, nx, ny, false); } ctx.db.game_nav_chunk().key().delete(key); - ctx.db.game_nav_chunk().insert(crate::nav::NavChunk { + ctx.db.game_nav_chunk().insert(crate::tables::NavChunk { key, map_id: me.map_id, cell_x: cx, @@ -2052,8 +2067,8 @@ pub fn playerbots_fixture_runner_second_attacker( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let me = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let me = crate::actor::live_entity(ctx, guid)?; let entry = 5_090_101; let first_guid = fixture_giver(); let second_guid = first_guid + 1; @@ -2063,11 +2078,11 @@ pub fn playerbots_fixture_runner_second_attacker( .entry() .find(entry) .ok_or("blocked quest creature template missing")?; - let mut first = crate::helpers::live_entity(ctx, first_guid)?; + let mut first = crate::actor::live_entity(ctx, first_guid)?; first.health = 1_000; first.max_health = 1_000; ctx.db.game_world_entity().guid().update(first); - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid: second_guid, entry, map_id: me.map_id, @@ -2083,8 +2098,8 @@ pub fn playerbots_fixture_runner_second_attacker( }; ctx.db.game_creature_spawn().guid().delete(second_guid); let spawn = ctx.db.game_creature_spawn().insert(spawn); - crate::creatures::despawn_creature_entity(ctx, second_guid); - let mut second = crate::creatures::build_creature_entity(&spawn, &template, 0, 0); + crate::package::fixture::despawn_creature_entity(ctx, second_guid); + let mut second = crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0); second.health = 1_000; second.max_health = 1_000; ctx.db.game_world_entity().insert(second); @@ -2098,17 +2113,17 @@ pub fn playerbots_fixture_runner_kill_creature( killer: u64, target: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let health = crate::helpers::live_entity(ctx, target)?.health; - let (amount, _) = crate::combat::fold_incoming_damage(ctx, killer, target, health); - let damage = crate::combat::final_damage(ctx, target, amount); - crate::loot::tag::clear(ctx, target); - let outcome = crate::combat::apply_hit( + crate::package::require_operator(ctx)?; + let health = crate::actor::live_entity(ctx, target)?.health; + let (amount, _) = crate::package::fixture::fold_incoming_damage(ctx, killer, target, health); + let damage = crate::package::fixture::final_damage(ctx, target, amount); + crate::package::fixture::clear_loot_tag(ctx, target); + let outcome = crate::package::fixture::apply_hit( ctx, killer, target, damage, - crate::combat::Hit::weapon(crate::combat::HitSource::MainHand, false), + crate::package::fixture::Hit::weapon(crate::package::fixture::HitSource::MainHand, false), ); if !outcome.killed { return Err("fixture creature survived lethal damage".to_string()); @@ -2132,7 +2147,7 @@ pub fn playerbots_fixture_interaction_stage( guid: u64, source_item: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_blocked_quest(ctx, guid)?; let mut bot = ctx .db @@ -2143,7 +2158,7 @@ pub fn playerbots_fixture_interaction_stage( .ok_or("bot missing")?; bot.next_think_micros = i64::MAX; ctx.db.pkg_playerbots_bot().id().update(bot); - use crate::{ + use crate::tables::{ game_character_quest, game_creature_quest, game_item_instance, game_item_template, game_quest_reward_choice, game_quest_reward_item, }; @@ -2216,28 +2231,32 @@ pub fn playerbots_fixture_interaction_stage( .id() .delete(GUARANTEED as u64); ctx.db.game_quest_reward_choice().id().delete(CHOICE as u64); - ctx.db.game_quest_objective().insert(crate::QuestObjective { - id: COLLECT as u64, - quest_entry: QUEST, - obj_index: 0, - kind: crate::quest::objective_kind::COLLECT_ITEM, - target_entry: COLLECT, - required_count: 1, - }); - for role in [ - crate::quest::quest_role::START, - crate::quest::quest_role::END, - ] { - ctx.db.game_creature_quest().insert(crate::CreatureQuest { - id: 0, - creature_entry: 5_090_101, + ctx.db + .game_quest_objective() + .insert(crate::tables::QuestObjective { + id: COLLECT as u64, quest_entry: QUEST, - role, + obj_index: 0, + kind: crate::actor::objective_kind::COLLECT_ITEM, + target_entry: COLLECT, + required_count: 1, }); + for role in [ + crate::actor::quest_role::START, + crate::actor::quest_role::END, + ] { + ctx.db + .game_creature_quest() + .insert(crate::tables::CreatureQuest { + id: 0, + creature_entry: 5_090_101, + quest_entry: QUEST, + role, + }); } ctx.db .game_quest_reward_item() - .insert(crate::QuestRewardItem { + .insert(crate::tables::QuestRewardItem { id: GUARANTEED as u64, quest_entry: QUEST, item_entry: GUARANTEED, @@ -2245,7 +2264,7 @@ pub fn playerbots_fixture_interaction_stage( }); ctx.db .game_quest_reward_choice() - .insert(crate::QuestRewardChoice { + .insert(crate::tables::QuestRewardChoice { id: CHOICE as u64, quest_entry: QUEST, choice_index: 0, @@ -2254,9 +2273,9 @@ pub fn playerbots_fixture_interaction_stage( }); if !source_item { crate::actor::stage_quest(ctx, guid, QUEST)?; - crate::items::grant_item(ctx, guid, COLLECT, 1)?; + crate::package::fixture::grant_item(ctx, guid, COLLECT, 1)?; } - crate::items::grant_item(ctx, guid, FILLER, if source_item { 16 } else { 15 })?; + crate::package::fixture::grant_item(ctx, guid, FILLER, if source_item { 16 } else { 15 })?; Ok(()) } @@ -2267,7 +2286,7 @@ pub fn playerbots_fixture_interact( accept: bool, choice: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if accept { let _ = super::actions::accept_quest(ctx, guid, fixture_giver(), QUEST); } else { @@ -2278,13 +2297,13 @@ pub fn playerbots_fixture_interact( #[reducer] pub fn playerbots_fixture_free_slot(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::items::remove_items(ctx, guid, FILLER, 1) + crate::package::require_operator(ctx)?; + crate::package::fixture::remove_items(ctx, guid, FILLER, 1) } #[reducer] pub fn playerbots_fixture_freeze(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut bot = ctx .db .pkg_playerbots_bot() @@ -2299,7 +2318,7 @@ pub fn playerbots_fixture_freeze(ctx: &ReducerContext, guid: u64) -> Result<(), #[reducer] pub fn playerbots_fixture_forget_credit(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::pkg_playerbots_goal; let mut goal = ctx .db @@ -2315,16 +2334,16 @@ pub fn playerbots_fixture_forget_credit(ctx: &ReducerContext, guid: u64) -> Resu #[reducer] pub fn playerbots_fixture_credit_kill(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let target = fixture_giver(); - let (amount, _) = crate::combat::fold_incoming_damage(ctx, guid, target, 10000); - let damage = crate::combat::final_damage(ctx, target, amount); - let outcome = crate::combat::apply_hit( + let (amount, _) = crate::package::fixture::fold_incoming_damage(ctx, guid, target, 10000); + let damage = crate::package::fixture::final_damage(ctx, target, amount); + let outcome = crate::package::fixture::apply_hit( ctx, guid, target, damage, - crate::combat::Hit::weapon(crate::combat::HitSource::MainHand, false), + crate::package::fixture::Hit::weapon(crate::package::fixture::HitSource::MainHand, false), ); if !outcome.killed { return Err("fixture hit was not lethal".to_string()); @@ -2338,7 +2357,7 @@ pub fn playerbots_fixture_runner_stage( guid: u64, healing: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut bot = ctx .db .pkg_playerbots_bot() @@ -2352,12 +2371,12 @@ pub fn playerbots_fixture_runner_stage( bot.home_map = 0; bot.next_think_micros = i64::MAX; let _ = crate::actor::stop_attack(ctx, guid); - use crate::game_threat; + use crate::tables::game_threat; let threats = ctx.db.game_threat(); for row in threats.by_source().filter(guid).collect::>() { threats.id().delete(row.id); } - let mut me = crate::helpers::live_entity(ctx, guid)?; + let mut me = crate::actor::live_entity(ctx, guid)?; me.health = if healing { me.max_health / 4 } else { @@ -2403,15 +2422,15 @@ pub fn playerbots_fixture_runner_damage( attacker: u64, damage: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let (amount, _) = crate::combat::fold_incoming_damage(ctx, attacker, guid, damage); - let damage = crate::combat::final_damage(ctx, guid, amount); - crate::combat::apply_hit( + crate::package::require_operator(ctx)?; + let (amount, _) = crate::package::fixture::fold_incoming_damage(ctx, attacker, guid, damage); + let damage = crate::package::fixture::final_damage(ctx, guid, amount); + crate::package::fixture::apply_hit( ctx, attacker, guid, damage, - crate::combat::Hit::weapon(crate::combat::HitSource::MainHand, false), + crate::package::fixture::Hit::weapon(crate::package::fixture::HitSource::MainHand, false), ); Ok(()) } @@ -2424,14 +2443,14 @@ pub fn playerbots_fixture_runner_damage_and_park( attacker: u64, damage: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_runner_damage(ctx, guid, attacker, damage)?; runner_park_for(ctx, guid) } #[reducer] pub fn playerbots_fixture_runner_due(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; for mut bot in ctx.db.pkg_playerbots_bot().iter().collect::>() { bot.next_think_micros = ctx.timestamp.to_micros_since_unix_epoch() - 2_000_000; ctx.db.pkg_playerbots_bot().id().update(bot); @@ -2441,7 +2460,7 @@ pub fn playerbots_fixture_runner_due(ctx: &ReducerContext) -> Result<(), String> #[reducer] pub fn playerbots_fixture_runner_pass(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; super::runner::pass(ctx); Ok(()) } @@ -2450,7 +2469,7 @@ pub fn playerbots_fixture_runner_pass(ctx: &ReducerContext) -> Result<(), String /// next step before the fixture inspects the result. #[reducer] pub fn playerbots_fixture_runner_pass_once(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; runner_due_for(ctx, guid)?; super::runner::pass(ctx); use super::pkg_playerbots_scheduler; @@ -2469,7 +2488,7 @@ pub fn playerbots_fixture_runner_pass_once(ctx: &ReducerContext, guid: u64) -> R /// Install the exact source-derived Smite facts used by the private Runner readiness checks. #[reducer] pub fn playerbots_fixture_runner_prepare_smite(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; super::quest_catalog_fixture::stage_loopback_smite(ctx) } @@ -2479,7 +2498,7 @@ pub fn playerbots_fixture_runner_cooldown_and_pass_once( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::actions::{self, ActionKind}; playerbots_fixture_companion_health(ctx, guid, 100)?; set_fixture_priest_power(ctx, guid, 100)?; @@ -2492,10 +2511,10 @@ pub fn playerbots_fixture_runner_cooldown_and_pass_once( if fortitude.cast_time_ms != 0 || fortitude.gcd_ms == 0 { return Err("source-derived spell 1243 is not an instant GCD spell".to_string()); } - crate::spell::learn_spell(ctx, guid, spacetimedb::Identity::ZERO, 1243); + crate::package::fixture::learn_spell(ctx, guid, spacetimedb::Identity::ZERO, 1243); if !matches!( crate::actor::request_cast(ctx, guid, 1243, guid), - Ok(crate::spell::CastStart::Resolved) + Ok(crate::actor::CastStart::Resolved) ) { return Err("fixture Priest could not resolve instant spell 1243".to_string()); } @@ -2516,7 +2535,7 @@ pub fn playerbots_fixture_runner_cooldown_and_pass_once( defense_movement(ctx, guid, DefenseMovementKind::Entity)?; playerbots_fixture_companion_health(ctx, guid, 25)?; playerbots_fixture_runner_pass_once(ctx, guid)?; - let body = crate::helpers::live_entity(ctx, guid)?; + let body = crate::actor::live_entity(ctx, guid)?; if !defense_movement_is_retained(ctx, guid, &expected_foreground, &expected_spline)? || u64::from(body.health) * 2 >= u64::from(body.max_health) || ctx @@ -2543,7 +2562,7 @@ pub fn playerbots_fixture_runner_insufficient_recovery_power_and_pass_once( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::actions::{self, ActionKind}; playerbots_fixture_companion_health(ctx, guid, 100)?; set_fixture_priest_power(ctx, guid, 100)?; @@ -2553,7 +2572,7 @@ pub fn playerbots_fixture_runner_insufficient_recovery_power_and_pass_once( playerbots_fixture_companion_health(ctx, guid, 25)?; set_fixture_priest_power(ctx, guid, 25)?; playerbots_fixture_runner_pass_once(ctx, guid)?; - let body = crate::helpers::live_entity(ctx, guid)?; + let body = crate::actor::live_entity(ctx, guid)?; if !defense_movement_is_retained(ctx, guid, &expected_foreground, &expected_spline)? || u64::from(body.health) * 2 >= u64::from(body.max_health) || body.power != 25 @@ -2575,7 +2594,7 @@ pub fn playerbots_fixture_runner_pending_defense_then_recovery( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::actions::{self, ActionKind, ActionOutcome}; use super::decision::{Action, CastAction, Reason}; use super::runner::Running; @@ -2593,7 +2612,7 @@ pub fn playerbots_fixture_runner_pending_defense_then_recovery( .foreground .as_ref() .ok_or("Defense cast foreground missing")?; - let pending = crate::spell::pending_cast(ctx, guid).ok_or("pending Defense cast missing")?; + let pending = crate::actor::pending_cast(ctx, guid).ok_or("pending Defense cast missing")?; if foreground.candidate.id.action != Action::Cast(CastAction { target: pending.target_guid, @@ -2602,15 +2621,6 @@ pub fn playerbots_fixture_runner_pending_defense_then_recovery( || foreground.candidate.id.reason != Reason::Defense || !matches!(&foreground.running, Running::Cast(handle) if handle == &pending) || state.chosen != Some(foreground.candidate) - || ctx - .db - .game_spell_cooldown() - .caster_guid() - .find(guid) - .is_some_and(|cooldown| { - cooldown.ready_at.to_micros_since_unix_epoch() - > ctx.timestamp.to_micros_since_unix_epoch() - }) { return Err("pending cast is not the Runner's exact Defense Smite".to_string()); } @@ -2624,9 +2634,28 @@ pub fn playerbots_fixture_runner_pending_defense_then_recovery( return Err("Defense cast action is not the exact pending Smite".to_string()); } let expected_foreground = foreground.clone(); + let mut cooldown = ctx + .db + .game_spell_cooldown() + .caster_guid() + .find(guid) + .ok_or("Defense Smite did not start its cooldown")?; + if cooldown.ready_at <= ctx.timestamp { + return Err("Defense Smite cooldown is not active".to_string()); + } + // Isolate power readiness while keeping the exact pending Defense cast. + cooldown.ready_at = ctx.timestamp; + ctx.db.game_spell_cooldown().caster_guid().update(cooldown); playerbots_fixture_companion_health(ctx, guid, 25)?; set_fixture_priest_power(ctx, guid, 25)?; + if !matches!( + crate::actor::cast_readiness(ctx, guid, 2050, guid), + Err(refusal) if refusal.kind == crate::actor::CastRefusalKind::InsufficientPower + ) { + return Err("Recovery readiness did not refuse insufficient power".to_string()); + } + playerbots_fixture_runner_pass_once(ctx, guid)?; let retained = ctx .db @@ -2638,12 +2667,12 @@ pub fn playerbots_fixture_runner_pending_defense_then_recovery( .foreground .as_ref() .ok_or("Defense cast foreground missing after insufficient-power pass")?; - let body = crate::helpers::live_entity(ctx, guid)?; + let body = crate::actor::live_entity(ctx, guid)?; let action = actions::observation(ctx, guid, ActionKind::Cast) .ok_or("retained Defense cast action missing")?; if !same_foreground(&expected_foreground, retained_foreground) || retained.chosen != Some(expected_foreground.candidate) - || crate::spell::pending_cast(ctx, guid).as_ref() != Some(&pending) + || crate::actor::pending_cast(ctx, guid).as_ref() != Some(&pending) || u64::from(body.health) * 2 >= u64::from(body.max_health) || body.power != 25 || action.spell_id != 585 @@ -2657,6 +2686,9 @@ pub fn playerbots_fixture_runner_pending_defense_then_recovery( )); } set_fixture_priest_power(ctx, guid, 100)?; + crate::actor::cast_readiness(ctx, guid, 2050, guid).map_err(|refusal| { + format!("Recovery remains unavailable after restoring power: {refusal}") + })?; playerbots_fixture_runner_start_and_retain_recovery(ctx, guid) } @@ -2666,7 +2698,7 @@ pub fn playerbots_fixture_runner_start_and_retain_recovery( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::actions::{self, ActionKind, ActionOutcome}; use super::decision::{Action, CastAction, Reason}; use super::runner::Running; @@ -2683,8 +2715,8 @@ pub fn playerbots_fixture_runner_start_and_retain_recovery( .foreground .as_ref() .ok_or("Recovery heal foreground missing")?; - let pending = crate::spell::pending_cast(ctx, guid).ok_or("pending Recovery heal missing")?; - let body = crate::helpers::live_entity(ctx, guid)?; + let pending = crate::actor::pending_cast(ctx, guid).ok_or("pending Recovery heal missing")?; + let body = crate::actor::live_entity(ctx, guid)?; if foreground.candidate.id.action != Action::Cast(CastAction { target: guid, @@ -2721,7 +2753,7 @@ pub fn playerbots_fixture_runner_start_and_retain_recovery( .ok_or("Recovery heal foreground missing after retained pass")?; if !same_foreground(&expected_foreground, retained_foreground) || retained.chosen != Some(expected_foreground.candidate) - || crate::spell::pending_cast(ctx, guid).as_ref() != Some(&pending) + || crate::actor::pending_cast(ctx, guid).as_ref() != Some(&pending) { return Err("second pass did not retain the exact Recovery heal".to_string()); } @@ -2752,18 +2784,17 @@ fn recovery_readiness_diagnostic(ctx: &ReducerContext, guid: u64) -> Result Result<(super::runner::Foreground, crate::CreatureSpline), String> { +) -> Result<(super::runner::Foreground, crate::tables::CreatureSpline), String> { use super::decision::{Action, MoveTarget, Reason}; use super::runner::Running; @@ -2806,7 +2837,7 @@ fn defense_movement( } _ => return Err("Runner is not retaining the expected Defense movement".to_string()), }; - if state.chosen != Some(foreground.candidate) || crate::spell::pending_cast(ctx, guid).is_some() + if state.chosen != Some(foreground.candidate) || crate::actor::pending_cast(ctx, guid).is_some() { return Err("Defense movement is not the Runner's sole active work".to_string()); } @@ -2815,9 +2846,7 @@ fn defense_movement( .game_creature_spline() .guid() .find(guid) - .ok_or_else(|| { - format!("Defense movement toward {target} is not active") - })?; + .ok_or_else(|| format!("Defense movement toward {target} is not active"))?; Ok((foreground, spline)) } @@ -2825,7 +2854,7 @@ fn defense_movement_is_retained( ctx: &ReducerContext, guid: u64, expected_foreground: &super::runner::Foreground, - expected_spline: &crate::CreatureSpline, + expected_spline: &crate::tables::CreatureSpline, ) -> Result { let state = ctx .db @@ -2839,14 +2868,14 @@ fn defense_movement_is_retained( let Some(spline) = ctx.db.game_creature_spline().guid().find(guid) else { return Ok(false); }; - let body = crate::helpers::live_entity(ctx, guid)?; + let body = crate::actor::live_entity(ctx, guid)?; let observed_current_body = matches!( &foreground.running, super::runner::Running::Movement(run) if run.from_x == body.x && run.from_y == body.y ); Ok(state.chosen == Some(expected_foreground.candidate) - && crate::spell::pending_cast(ctx, guid).is_none() + && crate::actor::pending_cast(ctx, guid).is_none() && same_foreground(expected_foreground, foreground) && same_spline(expected_spline, &spline) && observed_current_body) @@ -2869,7 +2898,10 @@ fn same_foreground(before: &super::runner::Foreground, after: &super::runner::Fo } } -fn same_spline(before: &crate::CreatureSpline, after: &crate::CreatureSpline) -> bool { +fn same_spline( + before: &crate::tables::CreatureSpline, + after: &crate::tables::CreatureSpline, +) -> bool { before.guid == after.guid && before.start_micros == after.start_micros && before.dur_ms == after.dur_ms @@ -2896,7 +2928,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_wait( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::quest_catalog::{ pkg_playerbots_quest_objective, CatalogObjectiveKind, ObjectiveExecutor, }; @@ -2939,7 +2971,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_wait( let _ = crate::actor::stop_attack(ctx, guid); ctx.db.game_creature_spline().guid().delete(guid); - let mut me = crate::helpers::live_entity(ctx, guid)?; + let mut me = crate::actor::live_entity(ctx, guid)?; me.map_id = destination.map_id; me.instance_id = destination.instance_id; me.x = destination.x; @@ -2970,7 +3002,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_wait( .by_map() .filter(&destination.map_id) .filter(|entity| { - crate::helpers::in_same_partition(entity, destination.map_id, destination.instance_id) + crate::actor::in_same_partition(entity, destination.map_id, destination.instance_id) && !entity.is_player() && entity.entry == target_entry && !entity.dead @@ -2981,7 +3013,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_wait( return Err("completed Quest fixture has no live target".to_string()); } for target in targets { - crate::creatures::despawn_creature_entity(ctx, target); + crate::package::fixture::despawn_creature_entity(ctx, target); } runner_park_for(ctx, guid) } @@ -2992,7 +3024,7 @@ pub fn playerbots_fixture_runner_expire_completed_quest_wait( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::runner::{ObjectiveKind, ObjectiveStage}; let rows = ctx.db.pkg_playerbots_runner(); @@ -3016,7 +3048,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_fight( guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::decision::{Action, Candidate, CandidateId, Reason}; use super::quest_catalog::{ pkg_playerbots_quest_objective, CatalogObjectiveKind, ObjectiveExecutor, @@ -3040,7 +3072,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_fight( { return Err("retained Quest does not own a creature objective".to_string()); } - let mut target = crate::helpers::live_entity(ctx, target_guid)?; + let mut target = crate::actor::live_entity(ctx, target_guid)?; if target.entry != retained.target.target_entry || target.dead { return Err("Quest Fight target differs from the retained source".to_string()); } @@ -3059,7 +3091,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_fight( .by_map() .filter(&target_map) .filter(|entity| { - crate::helpers::in_same_partition(entity, target_map, target_instance) + crate::actor::in_same_partition(entity, target_map, target_instance) && entity.guid != target_guid && entity.entry == retained.target.target_entry && !entity.dead @@ -3067,11 +3099,11 @@ pub fn playerbots_fixture_runner_stage_completed_quest_fight( .map(|entity| entity.guid) .collect(); for other_target in other_targets { - crate::creatures::despawn_creature_entity(ctx, other_target); + crate::package::fixture::despawn_creature_entity(ctx, other_target); } let _ = crate::actor::stop_attack(ctx, guid); ctx.db.game_creature_spline().guid().delete(guid); - let mut me = crate::helpers::live_entity(ctx, guid)?; + let mut me = crate::actor::live_entity(ctx, guid)?; me.map_id = target_map; me.instance_id = target_instance; me.x = target_x; @@ -3100,7 +3132,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_fight( x: target_x, y: target_y, z: target_z, - geometry_revision: crate::nav::coverage_generation(ctx, target_map), + geometry_revision: crate::actor::movement::coverage_generation(ctx, target_map), }; state.chosen = Some(candidate); state.candidate_order = vec![candidate]; @@ -3110,7 +3142,7 @@ pub fn playerbots_fixture_runner_stage_completed_quest_fight( work: Work::Fight(target_guid), reason: Reason::Quest, destination, - geometry: crate::nav::inputs(ctx, target_map), + geometry: crate::actor::movement::inputs(ctx, target_map), objective: candidate.id.objective, last_observed_micros: now, stalled_micros: 0, @@ -3136,7 +3168,7 @@ pub fn playerbots_fixture_runner_stage_recovery_capacity( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -3144,7 +3176,7 @@ pub fn playerbots_fixture_runner_stage_recovery_capacity( .filter(guid) .next() .ok_or("bot missing")?; - let me = crate::helpers::live_entity(ctx, guid)?; + let me = crate::actor::live_entity(ctx, guid)?; let rows = ctx.db.pkg_playerbots_runner(); let mut state = rows.character_guid().find(guid).ok_or("runner missing")?; if state.foreground.is_some() { @@ -3161,7 +3193,7 @@ pub fn playerbots_fixture_runner_stage_recovery_capacity( x: bot.home_x, y: bot.home_y, z: bot.home_z, - geometry_revision: crate::nav::coverage_generation(ctx, bot.home_map), + geometry_revision: crate::actor::movement::coverage_generation(ctx, bot.home_map), }; state.objective_sequence = identity; state.objective = Some(super::runner::Objective { @@ -3186,7 +3218,7 @@ pub fn playerbots_fixture_runner_stage_recovery_capacity( work: super::recovery::Work::Fight(target), reason: super::decision::Reason::Grind, destination: destination.clone(), - geometry: crate::nav::inputs(ctx, me.map_id), + geometry: crate::actor::movement::inputs(ctx, me.map_id), objective: identity, last_observed_micros: now, stalled_micros: 1_000_000, @@ -3210,7 +3242,7 @@ pub fn playerbots_fixture_runner_expire_recovery_capacity( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let rows = ctx.db.pkg_playerbots_runner(); let mut state = rows.character_guid().find(guid).ok_or("runner missing")?; let attempt = state @@ -3233,7 +3265,7 @@ pub fn playerbots_fixture_runner_select_cohort( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; super::runner::playerbots_select_controller(ctx, guid, super::Controller::Cohort)?; runner_park_for(ctx, guid) } @@ -3245,7 +3277,7 @@ pub fn playerbots_fixture_runner_stage_quest_retry( quest: u32, target: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::decision::{Action, Candidate, CandidateId, QuestInteraction, Reason}; use super::pkg_playerbots_runner; let mut state = ctx @@ -3279,7 +3311,7 @@ pub fn playerbots_fixture_runner_stage_defense_retry( guid: u64, target: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::decision::{Action, Candidate, CandidateId, Reason}; use super::pkg_playerbots_runner; let mut state = ctx @@ -3314,14 +3346,14 @@ pub fn playerbots_fixture_runner_refuse_quest_candidate( quest: u32, target: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; super::runner::fixture_refuse_quest_candidate(ctx, guid, quest, target)?; runner_park_for(ctx, guid) } #[reducer] pub fn playerbots_fixture_runner_survival(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut personality = ctx .db .pkg_playerbots_personality() @@ -3344,7 +3376,7 @@ pub fn playerbots_fixture_runner_survival_hit( attacker: u64, damage: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_runner_survival(ctx, guid)?; playerbots_fixture_runner_damage(ctx, guid, attacker, damage)?; if ctx @@ -3366,8 +3398,8 @@ pub fn playerbots_fixture_runner_clear_navigation( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let me = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let me = crate::actor::live_entity(ctx, guid)?; let cx = lyracore_shared::terrain::cell_index(me.x).ok_or("fixture off grid")?; let cy = lyracore_shared::terrain::cell_index(me.y).ok_or("fixture off grid")?; for x in cx.saturating_sub(1)..=cx.saturating_add(1).min(1023) { @@ -3386,7 +3418,7 @@ pub fn playerbots_fixture_runner_expire_objective( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::pkg_playerbots_runner; let mut state = ctx .db @@ -3415,7 +3447,7 @@ pub fn playerbots_fixture_runner_expire_home_during_live_recovery_cast( use super::pkg_playerbots_runner; use super::runner::{Failure, ObjectiveKind, ObjectiveStage, Running}; - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_runner_pass_once(ctx, guid)?; let state = ctx @@ -3442,7 +3474,7 @@ pub fn playerbots_fixture_runner_expire_home_during_live_recovery_cast( { return Err("runner cast identity is not retained".to_string()); } - if crate::spell::pending_cast(ctx, guid).as_ref() != Some(handle) { + if crate::actor::pending_cast(ctx, guid).as_ref() != Some(handle) { return Err("runner cast is not the exact pending cast".to_string()); } let action = @@ -3496,7 +3528,7 @@ pub fn playerbots_fixture_runner_expire_home_during_live_recovery_cast( { return Err("expired Home pass did not retain the tactical cast".to_string()); } - if crate::spell::pending_cast(ctx, guid).as_ref() != Some(&expected_handle) { + if crate::actor::pending_cast(ctx, guid).as_ref() != Some(&expected_handle) { return Err("expired Home pass did not retain the pending cast".to_string()); } let action = @@ -3540,7 +3572,7 @@ fn runner_park_for(ctx: &ReducerContext, guid: u64) -> Result<(), String> { #[reducer] pub fn playerbots_fixture_companion_due(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; runner_due_for(ctx, guid) } @@ -3549,9 +3581,9 @@ pub fn playerbots_fixture_runner_wide_recovery( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_runner_stage(ctx, guid, true)?; - use crate::game_player_spell; + use crate::tables::game_player_spell; let spells = ctx.db.game_player_spell(); let heal = spells .by_character_spell() @@ -3560,14 +3592,14 @@ pub fn playerbots_fixture_runner_wide_recovery( .ok_or("heal missing")?; spells.id().delete(heal.id); for spell_id in 5_091_000..5_091_300 { - spells.insert(crate::spell::PlayerSpell { + spells.insert(crate::tables::PlayerSpell { id: 0, character_guid: guid, owner_identity: heal.owner_identity, spell_id, }); } - spells.insert(crate::spell::PlayerSpell { id: 0, ..heal }); + spells.insert(crate::tables::PlayerSpell { id: 0, ..heal }); let rotations = ctx.db.pkg_playerbots_rotation(); let bot = ctx .db @@ -3614,7 +3646,7 @@ const PROVISION_SKILL_AVAILABILITY_ROWS: u32 = 17; const PROVISION_SKILL_AVAILABILITY_FIRST: u64 = 5_096_000; fn replace_profile_skill_availability(ctx: &ReducerContext, admitted: bool) -> Result<(), String> { - use crate::game_skill_availability; + use crate::tables::game_skill_availability; let line = super::provisioning::WARRIOR_PROFILE_SKILL; let rows = ctx.db.game_skill_availability(); let fixture_end = @@ -3647,7 +3679,7 @@ fn replace_profile_skill_availability(ctx: &ReducerContext, admitted: bool) -> R .delete(PROVISION_SKILL_AVAILABILITY_FIRST + u64::from(ordinal)); } for ordinal in 0..PROVISION_SKILL_AVAILABILITY_ROWS { - rows.insert(crate::SkillAvailability { + rows.insert(crate::tables::SkillAvailability { id: PROVISION_SKILL_AVAILABILITY_FIRST + u64::from(ordinal), skill_line: line, race_mask: 0, @@ -3671,7 +3703,7 @@ fn ensure_profile_item( spell_id: u32, restores_power: bool, ) -> Result<(), String> { - use crate::game_item_template; + use crate::tables::game_item_template; if ctx.db.game_item_template().entry().find(entry).is_some() { return Ok(()); } @@ -3703,7 +3735,7 @@ fn ensure_profile_item( #[reducer] pub fn playerbots_fixture_provision_catalog(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; // The seed and World import both own this real low id. Deliberately different values prove that // fixture staging takes the import-aware no-op path; item 52 is never part of the profile. ensure_profile_item( @@ -3740,7 +3772,7 @@ pub fn playerbots_fixture_provision_complete_profile( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -3783,8 +3815,8 @@ pub fn playerbots_fixture_provision_complete_profile( pub fn playerbots_fixture_provision_partial_talent_catalog( ctx: &ReducerContext, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - use crate::{game_talent, game_talent_tab}; + crate::package::require_operator(ctx)?; + use crate::tables::{game_talent, game_talent_tab}; if ctx.db.game_talent_tab().count() != 0 { return Err("partial talent catalogue requires no tab rows".to_string()); } @@ -3901,10 +3933,10 @@ fn provision_offering( spell_id: u32, required_level: u8, ) { - use crate::game_trainer_spell; + use crate::tables::game_trainer_spell; let rows = ctx.db.game_trainer_spell(); rows.id().delete(id); - rows.insert(crate::TrainerSpell { + rows.insert(crate::tables::TrainerSpell { id, trainer_entry, spell_id, @@ -3938,7 +3970,7 @@ pub fn playerbots_fixture_provision_trainer_catalog( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -3990,24 +4022,24 @@ pub fn playerbots_fixture_provision_trainer_catalog( } provision_trainer(ctx, PROVISION_WARRIOR_TRAINER, super::class::WARRIOR)?; provision_trainer(ctx, PROVISION_OTHER_TRAINER, super::class::PALADIN)?; - provision_wrapper(ctx, PROVISION_LEARN_WRAPPER, 2050, crate::spell::E_SCRIPTED)?; + provision_wrapper(ctx, PROVISION_LEARN_WRAPPER, 2050, crate::actor::E_SCRIPTED)?; provision_wrapper( ctx, PROVISION_CHANNEL_WRAPPER, 139, - crate::spell::A_PERIODIC_TRIGGER, + crate::actor::A_PERIODIC_TRIGGER, )?; provision_wrapper( ctx, PROVISION_PROC_WRAPPER, 133, - crate::spell::A_PROC_TRIGGER, + crate::actor::A_PROC_TRIGGER, )?; provision_wrapper( ctx, PROVISION_WRONG_WRAPPER, PROVISION_WRONG_WRAPPER_PAYLOAD, - crate::spell::E_SCRIPTED, + crate::actor::E_SCRIPTED, )?; for ordinal in 0..40u64 { @@ -4091,12 +4123,12 @@ pub fn playerbots_fixture_provision_trainer_catalog( 1, ); } - use crate::game_spell_chain; + use crate::tables::game_spell_chain; ctx.db .game_spell_chain() .spell_id() .delete(PROVISION_PREVIOUS_RANK_SPELL); - ctx.db.game_spell_chain().insert(crate::spell::SpellChain { + ctx.db.game_spell_chain().insert(crate::tables::SpellChain { spell_id: PROVISION_PREVIOUS_RANK_SPELL, prev_spell: PROVISION_PREVIOUS_REQUIRED, first_spell: PROVISION_PREVIOUS_REQUIRED, @@ -4112,7 +4144,7 @@ pub fn playerbots_fixture_provision_spell_action( guid: u64, spell_id: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if !matches!( spell_id, 355 | 2050 @@ -4179,7 +4211,7 @@ pub fn playerbots_fixture_provision_steps( guid: u64, count: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if count > 64 { return Err("provisioning fixture step count exceeds 64".to_string()); } @@ -4209,7 +4241,7 @@ pub fn playerbots_fixture_provision_steps( #[reducer] pub fn playerbots_fixture_provision_due(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::provisioning::pkg_playerbots_provisioning; let mut state = provision_state(ctx, guid)?; state.next_repair_micros = 0; @@ -4222,7 +4254,7 @@ pub fn playerbots_fixture_provision_due(ctx: &ReducerContext, guid: u64) -> Resu #[reducer] pub fn playerbots_fixture_provision_reset(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::provisioning::pkg_playerbots_provisioning; let mut state = provision_state(ctx, guid)?; state.action_cursor = 0; @@ -4240,13 +4272,13 @@ pub fn playerbots_fixture_provision_remove_recovery( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - use crate::game_player_spell; + crate::package::require_operator(ctx)?; + use crate::tables::game_player_spell; let spells = ctx.db.game_player_spell(); for row in spells.by_character().filter(guid).collect::>() { spells.id().delete(row.id); } - use crate::game_item_instance; + use crate::tables::game_item_instance; let items = ctx.db.game_item_instance(); let mut banked_food = false; for mut row in items @@ -4286,9 +4318,9 @@ pub fn playerbots_fixture_provision_full_bag( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_provision_catalog(ctx)?; - use crate::{game_item_instance, game_item_template}; + use crate::tables::{game_item_instance, game_item_template}; let items = ctx.db.game_item_instance(); for row in items .by_owner_guid() @@ -4317,8 +4349,8 @@ pub fn playerbots_fixture_provision_full_bag( .ok_or("quest item template missing")?; quest.bonding = 4; ctx.db.game_item_template().entry().update(quest); - crate::items::grant_item(ctx, guid, PROVISION_QUEST_ITEM, 1)?; - crate::items::grant_item(ctx, guid, PROVISION_FILLER, 15)?; + crate::package::fixture::grant_item(ctx, guid, PROVISION_QUEST_ITEM, 1)?; + crate::package::fixture::grant_item(ctx, guid, PROVISION_FILLER, 15)?; set_provision_action(ctx, guid, |action| { matches!( action, @@ -4333,9 +4365,9 @@ pub fn playerbots_fixture_provision_missing_resource( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_fixture_provision_catalog(ctx)?; - use crate::game_item_template; + use crate::tables::game_item_template; ctx.db.game_item_template().entry().delete(1251); set_provision_action(ctx, guid, |action| { matches!( @@ -4352,8 +4384,8 @@ pub fn playerbots_fixture_provision_missing_spell( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - use crate::{game_player_spell, game_spell, game_spell_effect}; + crate::package::require_operator(ctx)?; + use crate::tables::{game_player_spell, game_spell, game_spell_effect}; let spells = ctx.db.game_player_spell(); for row in spells .by_character_spell() @@ -4377,7 +4409,7 @@ pub fn playerbots_fixture_provision_wrong_class_spell( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -4409,7 +4441,7 @@ pub fn playerbots_fixture_provision_profile_overflow( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -4435,9 +4467,9 @@ pub fn playerbots_fixture_provision_skill_overflow( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; replace_profile_skill_availability(ctx, false)?; - use crate::game_player_skill; + use crate::tables::game_player_skill; let skills = ctx.db.game_player_skill(); for row in skills.by_character().filter(guid).collect::>() { if row.skill_line == super::provisioning::WARRIOR_PROFILE_SKILL { @@ -4457,9 +4489,9 @@ pub fn playerbots_fixture_provision_stronger_weapon( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::items::grant_item(ctx, guid, 50, 1)?; - use crate::game_item_instance; + crate::package::require_operator(ctx)?; + crate::package::fixture::grant_item(ctx, guid, 50, 1)?; + use crate::tables::game_item_instance; let slot = ctx .db .game_item_instance() @@ -4484,8 +4516,8 @@ pub fn playerbots_fixture_provision_bank_weapon( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - use crate::game_item_instance; + crate::package::require_operator(ctx)?; + use crate::tables::game_item_instance; let items = ctx.db.game_item_instance(); let mut weapon = items .by_owner_guid() @@ -4502,7 +4534,7 @@ pub fn playerbots_fixture_provision_equip_bank_weapon( ctx: &ReducerContext, guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; crate::actor::equip_profile_upgrade(ctx, guid, 39) .map(|_| ()) .map_err(|refusal| refusal.as_tag().to_string()) @@ -4510,8 +4542,8 @@ pub fn playerbots_fixture_provision_equip_bank_weapon( #[reducer] pub fn playerbots_fixture_provision_dead(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let mut entity = crate::helpers::live_entity(ctx, guid)?; + crate::package::require_operator(ctx)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; entity.dead = true; entity.health = 0; ctx.db.game_world_entity().guid().update(entity); @@ -4528,10 +4560,10 @@ pub fn playerbots_fixture_provision_dead(ctx: &ReducerContext, guid: u64) -> Res #[reducer] pub fn playerbots_fixture_provision_levelup(ctx: &ReducerContext, guid: u64) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::stats::set_character_level(ctx, guid, 9)?; - let mut entity = crate::helpers::live_entity(ctx, guid)?; - crate::xp::grant_xp(ctx, &mut entity, 1_000_000); + crate::package::require_operator(ctx)?; + crate::package::character::set_character_level(ctx, guid, 9)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; + crate::package::fixture::grant_xp(ctx, &mut entity, 1_000_000); ctx.db.game_world_entity().guid().update(entity); runner_park_for(ctx, guid) } diff --git a/playerbots/src/fixture/target_claims.rs b/playerbots/src/fixture/target_claims.rs index 93c8c79..ccd92f6 100644 --- a/playerbots/src/fixture/target_claims.rs +++ b/playerbots/src/fixture/target_claims.rs @@ -19,7 +19,7 @@ fn fight(ctx: &ReducerContext, guid: u64) -> Option { } fn available(ctx: &ReducerContext, guid: u64, target: u64) -> Result { - let me = crate::helpers::live_entity(ctx, guid)?; + let me = crate::actor::live_entity(ctx, guid)?; Ok(matches!( super::super::quest_loop::live_creature_target(ctx, &me, ENTRY, |_| true), super::super::quest_loop::LiveCreatureTarget::Found(found) if found.guid == target @@ -42,11 +42,11 @@ fn add_target(ctx: &ReducerContext, source: u64, guid: u64, x: f32) -> Result<() .find(ENTRY) .ok_or("template missing")?; let spawn = ctx.db.game_creature_spawn().insert(spawn); - let mut entity = crate::creatures::build_creature_entity(&spawn, &template, 0, 0); + let mut entity = crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0); entity.level = 1; entity.health = 1000; entity.max_health = 1000; - crate::creatures::insert_creature_entity(ctx, entity); + crate::package::fixture::insert_creature_entity(ctx, entity); Ok(()) } @@ -55,8 +55,8 @@ pub fn playerbots_fixture_solo_target_claim( ctx: &ReducerContext, case: String, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; if ctx.db.pkg_playerbots_bot().iter().next().is_some() { return Err("target claim fixture requires a fresh, private Shard".into()); } @@ -86,7 +86,7 @@ pub fn playerbots_fixture_solo_target_claim( bot.home_x = 1200.0; let guid = bot.character_guid; ctx.db.pkg_playerbots_bot().id().update(bot); - let mut me = crate::helpers::live_entity(ctx, guid)?; + let mut me = crate::actor::live_entity(ctx, guid)?; me.level = 1; me.power = 0; ctx.db.game_world_entity().guid().update(me); @@ -106,7 +106,7 @@ pub fn playerbots_fixture_solo_target_claim( } } let target = companion_creature(ctx, ENTRY, 1240.0, 1200.0, 50.0, None)?; - let mut entity = crate::helpers::live_entity(ctx, target)?; + let mut entity = crate::actor::live_entity(ctx, target)?; entity.level = 1; entity.health = 1000; entity.max_health = 1000; @@ -123,8 +123,8 @@ pub fn playerbots_fixture_solo_target_claim( return Err("another solo bot selected the claimed creature before combat".into()); } if !matches!( - crate::loot::tag::live_loot_tag_eligibility(ctx, target, other), - crate::loot::tag::LiveLootTagEligibility::Available + crate::actor::live_loot_tag_eligibility(ctx, target, other), + crate::actor::LiveLootTagEligibility::Available ) { return Err("fixture already had a Loot Tag, so it did not prove an approach claim".into()); } @@ -177,7 +177,7 @@ pub fn playerbots_fixture_solo_target_claim( let mut state = rows.character_guid().find(owner).ok_or("runner missing")?; state.solo_target_guid = u64::MAX; rows.character_guid().update(state); - let me = crate::helpers::live_entity(ctx, other)?; + let me = crate::actor::live_entity(ctx, other)?; if !matches!( TargetClaims::read(ctx, me.guid).availability(ctx, target), Availability::ReadLimit @@ -283,7 +283,7 @@ pub fn playerbots_fixture_solo_target_claim( let members = vec![owner, other]; let partitions = fixture_group_partitions(ctx, ROLES_GROUP, &members)?; let raid_slots = vec![0; members.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, ROLES_GROUP, owner, @@ -291,7 +291,7 @@ pub fn playerbots_fixture_solo_target_claim( 2, 0, members, - crate::SessionActor { + crate::package::fixture::SessionActor { guid: owner, ownership: None, }, @@ -313,14 +313,14 @@ pub fn playerbots_fixture_solo_target_claim( } } "death" => { - let mut me = crate::helpers::live_entity(ctx, owner)?; + let mut me = crate::actor::live_entity(ctx, owner)?; me.dead = true; me.health = 0; ctx.db.game_world_entity().guid().update(me); } "freeze" => super::super::runner::transition_controller(ctx, owner, Controller::Frozen)?, "partition" => { - let mut me = crate::helpers::live_entity(ctx, owner)?; + let mut me = crate::actor::live_entity(ctx, owner)?; me.instance_id = 1; ctx.db.game_world_entity().guid().update(me); } diff --git a/playerbots/src/goals.rs b/playerbots/src/goals.rs index f3150e8..2874fc0 100644 --- a/playerbots/src/goals.rs +++ b/playerbots/src/goals.rs @@ -8,8 +8,8 @@ use super::{ pkg_playerbots_rotation, PlayerbotsBot, PlayerbotsGoal, PlayerbotsPersonality, PlayerbotsRotation, ROLE_HEALER, ROLE_TANK, }; -use crate::transfer::game_bot_transfer_intent; -use crate::{ +use crate::tables::game_bot_transfer_intent; +use crate::tables::{ game_character, game_character_quest, game_corpse_loot, game_creature_loot, game_creature_quest, game_creature_spline, game_creature_template, game_group, game_group_member, game_melee_attack, game_quest_event_requirement, game_quest_objective, @@ -160,8 +160,8 @@ pub(super) fn think(ctx: &ReducerContext, bot: &PlayerbotsBot, now: i64) { me.max_health, flee_threshold(ctx, &me, personality.flee_at_pct), ) { - if let Some(cast) = crate::spell::pending_cast(ctx, me.guid) { - crate::spell::cancel_cast_attempt(ctx, me.guid, cast.scheduled_id); + if let Some(cast) = crate::actor::pending_cast(ctx, me.guid) { + crate::actor::cancel_cast_attempt(ctx, me.guid, cast.scheduled_id); } let _ = crate::actor::stop_attack(ctx, me.guid); walk_toward(ctx, &me, (bot.home_x, bot.home_y, bot.home_z), 0.0, true); @@ -169,14 +169,14 @@ pub(super) fn think(ctx: &ReducerContext, bot: &PlayerbotsBot, now: i64) { return; } - if crate::spell::pending_cast(ctx, me.guid).is_some() { + if crate::actor::pending_cast(ctx, me.guid).is_some() { return; } let party = party_of(ctx, bot.character_guid); let leader = party .as_ref() - .and_then(|party| crate::helpers::live_entity(ctx, party.leader_guid).ok()); + .and_then(|party| crate::actor::live_entity(ctx, party.leader_guid).ok()); match &leader { // A leader who has crossed into an instance on THIS Shard is followed before anything // else: a bot left outside is a bot fighting nothing, next to nobody. @@ -278,7 +278,12 @@ pub(crate) fn death_step(dead: bool, ghost: bool) -> DeathStep { /// /// Runs for every bot, grouped or not. A bot in a party used to lie dead for the rest of its life, /// which read as a wipe the party could not recover from. -fn get_back_up(ctx: &ReducerContext, me: &crate::WorldEntity, bot: &PlayerbotsBot, now: i64) { +fn get_back_up( + ctx: &ReducerContext, + me: &crate::tables::WorldEntity, + bot: &PlayerbotsBot, + now: i64, +) { use lyracore_shared::constants::player_flags; let step = death_step(me.dead, me.player_flags & player_flags::GHOST != 0); let outcome = match step { @@ -311,7 +316,7 @@ fn get_back_up(ctx: &ReducerContext, me: &crate::WorldEntity, bot: &PlayerbotsBo /// entity. A real player clears it by logging in; a bot never logs in, so a stale `true` would make /// the next rebuild — an ordinary Shard arrival — put a living bot back in the world as a corpse. fn clear_pending_ghost(ctx: &ReducerContext, character_guid: u64) { - let Some(mut character) = crate::helpers::character_by_guid(ctx, character_guid) else { + let Some(mut character) = crate::actor::character_by_guid(ctx, character_guid) else { return; }; if !character.pending_ghost { @@ -529,34 +534,15 @@ pub(crate) fn may_rebuild( && (goal_kind != Some(goal::IN_TRANSIT) || in_transit_for_micros >= IN_TRANSIT_WAIT_MICROS) } -/// This bot's live entity, rebuilt from its durable Character row when it has none. -/// -/// That rebuild IS the arrival. A Transfer carries the Character row and every registered transfer -/// arm across the Shard boundary; it does not carry a `game_world_entity`, and nothing on the far -/// side re-spawns one. Until this runs the arriving bot is durable but not in the world. -/// -/// Two states deliberately produce no body: -/// -/// - The bot is escrowed mid-Transfer. [`crate::helpers::character_by_guid`] is the in-transit -/// fence, so the durable row simply does not answer, and a bot half-way across a boundary is not -/// rebuilt on the Shard it is leaving. -/// - This Shard retains a durable Transfer Intent. The Gateway reads the Character row to decide -/// where the crossing goes, so putting a body back before exact completion would move the bot out -/// from under its own Intent. -/// -/// A REBUILT GHOST STAYS A GHOST. `build_player_entity` always builds alive, and a released ghost -/// loses its body whenever the graveyard it released to is on another map — a death in a dungeon -/// resolves to a graveyard outside it, and the cross-map placement despawns the entity. Rebuilding -/// that bot alive would hand it a free full-health resurrection on the spot, with its corpse left -/// behind and its death never resolved. `pending_instance_id`'s sibling carry column, -/// `pending_ghost`, is what the Character row remembers instead, and re-applying it here is the same -/// thing `player_login` does at the same point of the same rebuild. +/// Restore this bot's body after its Legacy Transfer wait and durable Transfer Intent permit it. +/// Core observes the Character's Transfer fence, restores ghost state, and removes any movement +/// leg from before the crossing. pub(super) fn body( ctx: &ReducerContext, bot: &PlayerbotsBot, now: i64, -) -> Option { - if let Ok(me) = crate::helpers::live_entity(ctx, bot.character_guid) { +) -> Option { + if let Ok(me) = crate::actor::live_entity(ctx, bot.character_guid) { return Some(me); } let current = goal_of(ctx, bot.character_guid); @@ -574,31 +560,14 @@ pub(super) fn body( ) { return None; } - let character = crate::helpers::character_by_guid(ctx, bot.character_guid)?; - let mut entity = - crate::creatures::build_player_entity(ctx, &character, spacetimedb::Identity::ZERO); - if character.pending_ghost { - let (dead, health, player_flags, unit_bytes_1) = - crate::world::ghost_restored_fields(entity.player_flags, entity.unit_bytes_1); - entity.dead = dead; - entity.health = health; - entity.player_flags = player_flags; - entity.unit_bytes_1 = unit_bytes_1; - } - ctx.db.game_world_entity().insert(entity); - // A leg thrown before the crossing would interpolate the arrival straight back across the map - // it just left. - ctx.db - .game_creature_spline() - .guid() - .delete(bot.character_guid); + let entity = crate::actor::restore_sessionless_body(ctx, bot.character_guid)?; spacetimedb::log::info!( "playerbots: bot {} is in the world on map {} instance {}", bot.character_guid, - character.map_id, - character.pending_instance_id + entity.map_id, + entity.instance_id ); - crate::helpers::live_entity(ctx, bot.character_guid).ok() + Some(entity) } // ---- personality: the row, or a Runtime Script that overrides it ------------------------------- @@ -635,7 +604,7 @@ pub(crate) fn threshold_from(answer: Option, row_pct: u8) -> u8 { } /// Where this bot breaks off, asked of its Runtime Scripts once per think. -fn flee_threshold(ctx: &ReducerContext, me: &crate::WorldEntity, row_pct: u8) -> u8 { +fn flee_threshold(ctx: &ReducerContext, me: &crate::tables::WorldEntity, row_pct: u8) -> u8 { let engaged = ctx .db .game_melee_attack() @@ -655,7 +624,7 @@ fn flee_threshold(ctx: &ReducerContext, me: &crate::WorldEntity, row_pct: u8) -> /// share that makes somebody worth healing who was not before. fn heal_threshold( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, row_pct: u8, ) -> u8 { @@ -777,7 +746,7 @@ pub(crate) fn leaves_the_party( } /// Does anybody else in this party hold a quest this bot is still working? -fn shares_quest_work(ctx: &ReducerContext, me: &crate::WorldEntity, party: &Party) -> bool { +fn shares_quest_work(ctx: &ReducerContext, me: &crate::tables::WorldEntity, party: &Party) -> bool { let mine = active_quest_entries(ctx, me.guid); party .members @@ -812,7 +781,7 @@ fn holds_one_of(ctx: &ReducerContext, character_guid: u64, entries: &[u32]) -> b /// mirror push contradicts. Repeating the decision costs one refused op — the Intent is picked up in /// well under a second, and the core refuses a leave for a Character in no party — so the tick or /// two before the roster catches up needs no latch of its own. -fn part_ways(ctx: &ReducerContext, me: &crate::WorldEntity) { +fn part_ways(ctx: &ReducerContext, me: &crate::tables::WorldEntity) { // ponytail: a Gateway that never picks the Intent up leaves this deciding, and saying so, once // a second. If that ever shows up in a log, put the decision on the invite-scan window rather // than adding a column to remember it. @@ -821,17 +790,17 @@ fn part_ways(ctx: &ReducerContext, me: &crate::WorldEntity) { still shares", me.guid ); - crate::group::emit_bot_leave_intent(ctx, me.guid); + crate::actor::emit_bot_leave_intent(ctx, me.guid); } /// What the scan knows about one entity in sight. The three reads are indexed but not free, which /// is why the caller only asks about players. fn neighbour_facts( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, my_race: u8, my_quests: &[u32], - other: &crate::WorldEntity, + other: &crate::tables::WorldEntity, ) -> Neighbour { Neighbour { guid: other.guid, @@ -840,7 +809,7 @@ fn neighbour_facts( distance_yd: distance_2d(me.x, me.y, other.x, other.y), // `character_by_guid` is the in-transit fence, so a Character half-way across a Shard // boundary answers nothing and is not invited. - same_team: crate::helpers::character_by_guid(ctx, other.guid) + same_team: crate::actor::character_by_guid(ctx, other.guid) .is_some_and(|character| lyracore_shared::faction::same_team(my_race, character.race)), grouped: ctx .db @@ -863,15 +832,15 @@ fn neighbour_facts( /// With no eligible neighbour this reads a handful of rows and changes nothing. fn invite_a_fellow_quester( ctx: &ReducerContext, - me: &crate::WorldEntity, - active: &[&crate::CharacterQuest], - sight: &[crate::WorldEntity], + me: &crate::tables::WorldEntity, + active: &[&crate::tables::CharacterQuest], + sight: &[crate::tables::WorldEntity], now: i64, ) { if active.is_empty() || !invite_scan_is_open(me.guid, now) { return; } - let Some(inviter) = crate::helpers::character_by_guid(ctx, me.guid) else { + let Some(inviter) = crate::actor::character_by_guid(ctx, me.guid) else { return; }; let my_quests: Vec = active.iter().map(|cq| cq.quest_entry).collect(); @@ -889,13 +858,13 @@ fn invite_a_fellow_quester( }; // Named, not numbered: this line is the only record a serendipity invite was ever decided, and // a wall of 15-digit guids is not a record anyone can read. - let fellow_name = crate::helpers::character_by_guid(ctx, fellow.guid) + let fellow_name = crate::actor::character_by_guid(ctx, fellow.guid) .map_or_else(|| fellow.guid.to_string(), |character| character.name); spacetimedb::log::info!( "playerbots: {} invites {fellow_name} — both are working a quest the other holds", inviter.name ); - crate::group::emit_bot_invite_intent(ctx, me.guid, fellow.guid); + crate::actor::emit_bot_invite_intent(ctx, me.guid, fellow.guid); } // ---- crossing a Shard boundary ------------------------------------------------------------------- @@ -977,9 +946,9 @@ fn live_target_is_available_to( fn live_target_is_available( ctx: &ReducerContext, character_guid: u64, - target: &crate::WorldEntity, + target: &crate::tables::WorldEntity, ) -> bool { - let entitlement = crate::loot::death_entitlement( + let entitlement = crate::actor::death_entitlement( ctx, target.guid, target.x, @@ -1002,7 +971,7 @@ fn current_melee_target( character_guid: u64, target_guid: u64, ) -> Option { - let Ok(target) = crate::helpers::live_entity(ctx, target_guid) else { + let Ok(target) = crate::actor::live_entity(ctx, target_guid) else { return Some(target_guid); }; if live_target_is_available(ctx, character_guid, &target) { @@ -1016,7 +985,7 @@ fn current_melee_target( /// or on somebody in its party nearby. fn combat_target( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, ) -> Option { let melee = ctx.db.game_melee_attack(); @@ -1027,7 +996,7 @@ fn combat_target( } let party_guids: &[u64] = party.map(|p| p.members.as_slice()).unwrap_or(&[]); for candidate in - crate::helpers::entities_near(ctx, me.map_id, me.instance_id, me.x, me.y, ASSIST_RADIUS_YD) + crate::actor::entities_near(ctx, me.map_id, me.instance_id, me.x, me.y, ASSIST_RADIUS_YD) { if candidate.is_player() || candidate.dead || candidate.owner_guid != 0 { continue; @@ -1056,7 +1025,7 @@ pub(crate) fn closes_to_melee(role: u8, in_a_party: bool) -> bool { fn fight( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, party: Option<&Party>, personality: &PlayerbotsPersonality, @@ -1066,7 +1035,7 @@ fn fight( let _ = super::actions::attack(ctx, me.guid, target); // Nothing chases for a PLAYER-typed entity, so a bot that pulled from range would stand // there swinging at nothing. Close first, cast second. - if let Ok(enemy) = crate::helpers::live_entity(ctx, target) { + if let Ok(enemy) = crate::actor::live_entity(ctx, target) { if distance_2d(me.x, me.y, enemy.x, enemy.y) > MELEE_RANGE_YD { walk_toward(ctx, me, (enemy.x, enemy.y, enemy.z), MELEE_RANGE_YD, true); } @@ -1080,7 +1049,7 @@ fn fight( /// are all the cast core's answers — a Refusal here just means the next row gets its turn. fn cast_rotation( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, party: Option<&Party>, personality: &PlayerbotsPersonality, @@ -1109,7 +1078,7 @@ fn cast_rotation( continue; }; let Some(cast_at) = available_rotation_target(selected, |target_guid| { - crate::helpers::live_entity(ctx, target_guid) + crate::actor::live_entity(ctx, target_guid) .is_ok_and(|target| live_target_is_available(ctx, me.guid, &target)) }) else { continue; @@ -1145,7 +1114,7 @@ fn available_rotation_target( /// per row. fn rotation_target( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, heal_at_pct: u8, row: &PlayerbotsRotation, @@ -1153,7 +1122,7 @@ fn rotation_target( ) -> Option { match row.condition { cond::ALWAYS => Some(RotationTarget::Hostile(current_target)), - cond::SELF_MISSING_AURA => (!crate::spell::has_aura(ctx, me.guid, row.spell_id)) + cond::SELF_MISSING_AURA => (!crate::actor::has_aura(ctx, me.guid, row.spell_id)) .then_some(RotationTarget::Friendly(me.guid)), cond::ENEMY_ON_ALLY => enemy_on_ally(ctx, me, party).map(RotationTarget::Hostile), cond::ALLY_HP_BELOW_PCT => { @@ -1183,20 +1152,20 @@ fn rotation_target( /// in the list: a healer is a party member too. fn party_entities( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, -) -> Vec { +) -> Vec { let Some(party) = party else { - return crate::helpers::live_entity(ctx, me.guid) + return crate::actor::live_entity(ctx, me.guid) .into_iter() .collect(); }; party .members .iter() - .filter_map(|guid| crate::helpers::live_entity(ctx, *guid).ok()) + .filter_map(|guid| crate::actor::live_entity(ctx, *guid).ok()) .filter(|entity| { - !entity.dead && crate::helpers::in_same_partition(entity, me.map_id, me.instance_id) + !entity.dead && crate::actor::in_same_partition(entity, me.map_id, me.instance_id) }) .collect() } @@ -1204,7 +1173,7 @@ fn party_entities( /// A hostile unit swinging at somebody in the party who is not this bot — the peel target. fn enemy_on_ally( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, ) -> Option { let allies: Vec = party_entities(ctx, me, party) @@ -1228,7 +1197,7 @@ fn enemy_on_ally( /// with one cast spends it where it counts. fn lowest_hurt_ally( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, threshold_pct: u8, ) -> Option { @@ -1247,13 +1216,13 @@ fn lowest_hurt_ally( fn ally_missing_aura( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&Party>, spell_id: u32, ) -> Option { party_entities(ctx, me, party) .into_iter() - .find(|entity| !crate::spell::has_aura(ctx, entity.guid, spell_id)) + .find(|entity| !crate::actor::has_aura(ctx, entity.guid, spell_id)) .map(|entity| entity.guid) } @@ -1281,7 +1250,11 @@ fn tank_target(ctx: &ReducerContext, party: Option<&Party>) -> Option { } /// How many distinct hostile units the party currently holds threat on. -fn engaged_enemies(ctx: &ReducerContext, me: &crate::WorldEntity, party: Option<&Party>) -> usize { +fn engaged_enemies( + ctx: &ReducerContext, + me: &crate::tables::WorldEntity, + party: Option<&Party>, +) -> usize { let threat = ctx.db.game_threat(); let mut seen: Vec = Vec::new(); for entity in party_entities(ctx, me, party) { @@ -1305,7 +1278,7 @@ fn engaged_enemies(ctx: &ReducerContext, me: &crate::WorldEntity, party: Option< /// same question — literally the same function — and `crate::actor::accept_quest` stays the /// authority that answers it for real. This only decides whether the walk is worth taking. /// -/// `crate::quest::accept_gates` IS the core's Gate: level, race, class, the previous step in the +/// `crate::actor::quest_acceptance` IS the core's Gate: level, race, class, the previous step in the /// chain, and whether the bot already holds the quest, in the order `apply_accept_quest` applies /// them. Nothing on this side re-states any of it, so there is nothing left to drift. /// @@ -1315,15 +1288,15 @@ fn engaged_enemies(ctx: &ReducerContext, me: &crate::WorldEntity, party: Option< /// order the core's order. fn worth_the_walk( ctx: &ReducerContext, - me: &crate::WorldEntity, - tmpl: &crate::QuestTemplate, + me: &crate::tables::WorldEntity, + tmpl: &crate::tables::QuestTemplate, ) -> Result<(), String> { // The `Ok` payload is the log row the accept would reset in place rather than duplicate. // Selection only needs to know that no Gate refused. - let _resettable_row = crate::quest::accept_gates(ctx, me, tmpl)?; + let _resettable_row = crate::actor::quest_acceptance(ctx, me, tmpl)?; if bag_can_take_the_quest_item( tmpl.src_item != 0, - crate::items::has_free_slot(ctx, me.guid), + crate::actor::has_free_slot(ctx, me.guid), ) { return Ok(()); } @@ -1348,8 +1321,8 @@ pub(crate) fn bag_can_take_the_quest_item(hands_over_an_item: bool, bag_has_room /// Every row in this bot's quest log, oldest first. Read once per tick: the work to do and the /// room-to-take-another decision both ask the same rows. -fn quest_log(ctx: &ReducerContext, character_guid: u64) -> Vec { - let mut rows: Vec = ctx +fn quest_log(ctx: &ReducerContext, character_guid: u64) -> Vec { + let mut rows: Vec = ctx .db .game_character_quest() .by_character() @@ -1409,7 +1382,7 @@ impl QuestStep { #[allow(clippy::too_many_arguments)] // one tick of one loop, and every argument is one of its inputs fn quest( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, personality: &PlayerbotsPersonality, engaged: Option, @@ -1427,10 +1400,10 @@ fn quest( return Some(goal::QUEST_TRAVEL); } let sight = - crate::helpers::entities_near(ctx, me.map_id, me.instance_id, me.x, me.y, QUEST_SIGHT_YD); + crate::actor::entities_near(ctx, me.map_id, me.instance_id, me.x, me.y, QUEST_SIGHT_YD); let log = quest_log(ctx, me.guid); - let active: Vec<&crate::CharacterQuest> = log + let active: Vec<&crate::tables::CharacterQuest> = log .iter() .filter(|row| !row.rewarded && !row.failed) .collect(); @@ -1466,12 +1439,12 @@ fn quest( #[allow(clippy::too_many_arguments)] // one decision, and every argument is one of its inputs fn decide_quest( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, personality: &PlayerbotsPersonality, engaged: Option, - active: &[&crate::CharacterQuest], - sight: &[crate::WorldEntity], + active: &[&crate::tables::CharacterQuest], + sight: &[crate::tables::WorldEntity], now: i64, ) -> Option { // Already swinging at something. Whether that is quest work is the bot's to say; anything else @@ -1503,11 +1476,11 @@ fn decide_quest( /// itself while grinding stays grinding, which the goal row is what remembers. fn engaged_reason( ctx: &ReducerContext, - active: &[&crate::CharacterQuest], + active: &[&crate::tables::CharacterQuest], target: u64, held: Option, ) -> Option { - let entry = crate::helpers::live_entity(ctx, target).ok()?.entry; + let entry = crate::actor::live_entity(ctx, target).ok()?.entry; if active .iter() .any(|cq| kill_target_entry(ctx, cq) == Some(entry)) @@ -1527,13 +1500,13 @@ fn held_kind(ctx: &ReducerContext, character_guid: u64) -> Option { /// Deliberately NOT a completeness test. `apply_turn_in_quest` owns completeness and answers a /// COLLECT objective out of the live bag; this only decides whether there is still hunting to do, /// which is what stops a bot culling wolves forever after the second one. -fn kill_target_entry(ctx: &ReducerContext, cq: &crate::CharacterQuest) -> Option { +fn kill_target_entry(ctx: &ReducerContext, cq: &crate::tables::CharacterQuest) -> Option { ctx.db .game_quest_objective() .by_quest() .filter(&cq.quest_entry) .find(|obj| { - obj.kind == crate::quest::objective_kind::KILL_CREATURE + obj.kind == crate::actor::objective_kind::KILL_CREATURE && cq.counts.get(obj.obj_index as usize).copied().unwrap_or(0) < obj.required_count }) .map(|obj| obj.target_entry) @@ -1546,13 +1519,13 @@ fn kill_target_entry(ctx: &ReducerContext, cq: &crate::CharacterQuest) -> Option /// movement leg and a failed Durable Request each time. `apply_turn_in_quest` remains the /// authority; this only decides whether the walk is worth taking, exactly like [`worth_the_walk`]. /// -/// Two questions, and the core owns one of them. `crate::quest::quest_is_complete` answers the +/// Two questions, and the core owns one of them. `crate::actor::quest_complete` answers the /// objectives — and it answers ONLY those, deliberately, so the deadline is this caller's to ask /// and is asked first. A quest past its deadline is refused as expired; the sweep that flips the /// row runs on its own clock, so between the deadline and the sweep the row still reads active, and /// a bot that trusted `failed` alone would set off for a Refusal in that window. -fn turn_in_ready(ctx: &ReducerContext, cq: &crate::CharacterQuest, now: i64) -> bool { - expired_quest_can_wait(cq.deadline_micros, now) && crate::quest::quest_is_complete(ctx, cq) +fn turn_in_ready(ctx: &ReducerContext, cq: &crate::tables::CharacterQuest, now: i64) -> bool { + expired_quest_can_wait(cq.deadline_micros, now) && crate::actor::quest_complete(ctx, cq) } /// Is this quest still inside its own time limit? `0` is untimed, which is nearly every quest. @@ -1573,7 +1546,7 @@ pub(crate) fn expired_quest_can_wait(deadline_micros: i64, now: i64) -> bool { /// Stops at the count the objective asks for. Past that the extras satisfy nothing and are not /// even handed back by the turn-in, which removes exactly `required_count`; they would sit in the /// bag for the rest of the bot's life. -fn wanted_items(ctx: &ReducerContext, active: &[&crate::CharacterQuest]) -> Vec { +fn wanted_items(ctx: &ReducerContext, active: &[&crate::tables::CharacterQuest]) -> Vec { let mut wanted = Vec::new(); for cq in active { for obj in ctx @@ -1581,9 +1554,9 @@ fn wanted_items(ctx: &ReducerContext, active: &[&crate::CharacterQuest]) -> Vec< .game_quest_objective() .by_quest() .filter(&cq.quest_entry) - .filter(|obj| obj.kind == crate::quest::objective_kind::COLLECT_ITEM) + .filter(|obj| obj.kind == crate::actor::objective_kind::COLLECT_ITEM) { - let have = crate::items::item_count(ctx, cq.character_guid, obj.target_entry); + let have = crate::actor::item_count(ctx, cq.character_guid, obj.target_entry); if have < obj.required_count && !wanted.contains(&obj.target_entry) { wanted.push(obj.target_entry); } @@ -1607,11 +1580,11 @@ fn wanted_items(ctx: &ReducerContext, active: &[&crate::CharacterQuest]) -> Vec< #[allow(clippy::too_many_arguments)] // one quest's next step, and every argument is one of its inputs fn work_quest( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, personality: &PlayerbotsPersonality, - cq: &crate::CharacterQuest, - sight: &[crate::WorldEntity], + cq: &crate::tables::CharacterQuest, + sight: &[crate::tables::WorldEntity], now: i64, stuck: bool, ) -> Option { @@ -1628,7 +1601,7 @@ fn work_quest( if !turn_in_ready(ctx, cq, now) { return None; } - if !crate::items::has_free_slot(ctx, me.guid) && !has_collect_objective(ctx, cq.quest_entry) { + if !crate::actor::has_free_slot(ctx, me.guid) && !has_collect_objective(ctx, cq.quest_entry) { return None; } hand_it_back(ctx, me, bot, cq.quest_entry, sight, stuck) @@ -1641,7 +1614,7 @@ fn has_collect_objective(ctx: &ReducerContext, quest_entry: u32) -> bool { .game_quest_objective() .by_quest() .filter(&quest_entry) - .any(|obj| obj.kind == crate::quest::objective_kind::COLLECT_ITEM) + .any(|obj| obj.kind == crate::actor::objective_kind::COLLECT_ITEM) } /// Carry a ready quest back. The ender in sight is walked to and asked; no ender in sight means the @@ -1657,17 +1630,17 @@ fn has_collect_objective(ctx: &ReducerContext, quest_entry: u32) -> bool { /// the stall clock — and `stuck` stops it making the same trip again after that clock has run. fn hand_it_back( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, quest_entry: u32, - sight: &[crate::WorldEntity], + sight: &[crate::tables::WorldEntity], stuck: bool, ) -> Option { let ender = nearest(me, sight, |e| { !e.is_player() && !e.dead && talkable(me, e) - && offers(ctx, e.entry, quest_entry, crate::quest::quest_role::END) + && offers(ctx, e.entry, quest_entry, crate::actor::quest_role::END) }); let Some(ender) = ender else { if stuck { @@ -1733,9 +1706,9 @@ fn hand_it_back( /// of them instead of reaching either. The nearest giver stays the nearest as the bot closes on it. fn take_a_quest( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, - sight: &[crate::WorldEntity], + sight: &[crate::tables::WorldEntity], now: i64, ) -> Option { let reach = entries_within_reach(ctx, me, bot); @@ -1786,11 +1759,11 @@ fn take_a_quest( /// bot that takes fewer quests is a bot that quests less, while a bot that takes more is the loop. fn open_quests_of( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, entry: u32, reach: &std::collections::BTreeSet, ) -> Vec { - let mut open: Vec = offered_by(ctx, entry, crate::quest::quest_role::START) + let mut open: Vec = offered_by(ctx, entry, crate::actor::quest_role::START) .into_iter() .filter(|quest_entry| workable(ctx, *quest_entry)) .filter(|quest_entry| within_reach(ctx, *quest_entry, reach)) @@ -1815,10 +1788,10 @@ fn open_quests_of( /// can work a quest on, as a set so `within_reach` is one lookup per entry. fn entries_within_reach( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, ) -> std::collections::BTreeSet { - crate::helpers::entities_near( + crate::actor::entities_near( ctx, me.map_id, me.instance_id, @@ -1850,14 +1823,14 @@ fn within_reach( .filter(&quest_entry) { match obj.kind { - crate::quest::objective_kind::KILL_CREATURE => kill_targets.push(obj.target_entry), - crate::quest::objective_kind::COLLECT_ITEM => collect_items.push(obj.target_entry), + crate::actor::objective_kind::KILL_CREATURE => kill_targets.push(obj.target_entry), + crate::actor::objective_kind::COLLECT_ITEM => collect_items.push(obj.target_entry), _ => {} } } let ender_in_reach = reach .iter() - .any(|entry| offers(ctx, *entry, quest_entry, crate::quest::quest_role::END)); + .any(|entry| offers(ctx, *entry, quest_entry, crate::actor::quest_role::END)); quest_in_reach( &kill_targets, &collect_items, @@ -1916,8 +1889,8 @@ pub(crate) fn objectives_are_workable(kinds: &[u8], needs_event_credit: bool) -> || kinds.iter().any(|kind| { matches!( *kind, - crate::quest::objective_kind::KILL_CREATURE - | crate::quest::objective_kind::COLLECT_ITEM + crate::actor::objective_kind::KILL_CREATURE + | crate::actor::objective_kind::COLLECT_ITEM ) }) } @@ -1927,7 +1900,7 @@ const GRIND_LEVEL_REACH: u32 = 3; /// Is this creature worth a `bot_level` bot's time, and survivable by it? /// -/// The low end is `crate::xp::xp_for_kill`, the core's own grey clamp: a creature that pays no +/// The low end is `crate::actor::xp_for_kill`, the core's own grey clamp: a creature that pays no /// experience is a creature there is no reason to fight. The high end is [`GRIND_LEVEL_REACH`], and /// elites are out whatever their level — a bot whose personality never lets it flee will otherwise /// walk into the nearest elite, die, resurrect, walk back into it, and spend its life doing that. @@ -1935,7 +1908,7 @@ const GRIND_LEVEL_REACH: u32 = 3; pub(crate) fn worth_grinding(bot_level: u32, victim_level: u32, elite: bool) -> bool { !elite && victim_level <= bot_level + GRIND_LEVEL_REACH - && crate::xp::xp_for_kill(victim_level, bot_level) > 0 + && crate::actor::xp_for_kill(victim_level, bot_level) > 0 } /// Is this creature an elite? The one place the core spells the elite ranks out is the kill-XP @@ -1945,15 +1918,15 @@ fn is_elite(ctx: &ReducerContext, entry: u32) -> bool { .game_creature_template() .entry() .find(entry) - .is_some_and(|template| crate::xp::rank_xp_multiplier(template.rank) > 1) + .is_some_and(|template| crate::actor::rank_xp_multiplier(template.rank) > 1) } /// The shared suitability policy for a fight chosen only for experience. Callers retain ownership /// of their spatial budget, home leash, target rotation, and current-work exclusion. pub(super) fn grind_target_is_worthwhile( ctx: &ReducerContext, - me: &crate::WorldEntity, - target: &crate::WorldEntity, + me: &crate::tables::WorldEntity, + target: &crate::tables::WorldEntity, ) -> bool { !target.is_player() && !target.dead @@ -1976,21 +1949,21 @@ fn has_quest_relation(ctx: &ReducerContext, creature_entry: u32) -> bool { /// Kill something for the experience. What a bot does when no quest it can take is on offer and /// nothing it holds can be worked — a bot standing still in a field reads as broken. /// -/// `crate::faction::is_friendly` is the same predicate the attack core's own gate uses, so a bot +/// `crate::actor::is_friendly` is the same predicate the attack core's own gate uses, so a bot /// picks exactly the targets a player could: hostile and neutral, never green. [`worth_grinding`] /// narrows that to the ones it can actually beat. With nothing in the band in sight the bot goes /// back to wandering, which is the right answer — a bot that stands still reads as broken, but a /// bot that throws itself at a level 60 elite reads as broken faster. fn grind( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, bot: &PlayerbotsBot, personality: &PlayerbotsPersonality, - sight: &[crate::WorldEntity], + sight: &[crate::tables::WorldEntity], ) -> Option { let victim = pick_near(me, sight, pick_salt(ctx, me.guid), |target| { grind_target_is_worthwhile(ctx, me, target) - && !crate::faction::is_friendly(ctx, me.faction_template, target.faction_template) + && !crate::actor::is_friendly(ctx, me.faction_template, target.faction_template) && live_target_is_available(ctx, me.guid, target) })?; fight(ctx, me, bot, None, personality, victim.guid); @@ -2007,16 +1980,16 @@ fn grind( /// slot against its own looting. fn take_what_the_kill_left( ctx: &ReducerContext, - me: &crate::WorldEntity, - sight: &[crate::WorldEntity], + me: &crate::tables::WorldEntity, + sight: &[crate::tables::WorldEntity], wanted: &[u32], ) { for corpse in sight .iter() .filter(|e| e.dead && !e.is_player() && distance_2d(me.x, me.y, e.x, e.y) <= LOOT_RANGE_YD) { - let recipients = crate::loot::corpse_eligible_recipients(ctx, corpse.guid); - if !crate::loot::corpse_eligible_for_access(&recipients, me.guid) { + let recipients = crate::actor::corpse_eligible_recipients(ctx, corpse.guid); + if !crate::actor::corpse_eligible_for_access(&recipients, me.guid) { continue; } let result = loot_eligible_corpse( @@ -2034,7 +2007,7 @@ fn take_what_the_kill_left( .collect() } }, - || crate::items::has_free_slot(ctx, me.guid), + || crate::actor::has_free_slot(ctx, me.guid), |action| match action { CorpseLootAction::Money => crate::actor::loot_money(ctx, me.guid, corpse.guid), CorpseLootAction::Item(slot) => { @@ -2111,27 +2084,30 @@ fn offers(ctx: &ReducerContext, entry: u32, quest_entry: u32, role: u8) -> bool /// The closest entity in `sight` that `wanted` accepts. fn nearest<'a>( - me: &crate::WorldEntity, - sight: &'a [crate::WorldEntity], - mut wanted: impl FnMut(&crate::WorldEntity) -> bool, -) -> Option<&'a crate::WorldEntity> { + me: &crate::tables::WorldEntity, + sight: &'a [crate::tables::WorldEntity], + mut wanted: impl FnMut(&crate::tables::WorldEntity) -> bool, +) -> Option<&'a crate::tables::WorldEntity> { sight .iter() .filter(|e| e.guid != me.guid && wanted(e)) - .fold(None, |best: Option<(&crate::WorldEntity, f32)>, e| { - let d = distance_2d(me.x, me.y, e.x, e.y); - match best { - Some((_, bd)) if bd <= d => best, - _ => Some((e, d)), - } - }) + .fold( + None, + |best: Option<(&crate::tables::WorldEntity, f32)>, e| { + let d = distance_2d(me.x, me.y, e.x, e.y); + match best { + Some((_, bd)) if bd <= d => best, + _ => Some((e, d)), + } + }, + ) .map(|(e, _)| e) } /// A giver or ender a bot can talk to from the ground it walks on. The core's accept and turn-in /// gates measure the full 3D distance (10 yd), while the walk only closes the 2D gap, so a /// creature on an upper floor or a ledge would be walked to and then refused every tick. -fn talkable(me: &crate::WorldEntity, e: &crate::WorldEntity) -> bool { +fn talkable(me: &crate::tables::WorldEntity, e: &crate::tables::WorldEntity) -> bool { (e.z - me.z).abs() <= INTERACT_RANGE_YD } @@ -2144,12 +2120,12 @@ pub(super) const PICK_AMONG_NEAREST: usize = 3; /// bot's guid mixed with the time window, so two bots on the same spot choose differently and one /// bot keeps its choice for a few seconds rather than flipping every tick. fn pick_near<'a>( - me: &crate::WorldEntity, - sight: &'a [crate::WorldEntity], + me: &crate::tables::WorldEntity, + sight: &'a [crate::tables::WorldEntity], salt: u64, - mut wanted: impl FnMut(&crate::WorldEntity) -> bool, -) -> Option<&'a crate::WorldEntity> { - let mut candidates: Vec<(&crate::WorldEntity, f32)> = sight + mut wanted: impl FnMut(&crate::tables::WorldEntity) -> bool, +) -> Option<&'a crate::tables::WorldEntity> { + let mut candidates: Vec<(&crate::tables::WorldEntity, f32)> = sight .iter() .filter(|e| e.guid != me.guid && wanted(e)) .map(|e| (e, distance_2d(me.x, me.y, e.x, e.y))) @@ -2184,23 +2160,23 @@ pub(super) fn pick_salt(ctx: &ReducerContext, character_guid: u64) -> u64 { /// handles that case through its typed AreaTrigger path before legacy goals run. fn follow_across_partitions( ctx: &ReducerContext, - me: &crate::WorldEntity, - leader: &crate::WorldEntity, + me: &crate::tables::WorldEntity, + leader: &crate::tables::WorldEntity, ) -> bool { - if crate::helpers::in_same_partition(leader, me.map_id, me.instance_id) { + if crate::actor::in_same_partition(leader, me.map_id, me.instance_id) { return false; } let _ = crate::actor::stop_attack(ctx, me.guid); // A leg that was playing on the old map would interpolate the bot across the world. ctx.db.game_creature_spline().guid().delete(me.guid); - let Ok(mut moved) = crate::helpers::live_entity(ctx, me.guid) else { + let Ok(mut moved) = crate::actor::live_entity(ctx, me.guid) else { return false; }; moved.map_id = leader.map_id; moved.instance_id = leader.instance_id; place(&mut moved, leader.x, leader.y, leader.z); ctx.db.game_world_entity().guid().update(moved); - if let Some(mut character) = crate::helpers::character_by_guid(ctx, me.guid) { + if let Some(mut character) = crate::actor::character_by_guid(ctx, me.guid) { character.map_id = leader.map_id; character.x = leader.x; character.y = leader.y; @@ -2210,8 +2186,8 @@ fn follow_across_partitions( true } -fn follow_leader(ctx: &ReducerContext, me: &crate::WorldEntity, leader_guid: u64) { - let Ok(leader) = crate::helpers::live_entity(ctx, leader_guid) else { +fn follow_leader(ctx: &ReducerContext, me: &crate::tables::WorldEntity, leader_guid: u64) { + let Ok(leader) = crate::actor::live_entity(ctx, leader_guid) else { return; }; if distance_2d(me.x, me.y, leader.x, leader.y) <= FOLLOW_RANGE_YD { @@ -2228,7 +2204,7 @@ fn follow_leader(ctx: &ReducerContext, me: &crate::WorldEntity, leader_guid: u64 /// An ungrouped bot mills about near its home point, so a realm with bots in it looks inhabited /// rather than staged. -fn wander(ctx: &ReducerContext, me: &crate::WorldEntity, bot: &PlayerbotsBot) { +fn wander(ctx: &ReducerContext, me: &crate::tables::WorldEntity, bot: &PlayerbotsBot) { let window = ctx.timestamp.to_micros_since_unix_epoch() / WANDER_LEG_MICROS; let (dx, dy) = wander_offset(bot.character_guid, window); let dest = (bot.home_x + dx, bot.home_y + dy, bot.home_z); @@ -2259,7 +2235,7 @@ pub(crate) fn wander_offset(character_guid: u64, window: i64) -> (f32, f32) { /// writes, so the Gateway relays it through the one movement path it already has. pub(super) fn walk_toward( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, dest: (f32, f32, f32), stand_off: f32, run: bool, @@ -2271,7 +2247,7 @@ pub(super) fn walk_toward( }; let full = distance_2d(me.x, me.y, dest.0, dest.1); let step = step_length(full, stand_off, speed, THINK_INTERVAL_MICROS); - let route = crate::nav::route_step( + let route = crate::actor::movement::route_step( ctx, me.map_id, me.instance_id, @@ -2297,12 +2273,12 @@ pub(super) fn walk_toward( } // The creature leg, verbatim: the core snaps the landing to the ground, relays the spline, and // its advance pass glides the row along it tick by tick. The bot does not jump to the leg end. - let Ok(mut mover) = crate::helpers::live_entity(ctx, me.guid) else { + let Ok(mut mover) = crate::actor::live_entity(ctx, me.guid) else { return; }; mover.orientation = (ly - me.y).atan2(lx - me.x); let now_ms = (ctx.timestamp.to_micros_since_unix_epoch() / 1000) as u32; - crate::creatures::tick::emit_creature_leg( + crate::actor::movement::emit_creature_leg( ctx, mover, (lx, ly), @@ -2328,7 +2304,7 @@ pub(crate) fn step_length(distance: f32, stand_off: f32, speed: f32, interval_mi /// Write a position and everything the spatial indexes derive from it. One place, so a move can /// never leave the grid columns naming a cell the entity is no longer in. -fn place(entity: &mut crate::WorldEntity, x: f32, y: f32, z: f32) { +fn place(entity: &mut crate::tables::WorldEntity, x: f32, y: f32, z: f32) { let (grid_x, grid_y) = lyracore_shared::spatial::grid_cell(x, y); entity.x = x; entity.y = y; diff --git a/playerbots/src/goals/tests.rs b/playerbots/src/goals/tests.rs index 7216877..1736e7d 100644 --- a/playerbots/src/goals/tests.rs +++ b/playerbots/src/goals/tests.rs @@ -1,6 +1,6 @@ #![cfg(test)] -use crate::package_test::{ +use crate::package::test::{ ask_offline, code_of, read_scanned, shape_of, EntityView, RuntimeScript, }; @@ -349,12 +349,12 @@ fn a_hostile_rotation_target_requires_live_target_ownership() { #[test] fn an_eligible_corpse_is_considered_for_loot() { - assert!(crate::loot::corpse_eligible_for_access(&[11, 17, 23], 17)); + assert!(crate::actor::corpse_eligible_for_access(&[11, 17, 23], 17)); } #[test] fn a_foreign_corpse_is_skipped_before_loot_work() { - assert!(!crate::loot::corpse_eligible_for_access(&[11, 23], 17)); + assert!(!crate::actor::corpse_eligible_for_access(&[11, 23], 17)); } #[test] @@ -513,7 +513,7 @@ fn an_elite_is_never_grind_bait_however_low_it_is() { // ---- what a bot can finish ----------------------------------------------------------------- -use crate::quest::objective_kind::{ +use crate::actor::objective_kind::{ COLLECT_ITEM, EXPLORE_AREATRIGGER, KILL_CREATURE, USE_GAMEOBJECT, }; diff --git a/playerbots/src/imported_fixture.rs b/playerbots/src/imported_fixture.rs index 1d4adac..b5ae137 100644 --- a/playerbots/src/imported_fixture.rs +++ b/playerbots/src/imported_fixture.rs @@ -1,4 +1,4 @@ -use crate::{game_config, game_start_position, game_world_entity}; +use crate::tables::{game_config, game_start_position, game_world_entity}; use spacetimedb::{reducer, table, ReducerContext, SpacetimeType, Table}; use super::{pkg_playerbots_bot, Controller}; @@ -29,7 +29,7 @@ pub struct ImportedFixture { pub bots: Vec, pub source_revision: String, pub dump_sha256: String, - pub navigation: crate::nav::NavigationInputs, + pub navigation: crate::actor::movement::NavigationInputs, pub staged_micros: i64, pub started_micros: Option, } @@ -38,7 +38,7 @@ fn imported_inputs( ctx: &ReducerContext, source_revision: &str, dump_sha256: &str, -) -> Result { +) -> Result { for (value, length) in [(source_revision, 40), (dump_sha256, 64)] { if value.len() != length || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) { return Err("imported fixture needs exact source and dump identities".to_string()); @@ -63,7 +63,7 @@ fn imported_inputs( return Err(format!("imported fixture has different {family} inputs")); } } - let navigation = crate::nav::inputs(ctx, 0); + let navigation = crate::actor::movement::inputs(ctx, 0); if !navigation.navigation_enabled || !navigation.collision_enabled || !navigation.coverage_enabled @@ -89,7 +89,7 @@ pub fn playerbots_imported_stage( source_revision: String, dump_sha256: String, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if !(1..=30).contains(&seed) { return Err("imported fixture needs seed 1 through 30".to_string()); } @@ -131,12 +131,12 @@ pub fn playerbots_imported_stage( } let spot = super::spawn_spot(ctx, (start.x, start.y, start.z), candidate_index); candidate_index += 1; - let ground = crate::terrain::ground_z(ctx, start.map_id, spot.0, spot.1); + let ground = crate::actor::movement::ground_z(ctx, start.map_id, spot.0, spot.1); let separated = bots .iter() .all(|bot| (bot.x - spot.0).hypot(bot.y - spot.1) > IMPORTED_SPOT_CLEARANCE_YD); if ground.is_some_and(f32::is_finite) - && crate::nav::walkable(ctx, start.map_id, spot.0, spot.1) == Some(true) + && crate::actor::movement::walkable(ctx, start.map_id, spot.0, spot.1) == Some(true) && spot.2.is_finite() && separated { @@ -196,7 +196,7 @@ pub fn playerbots_imported_stage( pub fn playerbots_fixture_imported_staged(ctx: &ReducerContext) -> Result<(), String> { use super::runner::pkg_playerbots_runner; - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let fixture = ctx .db .pkg_playerbots_imported_fixture() @@ -207,7 +207,7 @@ pub fn playerbots_fixture_imported_staged(ctx: &ReducerContext) -> Result<(), St || fixture.bots.len() != IMPORTED_BOT_COUNT || ctx.db.pkg_playerbots_bot().count() as usize != IMPORTED_BOT_COUNT || ctx.db.pkg_playerbots_runner().count() as usize != IMPORTED_BOT_COUNT - || crate::nav::inputs(ctx, 0) != fixture.navigation + || crate::actor::movement::inputs(ctx, 0) != fixture.navigation { return Err("imported fixture is not an exact parked roster".to_string()); } @@ -252,9 +252,10 @@ pub fn playerbots_fixture_imported_staged(ctx: &ReducerContext) -> Result<(), St || staged.first_due_offset_micros != expected_offset || runner.objective.is_some() || runner.foreground.is_some() - || !crate::terrain::ground_z(ctx, staged.map_id, staged.x, staged.y) + || !crate::actor::movement::ground_z(ctx, staged.map_id, staged.x, staged.y) .is_some_and(f32::is_finite) - || crate::nav::walkable(ctx, staged.map_id, staged.x, staged.y) != Some(true) + || crate::actor::movement::walkable(ctx, staged.map_id, staged.x, staged.y) + != Some(true) { return Err(format!( "imported bot {index} differs from its parked manifest" @@ -267,7 +268,7 @@ pub fn playerbots_fixture_imported_staged(ctx: &ReducerContext) -> Result<(), St /// Start the complete roster once. The ordinary scheduler owns all later progression. #[reducer] pub fn playerbots_imported_begin(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut fixture = ctx .db .pkg_playerbots_imported_fixture() diff --git a/playerbots/src/load_fixture.rs b/playerbots/src/load_fixture.rs index 618f6bc..f0a4a4a 100644 --- a/playerbots/src/load_fixture.rs +++ b/playerbots/src/load_fixture.rs @@ -1,5 +1,7 @@ -use crate::nav::game_nav_chunk; -use crate::terrain::game_terrain_chunk; +#![cfg(feature = "debug_reducers")] + +use crate::tables::game_nav_chunk; +use crate::tables::game_terrain_chunk; use spacetimedb::{reducer, table, ReducerContext, SpacetimeType, Table}; use super::{pkg_playerbots_bot, pkg_playerbots_runner, Controller}; @@ -58,7 +60,7 @@ fn route_geometry(ctx: &ReducerContext) -> Result<(Vec, Vec Result<(Vec, Vec Result<(Vec, Vec Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if !matches!(count, 10 | 25 | 100 | 250 | 500 | 1_000) || !(1..=30).contains(&seed) { return Err( "load fixture needs 10, 25, 100, 250, 500 or 1000 bots and seed 1 through 30" @@ -172,7 +174,7 @@ pub fn playerbots_load_stage(ctx: &ReducerContext, count: u32, seed: u32) -> Res #[reducer] pub fn playerbots_load_begin(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut fixture = ctx .db .pkg_playerbots_load_fixture() @@ -211,7 +213,7 @@ pub fn playerbots_load_foreign_motion( guid: u64, duration_ms: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let staged = ctx .db .pkg_playerbots_load_fixture() @@ -243,8 +245,8 @@ pub fn playerbots_load_foreign_motion( .ok_or("selected load bot is absent")?; bot.next_think_micros = i64::MAX; ctx.db.pkg_playerbots_bot().id().update(bot); - let me = crate::helpers::live_entity(ctx, guid)?; - crate::creatures::tick::emit_move_spline( + let me = crate::actor::live_entity(ctx, guid)?; + crate::package::fixture::emit_move_spline( ctx, guid, (me.x, me.y, me.z), diff --git a/playerbots/src/mod.rs b/playerbots/src/mod.rs index c705a43..0cd07bf 100644 --- a/playerbots/src/mod.rs +++ b/playerbots/src/mod.rs @@ -44,11 +44,11 @@ mod acceptance_fixture; #[cfg(feature = "debug_reducers")] pub(crate) use acceptance_fixture::*; #[cfg(feature = "debug_reducers")] +mod fixture; +#[cfg(feature = "debug_reducers")] mod imported_fixture; #[cfg(feature = "debug_reducers")] mod load_fixture; -#[cfg(feature = "debug_reducers")] -mod fixture; mod spell_ranks; #[cfg(feature = "debug_reducers")] mod transfer_assist_fixture; @@ -58,9 +58,9 @@ pub(crate) use transfer_assist_fixture::{ sweep_transfer_pkg_playerbots_transfer_assist_source, }; #[cfg(feature = "debug_reducers")] -mod transfer_orders_fixture; +mod action_lifecycle_fixture; #[cfg(feature = "debug_reducers")] -mod transfer_recovery_fixture; +mod companion_acceptance_fixture; #[cfg(feature = "debug_reducers")] mod quest_catalog_fixture; #[cfg(feature = "debug_reducers")] @@ -68,20 +68,19 @@ mod transfer_destination_fixture; #[cfg(feature = "debug_reducers")] mod transfer_fixture; #[cfg(feature = "debug_reducers")] -mod transfer_lifecycle_fixture; -#[cfg(feature = "debug_reducers")] mod transfer_gateway_fixture; #[cfg(feature = "debug_reducers")] -mod action_lifecycle_fixture; +mod transfer_lifecycle_fixture; #[cfg(feature = "debug_reducers")] -mod companion_acceptance_fixture; +mod transfer_orders_fixture; +#[cfg(feature = "debug_reducers")] +mod transfer_recovery_fixture; #[cfg(feature = "debug_reducers")] pub(crate) use companion_acceptance_fixture::{ playerbots_companion_acceptance_observe_damage, playerbots_companion_acceptance_observe_finished_cast, playerbots_companion_acceptance_observe_resolved_cast, - playerbots_companion_acceptance_observe_tick, - sweep_delete_pkg_playerbots_companion_acceptance, + playerbots_companion_acceptance_observe_tick, sweep_delete_pkg_playerbots_companion_acceptance, sweep_delete_pkg_playerbots_companion_cast_receipt, sweep_delete_pkg_playerbots_companion_combat_receipt, sweep_delete_pkg_playerbots_companion_fault, @@ -96,13 +95,13 @@ pub(crate) use companion_acceptance_fixture::{ }; #[cfg(feature = "debug_reducers")] mod controller_transition_fixture; -#[cfg(feature = "debug_reducers")] -pub(crate) use quest_catalog_fixture::*; pub(crate) use goals::*; pub(crate) use orders::*; +#[cfg(feature = "debug_reducers")] +pub(crate) use quest_catalog_fixture::*; -use crate::package_config::game_package_config; -use crate::{game_character, game_world_entity}; +use crate::tables::game_package_config; +use crate::tables::{game_character, game_world_entity}; /// The Package Config namespace and the Package name the Trust Review prints. pub(crate) const PACKAGE: &str = "playerbots"; @@ -444,7 +443,7 @@ fn config_parsed(ctx: &ReducerContext, key: &str, fallback pub(crate) fn ensure_defaults(ctx: &ReducerContext) { crate::actor::reconcile_starter_role_spell_levels(ctx); for (key, value) in CONFIG_DEFAULTS { - crate::package_config::ensure_package_config_default(ctx, PACKAGE, key, value); + crate::package::ensure_config_default(ctx, PACKAGE, key, value); } quest_catalog::ensure_catalog(ctx); if config_value(ctx, "class_defaults_revision").is_none() { @@ -454,12 +453,7 @@ pub(crate) fn ensure_defaults(ctx: &ReducerContext) { seed_class_role_data(ctx); } } - crate::package_config::ensure_package_config_default( - ctx, - PACKAGE, - "class_defaults_revision", - "1", - ); + crate::package::ensure_config_default(ctx, PACKAGE, "class_defaults_revision", "1"); } type RotationSeed = (u8, u8, u8, u32, u8, u8); @@ -768,8 +762,8 @@ fn spawn_one( let (x, y, z) = at; let name = first_free_name(ctx, name_stem) .ok_or_else(|| format!("no free bot name left for stem '{name_stem}'"))?; - let guid = crate::package_account::create_package_character(ctx, PACKAGE, &name, race, class)?; - crate::stats::set_character_level(ctx, guid, level as u32)?; + let guid = crate::package::create_character(ctx, PACKAGE, &name, race, class)?; + crate::package::character::set_character_level(ctx, guid, level as u32)?; // The Character was created at its class start position; move it to where the Operator asked // for it, and make that its home so a logout-equivalent never drags it back. Read after @@ -789,7 +783,7 @@ fn spawn_one( character.home_z = z; let character = chars.guid().update(character); let account_id = character.account_id; - let entity = crate::creatures::build_player_entity(ctx, &character, Identity::ZERO); + let entity = crate::package::character::build_player_entity(ctx, &character, Identity::ZERO); ctx.db.game_world_entity().insert(entity); let (flee_at_pct, heal_at_pct) = role_personality_defaults(role); @@ -886,7 +880,7 @@ fn spawn_spot(ctx: &ReducerContext, at: (f32, f32, f32), index: usize) -> (f32, ( x, y, - crate::terrain::snap_z(ctx, SPAWN_MAP_ID, 0, x, y, at.2), + crate::actor::movement::snap_z(ctx, SPAWN_MAP_ID, 0, x, y, at.2), ) } @@ -921,7 +915,7 @@ pub fn playerbots_spawn( y: f32, z: f32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; ensure_defaults(ctx); let level = spawn_level(ctx); let stems = NAME_STEMS; @@ -950,7 +944,7 @@ pub fn playerbots_spawn_role( z: f32, role: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; ensure_defaults(ctx); let class = default_class_for_role(role).ok_or_else(|| cannot_fill_role_message(0, role))?; spawn_batch(ctx, count, (x, y, z), class, role, role_name_stem(role)) @@ -968,7 +962,7 @@ pub fn playerbots_spawn_class_role( class: u8, role: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; ensure_defaults(ctx); spawn_batch(ctx, count, (x, y, z), class, role, role_name_stem(role)) } @@ -989,7 +983,7 @@ crate::game_package_characters!(fn playerbots_characters(ctx) { /// sweeps, so there is one delete path and nothing to keep in step with it. #[reducer] pub fn playerbots_despawn_all(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let guids: Vec = ctx .db .pkg_playerbots_bot() @@ -999,8 +993,8 @@ pub fn playerbots_despawn_all(ctx: &ReducerContext) -> Result<(), String> { for guid in guids { // A bot in a party must leave it first: the cascade deletes the Character, and a member row // pointing at a Character that no longer exists is a party the survivors cannot dissolve. - let _ = crate::group::leave_group_for(ctx, guid); - crate::world::cascade_delete_character(ctx, guid); + let _ = crate::actor::leave_group_for(ctx, guid); + crate::package::character::cascade_delete_character(ctx, guid); } Ok(()) } @@ -1017,7 +1011,7 @@ pub(crate) fn populate_shortfall(existing: usize, target: usize) -> usize { /// on publish — a realm gets bots when its Operator asks for them. #[reducer] pub fn playerbots_populate(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; ensure_defaults(ctx); let target = config_parsed::(ctx, "population_count", 0); let existing = ctx.db.pkg_playerbots_bot().count() as usize; diff --git a/playerbots/src/orders.rs b/playerbots/src/orders.rs index 6d39a49..5564b29 100644 --- a/playerbots/src/orders.rs +++ b/playerbots/src/orders.rs @@ -1,7 +1,7 @@ //! Authenticated companion orders and their runner-facing policy. use super::{pkg_playerbots_bot, Controller}; -use crate::{game_world_entity, WorldEntity}; +use crate::tables::{game_world_entity, WorldEntity}; use spacetimedb::{table, ReducerContext, Table}; pub(crate) const FOLLOW: u8 = 0; diff --git a/playerbots/src/population.rs b/playerbots/src/population.rs index 93f3209..4a7af8d 100644 --- a/playerbots/src/population.rs +++ b/playerbots/src/population.rs @@ -1,6 +1,6 @@ //! Bounded Operator batches across imported starting areas. -use crate::{game_start_position, game_world_entity}; +use crate::tables::{game_start_position, game_world_entity}; use spacetimedb::{reducer, ReducerContext, Table}; use super::{class, pkg_playerbots_bot, Controller, ROLE_DPS, ROLE_HEALER, ROLE_TANK}; @@ -31,7 +31,7 @@ fn connected_position( } budget.routes -= 1; // A clear direct route remains available after the search allowance is spent. - let route = crate::nav::route_path_with_budget( + let route = crate::actor::movement::route_path_with_budget( ctx, map_id, 0, @@ -41,14 +41,14 @@ fn connected_position( budget.expansions.min(4096), ); budget.expansions = budget.expansions.saturating_sub(route.step.expansions); - if route.step.status != crate::nav::RouteStatus::Complete + if route.step.status != crate::actor::movement::RouteStatus::Complete || route.step.clipping.is_some() || route.points.is_empty() { return None; } for point in route.points { - if !crate::nav::route_segment_clear(ctx, map_id, 0, at, point) { + if !crate::actor::movement::route_segment_clear(ctx, map_id, 0, at, point) { return None; } at = point; @@ -97,7 +97,7 @@ pub fn playerbots_spawn_starting_area( count: u32, controller: Controller, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; super::capacity::require_capacity(ctx)?; if !(1..=50).contains(&count) { return Err("starting-area batches must contain 1 through 50 bots".to_string()); @@ -106,7 +106,7 @@ pub fn playerbots_spawn_starting_area( return Err("starting-area bots require Cohort or Frozen control".to_string()); } let area = starting_area(&area)?; - if !crate::nav::inputs(ctx, area.map_id).navigation_enabled { + if !crate::actor::movement::inputs(ctx, area.map_id).navigation_enabled { return Err("starting-area bots require enabled navigation".to_string()); } super::ensure_defaults(ctx); @@ -134,10 +134,10 @@ pub fn playerbots_spawn_starting_area( .find_map(|attempt| { let (dx, dy) = starting_offset(ordinal * 100 + attempt); let (x, y) = (start.x + dx, start.y + dy); - let z = crate::terrain::ground_z(ctx, area.map_id, x, y)?; + let z = crate::actor::movement::ground_z(ctx, area.map_id, x, y)?; if !(z.is_finite() - && crate::terrain::zone_id_at(ctx, area.map_id, x, y) == Some(area.zone_id) - && crate::nav::walkable(ctx, area.map_id, x, y) == Some(true)) + && crate::actor::movement::zone_id_at(ctx, area.map_id, x, y) == Some(area.zone_id) + && crate::actor::movement::walkable(ctx, area.map_id, x, y) == Some(true)) { return None; } diff --git a/playerbots/src/provisioning.rs b/playerbots/src/provisioning.rs index 55b3621..221d6e6 100644 --- a/playerbots/src/provisioning.rs +++ b/playerbots/src/provisioning.rs @@ -4,7 +4,7 @@ use super::{ class, pkg_playerbots_bot, pkg_playerbots_kit, Controller, PlayerbotsBot, ROLE_DPS, ROLE_HEALER, }; -use crate::{game_item_instance, game_spell, game_world_entity}; +use crate::tables::{game_item_instance, game_spell, game_world_entity}; use spacetimedb::{table, ReducerContext, Table}; const PROFILE_REVISION: u32 = 3; diff --git a/playerbots/src/quest_catalog.rs b/playerbots/src/quest_catalog.rs index e6edb38..846cc8c 100644 --- a/playerbots/src/quest_catalog.rs +++ b/playerbots/src/quest_catalog.rs @@ -1,6 +1,6 @@ //! A versioned catalog of quest work this Package can execute with current core operations. -use crate::{ +use crate::tables::{ game_character_quest, game_creature_loot, game_creature_quest, game_gameobject_loot, game_gameobject_quest, game_gameobject_template, game_item_template, game_quest_cast_objective, game_quest_event_requirement, game_quest_objective, game_quest_template, game_world_entity, @@ -696,7 +696,7 @@ fn destinations( ) -> Vec { let mut found: Vec<_> = match entity.kind { CatalogEntityKind::Creature => { - crate::creatures::creature_spawn_evidence(ctx, entity.entry, map_id, DESTINATION_LIMIT) + crate::actor::creature_spawn_evidence(ctx, entity.entry, map_id, DESTINATION_LIMIT) .into_iter() .map(|spawn| CatalogDestination { kind: entity.kind, @@ -710,7 +710,7 @@ fn destinations( }) .collect() } - CatalogEntityKind::GameObject => crate::gameobject::gameobject_destination_evidence( + CatalogEntityKind::GameObject => crate::actor::gameobject_destination_evidence( ctx, entity.entry, map_id, @@ -858,14 +858,14 @@ fn observed_content_revision(ctx: &ReducerContext) -> String { definition.start.kind, definition.start.entry, definition.entry, - crate::quest::quest_role::START, + crate::actor::quest_role::START, ) as u8, relation_exists( ctx, definition.actual_ender.kind, definition.actual_ender.entry, definition.entry, - crate::quest::quest_role::END, + crate::actor::quest_role::END, ) as u8, ]); let mut entity_destinations = destinations(ctx, definition.start, definition.map_id, 0); @@ -1458,7 +1458,7 @@ fn source_gate( ctx: &ReducerContext, catalog: &PlayerbotsCatalogObjective, require_current_destination: bool, - character: &crate::WorldEntity, + character: &crate::tables::WorldEntity, ) -> Result, AdmissionRefusal> { let mut entries = Vec::new(); match catalog.executor { @@ -1492,7 +1492,7 @@ fn source_gate( }; has_template = true; let simple = - template.type_id == crate::gameobject::go_type::CHEST && template.lock_id == 0; + template.type_id == crate::actor::go_type::CHEST && template.lock_id == 0; has_simple_chest |= simple; if simple && ctx @@ -1530,8 +1530,7 @@ fn source_gate( .entry() .find(entry) .is_some_and(|template| { - template.type_id == crate::gameobject::go_type::GOOBER - && template.lock_id == 0 + template.type_id == crate::actor::go_type::GOOBER && template.lock_id == 0 }) })); if entries.is_empty() { @@ -1583,11 +1582,11 @@ enum AdmissionKind { fn executor_gate( ctx: &ReducerContext, - character: &crate::WorldEntity, + character: &crate::tables::WorldEntity, admission_kind: AdmissionKind, quest: &PlayerbotsCatalogQuest, catalog: &PlayerbotsCatalogObjective, - core: Option<&crate::QuestObjective>, + core: Option<&crate::tables::QuestObjective>, ) -> Result, AdmissionRefusal> { match catalog.kind { CatalogObjectiveKind::ExploreAreaTrigger => { @@ -1642,7 +1641,7 @@ fn executor_gate( "kill objective is absent from core content", )); }; - if core.kind != crate::quest::objective_kind::KILL_CREATURE + if core.kind != crate::actor::objective_kind::KILL_CREATURE || core.target_entry != catalog.target_entry || core.required_count != catalog.required_count || catalog.executor != ObjectiveExecutor::Attack @@ -1666,7 +1665,7 @@ fn executor_gate( "collect objective is absent from core content", )); }; - if core.kind != crate::quest::objective_kind::COLLECT_ITEM + if core.kind != crate::actor::objective_kind::COLLECT_ITEM || core.target_entry != catalog.target_entry || core.required_count != catalog.required_count { @@ -1709,7 +1708,7 @@ fn executor_gate( )); } if admission_kind == AdmissionKind::Held - && crate::items::item_count(ctx, character.guid, catalog.target_entry) + && crate::actor::item_count(ctx, character.guid, catalog.target_entry) < catalog.required_count { return Err(missing( @@ -1740,7 +1739,7 @@ fn executor_gate( "GameObject objective is absent from core content", )); }; - if core.kind != crate::quest::objective_kind::USE_GAMEOBJECT + if core.kind != crate::actor::objective_kind::USE_GAMEOBJECT || core.target_entry != catalog.target_entry || core.required_count != catalog.required_count || catalog.executor != ObjectiveExecutor::SimpleGameObject @@ -1812,7 +1811,7 @@ fn inspect( .find(character_guid) .ok_or_else(|| AdmissionRefusal::Ineligible("Character is not in the world".to_string()))?; if admission_kind == AdmissionKind::Available { - crate::quest::accept_gates(ctx, &character, &template) + crate::actor::quest_acceptance(ctx, &character, &template) .map_err(AdmissionRefusal::Ineligible)?; if !quest.start_destinations.is_empty() && !quest.start_destinations.iter().any(|destination| { @@ -1832,7 +1831,7 @@ fn inspect( quest.start_kind, quest.start_entry, quest_entry, - crate::quest::quest_role::START, + crate::actor::quest_role::START, ) { return Err(missing( MissingCapability::MissingStartRelation, @@ -1844,7 +1843,7 @@ fn inspect( quest.actual_ender_kind, quest.actual_ender_entry, quest_entry, - crate::quest::quest_role::END, + crate::actor::quest_role::END, ) { return Err(missing( MissingCapability::MissingActualEndRelation, @@ -1907,15 +1906,15 @@ fn inspect( )?; gated.push((catalog_objective, source)); } - let held = crate::quest::character_quest_row(ctx, character_guid, quest_entry); + let held = crate::actor::character_quest(ctx, character_guid, quest_entry); let (selected, source) = gated .iter() .find(|(objective, _)| { core.iter() .find(|core| core.obj_index == objective.objective_index) .is_none_or(|core| { - let count = if core.kind == crate::quest::objective_kind::COLLECT_ITEM { - crate::items::item_count(ctx, character_guid, core.target_entry) + let count = if core.kind == crate::actor::objective_kind::COLLECT_ITEM { + crate::actor::item_count(ctx, character_guid, core.target_entry) } else { held.as_ref() .and_then(|row| row.counts.get(core.obj_index as usize).copied()) @@ -2019,7 +2018,7 @@ fn inspect( }); let complete = held .as_ref() - .is_some_and(|row| crate::quest::quest_is_complete(ctx, row)); + .is_some_and(|row| crate::actor::quest_complete(ctx, row)); let destination = if admission_kind == AdmissionKind::Available { start .clone() @@ -2073,7 +2072,7 @@ pub(super) fn admit_held( character_guid: u64, quest_entry: u32, ) -> Result { - let held = crate::quest::character_quest_row(ctx, character_guid, quest_entry) + let held = crate::actor::character_quest(ctx, character_guid, quest_entry) .is_some_and(|row| !row.rewarded && !row.failed); if !held { return Err(AdmissionRefusal::Ineligible( @@ -2121,9 +2120,9 @@ pub(super) fn reconcile_active( .game_character_quest() .by_character_active() .filter((character_guid, false, false)) - .take(crate::quest::MAX_QUEST_LOG_SIZE + 1) + .take(crate::actor::MAX_QUEST_LOG_SIZE + 1) .collect(); - if active.len() > crate::quest::MAX_QUEST_LOG_SIZE { + if active.len() > crate::actor::MAX_QUEST_LOG_SIZE { return ReconcileResult::ReadLimit; } let catalog = ctx.db.pkg_playerbots_catalog_quest(); @@ -2316,7 +2315,7 @@ pub(super) fn active_wait_until(ctx: &ReducerContext, character_guid: u64) -> Op .character_guid() .find(character_guid) .filter(|row| row.state == QuestAdmissionState::Waiting)?; - crate::quest::character_quest_row(ctx, character_guid, admission.considered_quest) + crate::actor::character_quest(ctx, character_guid, admission.considered_quest) .is_some_and(|quest| !quest.rewarded && !quest.failed) .then_some(admission.wait_until_micros) } diff --git a/playerbots/src/quest_catalog_fixture.rs b/playerbots/src/quest_catalog_fixture.rs index 5c5f90a..a7a5784 100644 --- a/playerbots/src/quest_catalog_fixture.rs +++ b/playerbots/src/quest_catalog_fixture.rs @@ -10,7 +10,7 @@ use super::quest_catalog::{ ObjectiveExecutor, PlayerbotsCatalogObjective, PlayerbotsCatalogQuest, CATALOG_REVISION, CATALOG_WALK_LIMIT, }; -use crate::{ +use crate::tables::{ game_character_quest, game_corpse_loot, game_creature_loot, game_creature_quest, game_creature_spawn, game_creature_spline, game_creature_template, game_faction_template, game_gameobject, game_gameobject_loot, game_gameobject_quest, game_gameobject_template, @@ -153,7 +153,7 @@ crate::game_hook!(on_loot, fn playerbots_quest_fixture_looted(ctx, payload) { return; } let rows = ctx.db.pkg_playerbots_quest_loot_receipt_fixture(); - let carried_count = crate::items::item_count(ctx, payload.looter_guid, payload.item_entry); + let carried_count = crate::actor::item_count(ctx, payload.looter_guid, payload.item_entry); if let Some(mut row) = rows.character_guid().find(payload.looter_guid) { row.received_count = row.received_count.saturating_add(payload.count); row.peak_carried_count = row.peak_carried_count.max(carried_count); @@ -216,7 +216,7 @@ enum FixtureStage { } fn require_fixture(ctx: &ReducerContext) -> Result<(), String> { - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; let ownership = ctx .db .pkg_playerbots_quest_fixture_ownership() @@ -236,7 +236,7 @@ pub fn playerbots_recovery_fixture_block_quest_target( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let target = ctx .db @@ -245,9 +245,9 @@ pub fn playerbots_recovery_fixture_block_quest_target( .find(creature_guid(6)) .ok_or("quest target missing")?; super::fixture::playerbots_fixture_position(ctx, character_guid, target.x - 3.0)?; - let me = crate::helpers::live_entity(ctx, character_guid)?; + let me = crate::actor::live_entity(ctx, character_guid)?; block_navigation(ctx, &me)?; - if crate::nav::has_los( + if crate::actor::movement::has_los( ctx, me.map_id, me.instance_id, @@ -259,15 +259,15 @@ pub fn playerbots_recovery_fixture_block_quest_target( super::fixture::playerbots_fixture_runner_select_cohort(ctx, character_guid) } -fn block_navigation(ctx: &ReducerContext, me: &crate::WorldEntity) -> Result<(), String> { +fn block_navigation(ctx: &ReducerContext, me: &crate::tables::WorldEntity) -> Result<(), String> { let cx = lyracore_shared::terrain::cell_index(me.x).ok_or("fixture off grid")?; let cy = lyracore_shared::terrain::cell_index(me.y).ok_or("fixture off grid")?; - use crate::nav::game_nav_chunk; + use crate::tables::game_nav_chunk; for x in cx.saturating_sub(1)..=cx.saturating_add(1).min(1023) { for y in cy.saturating_sub(1)..=cy.saturating_add(1).min(1023) { let key = lyracore_shared::terrain::cell_key(me.map_id, x, y); ctx.db.game_nav_chunk().key().delete(key); - ctx.db.game_nav_chunk().insert(crate::nav::NavChunk { + ctx.db.game_nav_chunk().insert(crate::tables::NavChunk { key, map_id: me.map_id, cell_x: x, @@ -287,11 +287,11 @@ pub fn playerbots_recovery_fixture_partial_route( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; super::fixture::playerbots_fixture_partial_route(ctx, character_guid)?; - let me = crate::helpers::live_entity(ctx, character_guid)?; - use crate::nav::game_nav_chunk; + let me = crate::actor::live_entity(ctx, character_guid)?; + use crate::tables::game_nav_chunk; let mut chunks = std::collections::BTreeMap::new(); for x in -16..=8 { for y in -16..=16 { @@ -307,7 +307,7 @@ pub fn playerbots_recovery_fixture_partial_route( .game_nav_chunk() .key() .find(key) - .unwrap_or(crate::nav::NavChunk { + .unwrap_or(crate::tables::NavChunk { key, map_id: me.map_id, cell_x: cx, @@ -336,9 +336,9 @@ pub fn playerbots_recovery_fixture_block_companion( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; - let me = crate::helpers::live_entity(ctx, character_guid)?; + let me = crate::actor::live_entity(ctx, character_guid)?; block_navigation(ctx, &me)?; super::fixture::playerbots_fixture_runner_select_cohort(ctx, character_guid) } @@ -348,7 +348,7 @@ pub fn playerbots_recovery_fixture_pass_and_park_movement( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; super::fixture::playerbots_fixture_runner_pass_once(ctx, character_guid)?; use super::runner::pkg_playerbots_movement; @@ -365,7 +365,7 @@ pub fn playerbots_recovery_fixture_exhaust_attempt( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; use super::runner::pkg_playerbots_runner; let rows = ctx.db.pkg_playerbots_runner(); @@ -390,7 +390,7 @@ pub fn playerbots_recovery_fixture_expire_destinations( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; use super::runner::pkg_playerbots_runner; let rows = ctx.db.pkg_playerbots_runner(); @@ -413,7 +413,7 @@ pub fn playerbots_recovery_fixture_keep_two_quest_targets( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let fixture = ctx .db @@ -453,7 +453,7 @@ pub fn playerbots_recovery_fixture_expire_quest_target( character_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; use super::runner::pkg_playerbots_runner; let rows = ctx.db.pkg_playerbots_runner(); @@ -538,7 +538,7 @@ fn insert_alternative_gameobject(ctx: &ReducerContext, entry: u32) -> Result Result<(), String> { // The pinned ClassicDB spell_template row supplies the gameplay fields below. The dump omits // auxiliary DBC rows for CastTimeIndex 16, RangeIndex 4, and DurationIndex 0, so this private // fixture declares 1500ms, 30yd, and 0ms for those three values. - ctx.db.game_spell().insert(crate::spell::Spell { + ctx.db.game_spell().insert(crate::tables::Spell { spell_id: LOOPBACK_SMITE, name: "Smite".to_string(), power_type: 0, @@ -631,7 +631,7 @@ pub(super) fn stage_loopback_smite(ctx: &ReducerContext) -> Result<(), String> { }); ctx.db .game_spell_effect() - .insert(crate::spell::SpellEffect { + .insert(crate::tables::SpellEffect { id: 2_340, spell_id: LOOPBACK_SMITE, effect_index: 0, @@ -659,8 +659,8 @@ pub fn playerbots_quest_loop_fixture_stage_simple_gameobject( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; if let Some(existing) = ctx .db .pkg_playerbots_seeded_quest_fixture() @@ -749,7 +749,7 @@ pub fn playerbots_quest_loop_fixture_stage_simple_gameobject( insert_gameobject( ctx, SEEDED_USE_GAMEOBJECT, - crate::gameobject::go_type::GOOBER, + crate::actor::go_type::GOOBER, 0, destination.map_id, destination.instance_id, @@ -757,50 +757,54 @@ pub fn playerbots_quest_loop_fixture_stage_simple_gameobject( destination.y, destination.z, ); - ctx.db.game_quest_template().insert(crate::QuestTemplate { - entry: SEEDED_USE_QUEST, - min_level: 1, - quest_level: 1, - title: "Loopback simple GameObject".to_string(), - reward_money: 3, - reward_xp: 5, - prev_quest_id: 0, - required_races: 77, - required_classes: 0, - zone_or_sort: 9, - rew_rep_faction_1: 0, - rew_rep_value_1: 0, - rew_rep_faction_2: 0, - rew_rep_value_2: 0, - src_item: 0, - src_item_count: 0, - repeatable: false, - next_quest_id: 0, - limit_time: 0, - reward_money_max_level: 0, - quest_type: 0, - }); + ctx.db + .game_quest_template() + .insert(crate::tables::QuestTemplate { + entry: SEEDED_USE_QUEST, + min_level: 1, + quest_level: 1, + title: "Loopback simple GameObject".to_string(), + reward_money: 3, + reward_xp: 5, + prev_quest_id: 0, + required_races: 77, + required_classes: 0, + zone_or_sort: 9, + rew_rep_faction_1: 0, + rew_rep_value_1: 0, + rew_rep_faction_2: 0, + rew_rep_value_2: 0, + src_item: 0, + src_item_count: 0, + repeatable: false, + next_quest_id: 0, + limit_time: 0, + reward_money_max_level: 0, + quest_type: 0, + }); for (id, role) in [ - (SEEDED_USE_RELATION_START, crate::quest::quest_role::START), - (SEEDED_USE_RELATION_END, crate::quest::quest_role::END), + (SEEDED_USE_RELATION_START, crate::actor::quest_role::START), + (SEEDED_USE_RELATION_END, crate::actor::quest_role::END), ] { ctx.db .game_gameobject_quest() - .insert(crate::GameObjectQuest { + .insert(crate::tables::GameObjectQuest { id, go_entry: SEEDED_USE_GAMEOBJECT, quest_entry: SEEDED_USE_QUEST, role, }); } - ctx.db.game_quest_objective().insert(crate::QuestObjective { - id: SEEDED_USE_OBJECTIVE, - quest_entry: SEEDED_USE_QUEST, - obj_index: 0, - kind: crate::quest::objective_kind::USE_GAMEOBJECT, - target_entry: SEEDED_USE_GAMEOBJECT, - required_count: 1, - }); + ctx.db + .game_quest_objective() + .insert(crate::tables::QuestObjective { + id: SEEDED_USE_OBJECTIVE, + quest_entry: SEEDED_USE_QUEST, + obj_index: 0, + kind: crate::actor::objective_kind::USE_GAMEOBJECT, + target_entry: SEEDED_USE_GAMEOBJECT, + required_count: 1, + }); quest_catalog::ensure_catalog(ctx); ctx.db .pkg_playerbots_catalog_quest() @@ -878,7 +882,7 @@ fn relation_exists( } fn stage_gate(ctx: &ReducerContext, character_guid: u64) -> Result { - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; if let Some(ownership) = ctx .db .pkg_playerbots_quest_fixture_ownership() @@ -1010,8 +1014,8 @@ fn stage_gate(ctx: &ReducerContext, character_guid: u64) -> Result template.faction_template = 14; let template = ctx.db.game_creature_template().insert(template); let guid = creature_guid(entry); - let spawn = ctx.db.game_creature_spawn().insert(crate::CreatureSpawn { - guid, - entry, - map_id: 0, - x, - y, - z, - orientation: 0.0, - respawn_at: crate::creatures::timer_never(ctx), - despawn_at: crate::creatures::timer_never(ctx), - movement_type: 0, - respawn_secs: 60, - life_seq: 1, - }); - crate::creatures::insert_creature_entity( + let spawn = ctx + .db + .game_creature_spawn() + .insert(crate::tables::CreatureSpawn { + guid, + entry, + map_id: 0, + x, + y, + z, + orientation: 0.0, + respawn_at: crate::package::fixture::timer_never(ctx), + despawn_at: crate::package::fixture::timer_never(ctx), + movement_type: 0, + respawn_secs: 60, + life_seq: 1, + }); + crate::package::fixture::insert_creature_entity( ctx, - crate::creatures::build_creature_entity(&spawn, &template, 0, 0), + crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0), ); Ok(()) } @@ -1148,7 +1155,7 @@ fn insert_gameobject( ) { ctx.db .game_gameobject_template() - .insert(crate::GameObjectTemplate { + .insert(crate::tables::GameObjectTemplate { entry, type_id, display_id: 259, @@ -1162,7 +1169,7 @@ fn insert_gameobject( size: 1.0, }); let guid = gameobject_guid(entry); - ctx.db.game_gameobject().insert(crate::GameObject { + ctx.db.game_gameobject().insert(crate::tables::GameObject { guid, template_entry: entry, map_id, @@ -1207,29 +1214,31 @@ fn insert_quest( start: u32, actual_ender: u32, ) { - ctx.db.game_quest_template().insert(crate::QuestTemplate { - entry, - min_level, - quest_level: min_level.max(1), - title: format!("Catalog quest {entry}"), - reward_money: 1, - reward_xp: 1, - prev_quest_id: prerequisite, - required_races: 77, - required_classes: 0, - zone_or_sort: if entry >= 40 && entry < 100 { 12 } else { 9 }, - rew_rep_faction_1: 0, - rew_rep_value_1: 0, - rew_rep_faction_2: 0, - rew_rep_value_2: 0, - src_item: if entry == 3905 { 11125 } else { 0 }, - src_item_count: if entry == 3905 { 1 } else { 0 }, - repeatable: false, - next_quest_id: if entry == 783 { 7 } else { 0 }, - limit_time: 0, - reward_money_max_level: 0, - quest_type: 0, - }); + ctx.db + .game_quest_template() + .insert(crate::tables::QuestTemplate { + entry, + min_level, + quest_level: min_level.max(1), + title: format!("Catalog quest {entry}"), + reward_money: 1, + reward_xp: 1, + prev_quest_id: prerequisite, + required_races: 77, + required_classes: 0, + zone_or_sort: if entry >= 40 && entry < 100 { 12 } else { 9 }, + rew_rep_faction_1: 0, + rew_rep_value_1: 0, + rew_rep_faction_2: 0, + rew_rep_value_2: 0, + src_item: if entry == 3905 { 11125 } else { 0 }, + src_item_count: if entry == 3905 { 1 } else { 0 }, + repeatable: false, + next_quest_id: if entry == 783 { 7 } else { 0 }, + limit_time: 0, + reward_money_max_level: 0, + quest_type: 0, + }); let start_kind = if matches!(start, 55 | 56) { CatalogEntityKind::GameObject } else { @@ -1245,14 +1254,14 @@ fn insert_quest( start_kind, start, entry, - crate::quest::quest_role::START, + crate::actor::quest_role::START, ); insert_relation( ctx, end_kind, actual_ender, entry, - crate::quest::quest_role::END, + crate::actor::quest_role::END, ); } @@ -1268,7 +1277,7 @@ fn insert_relation( let table = ctx.db.game_creature_quest(); let id = relation_id(quest_entry, role); table.id().delete(id); - table.insert(crate::CreatureQuest { + table.insert(crate::tables::CreatureQuest { id, creature_entry: entry, quest_entry, @@ -1279,7 +1288,7 @@ fn insert_relation( let table = ctx.db.game_gameobject_quest(); let id = relation_id(quest_entry, role); table.id().delete(id); - table.insert(crate::GameObjectQuest { + table.insert(crate::tables::GameObjectQuest { id, go_entry: entry, quest_entry, @@ -1299,14 +1308,16 @@ fn insert_objective( ) { let id = objective_id(quest_entry, index); ctx.db.game_quest_objective().id().delete(id); - ctx.db.game_quest_objective().insert(crate::QuestObjective { - id, - quest_entry, - obj_index: index, - kind, - target_entry, - required_count, - }); + ctx.db + .game_quest_objective() + .insert(crate::tables::QuestObjective { + id, + quest_entry, + obj_index: index, + kind, + target_entry, + required_count, + }); } fn reward_prerequisite(ctx: &ReducerContext, guid: u64, quest_entry: u32) -> Result<(), String> { @@ -1327,16 +1338,18 @@ fn reward_prerequisite(ctx: &ReducerContext, guid: u64, quest_entry: u32) -> Res .find(guid) .ok_or("Character missing")? .owner_identity; - ctx.db.game_character_quest().insert(crate::CharacterQuest { - id: 0, // Core allocates per-Character quest-log ids. - character_guid: guid, - owner_identity, - quest_entry, - counts: Vec::new(), - rewarded: true, - deadline_micros: 0, - failed: false, - }); + ctx.db + .game_character_quest() + .insert(crate::tables::CharacterQuest { + id: 0, // Core allocates per-Character quest-log ids. + character_guid: guid, + owner_identity, + quest_entry, + counts: Vec::new(), + rewarded: true, + deadline_micros: 0, + failed: false, + }); Ok(()) } @@ -1345,7 +1358,7 @@ pub fn playerbots_quest_fixture_stage( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if stage_gate(ctx, character_guid)? == FixtureStage::Existing { return Ok(()); } @@ -1393,7 +1406,7 @@ pub fn playerbots_quest_fixture_stage( insert_gameobject( ctx, 55, - crate::gameobject::go_type::QUESTGIVER, + crate::actor::go_type::QUESTGIVER, 0, character.map_id, character.instance_id, @@ -1404,7 +1417,7 @@ pub fn playerbots_quest_fixture_stage( insert_gameobject( ctx, 56, - crate::gameobject::go_type::QUESTGIVER, + crate::actor::go_type::QUESTGIVER, 0, character.map_id, character.instance_id, @@ -1415,7 +1428,7 @@ pub fn playerbots_quest_fixture_stage( insert_gameobject( ctx, CHEST_ENTRY, - crate::gameobject::go_type::CHEST, + crate::actor::go_type::CHEST, CHEST_LOOT, character.map_id, character.instance_id, @@ -1427,11 +1440,11 @@ pub fn playerbots_quest_fixture_stage( insert_quest(ctx, quest, min_level, prerequisite, start, actual_ender); } for (quest, kind, target, count) in [ - (7, crate::quest::objective_kind::KILL_CREATURE, 6, 10), - (33, crate::quest::objective_kind::COLLECT_ITEM, 750, 8), - (18, crate::quest::objective_kind::COLLECT_ITEM, 752, 12), - (3904, crate::quest::objective_kind::COLLECT_ITEM, 11119, 8), - (3905, crate::quest::objective_kind::COLLECT_ITEM, 11125, 1), + (7, crate::actor::objective_kind::KILL_CREATURE, 6, 10), + (33, crate::actor::objective_kind::COLLECT_ITEM, 750, 8), + (18, crate::actor::objective_kind::COLLECT_ITEM, 752, 12), + (3904, crate::actor::objective_kind::COLLECT_ITEM, 11119, 8), + (3905, crate::actor::objective_kind::COLLECT_ITEM, 11125, 1), ] { insert_objective(ctx, quest, 0, kind, target, count); } @@ -1439,7 +1452,7 @@ pub fn playerbots_quest_fixture_stage( for (source, item) in [(299, 750), (69, 750), (38, 752)] { let id = creature_loot_id(source); creature_loot.id().delete(id); - creature_loot.insert(crate::CreatureLoot { + creature_loot.insert(crate::tables::CreatureLoot { id, creature_entry: source, item_entry: item, @@ -1451,7 +1464,7 @@ pub fn playerbots_quest_fixture_stage( } let gameobject_loot = ctx.db.game_gameobject_loot(); gameobject_loot.id().delete(GAMEOBJECT_LOOT_ID); - gameobject_loot.insert(crate::GameObjectLoot { + gameobject_loot.insert(crate::tables::GameObjectLoot { id: GAMEOBJECT_LOOT_ID, loot_id: CHEST_LOOT, item_entry: 11119, @@ -1471,7 +1484,7 @@ pub fn playerbots_quest_loop_fixture_stage_named( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; stage_named_with_rotation(ctx, character_guid, 0).map(|_| ()) } @@ -1482,7 +1495,7 @@ pub fn playerbots_quest_loop_fixture_prepare_dispersion( ctx: &ReducerContext, excluded_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let primary = ctx .db @@ -1507,10 +1520,10 @@ pub fn playerbots_quest_loop_fixture_prepare_dispersion( if subjects.len() != DISPERSION_POPULATION { return Err("dispersion fixture requires exactly 25 subjects".to_string()); } - if crate::group::group_of(ctx, excluded_guid).is_some() + if crate::actor::group_of(ctx, excluded_guid).is_some() || subjects .iter() - .any(|guid| crate::group::group_of(ctx, *guid).is_some()) + .any(|guid| crate::actor::group_of(ctx, *guid).is_some()) { return Err("dispersion fixture Characters must be ungrouped".to_string()); } @@ -1534,7 +1547,7 @@ pub fn playerbots_quest_loop_fixture_prepare_dispersion( .pkg_playerbots_provisioning() .character_guid() .update(provisioning); - let mut entity = crate::helpers::live_entity(ctx, guid)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; entity.x = giver.x; entity.y = giver.y; entity.z = giver.z; @@ -1544,7 +1557,7 @@ pub fn playerbots_quest_loop_fixture_prepare_dispersion( entity.cell = lyracore_shared::spatial::grid_cell_id(grid_x, grid_y); ctx.db.game_world_entity().guid().update(entity); playerbots_quest_fixture_admit_accept(ctx, guid, 7)?; - let mut entity = crate::helpers::live_entity(ctx, guid)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; entity.x = selection_x; entity.y = primary.y; entity.z = primary.z; @@ -1567,7 +1580,7 @@ pub fn playerbots_quest_loop_fixture_prepare_dispersion( ctx.db.pkg_playerbots_bot().id().update(bot); super::fixture::playerbots_fixture_runner_select_cohort(ctx, guid)?; } - let mut excluded = crate::helpers::live_entity(ctx, excluded_guid)?; + let mut excluded = crate::actor::live_entity(ctx, excluded_guid)?; excluded.x = primary.x; excluded.y = primary.y; excluded.z = primary.z; @@ -1585,7 +1598,7 @@ pub fn playerbots_quest_loop_fixture_pass_dispersion( ctx: &ReducerContext, excluded_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let mut subjects: Vec<_> = ctx .db @@ -1683,7 +1696,7 @@ pub(super) fn stage_named_with_rotation( let guid = creature_guid(6).saturating_add(offset); let x = character.x + 160.0 + offset as f32 * 0.4; let y = character.y + (offset % 2) as f32 * 0.4; - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid, entry: 6, map_id: character.map_id, @@ -1691,17 +1704,17 @@ pub(super) fn stage_named_with_rotation( y, z: character.z, orientation: 0.0, - respawn_at: crate::creatures::timer_never(ctx), - despawn_at: crate::creatures::timer_never(ctx), + respawn_at: crate::package::fixture::timer_never(ctx), + despawn_at: crate::package::fixture::timer_never(ctx), movement_type: 0, respawn_secs: 60, life_seq: 1, }; - let entity = crate::creatures::build_creature_entity(&spawn, &template, 0, 0); + let entity = crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0); spawns.guid().delete(guid); spawns.insert(spawn); entities.guid().delete(guid); - crate::creatures::insert_creature_entity(ctx, entity); + crate::package::fixture::insert_creature_entity(ctx, entity); inserted_sources.push(guid); } let fixtures = ctx.db.pkg_playerbots_quest_loop_fixture(); @@ -1728,7 +1741,7 @@ pub fn playerbots_quest_loop_fixture_stage_search_limit( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; if ctx .db @@ -1739,7 +1752,7 @@ pub fn playerbots_quest_loop_fixture_stage_search_limit( { return Err("named quest-loop fixture is absent".to_string()); } - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let templates = ctx.db.game_creature_template(); let mut template = templates .entry() @@ -1754,7 +1767,7 @@ pub fn playerbots_quest_loop_fixture_stage_search_limit( for offset in 0..SEARCH_LIMIT_ROWS { let guid = (0xF130u64 << 48) | (u64::from(SEARCH_LIMIT_ENTRY) << 24) | offset.saturating_add(1); - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid, entry: SEARCH_LIMIT_ENTRY, map_id: character.map_id, @@ -1762,17 +1775,17 @@ pub fn playerbots_quest_loop_fixture_stage_search_limit( y: character.y + 90.0, z: character.z, orientation: 0.0, - respawn_at: crate::creatures::timer_never(ctx), - despawn_at: crate::creatures::timer_never(ctx), + respawn_at: crate::package::fixture::timer_never(ctx), + despawn_at: crate::package::fixture::timer_never(ctx), movement_type: 0, respawn_secs: 0, life_seq: 1, }; - let entity = crate::creatures::build_creature_entity(&spawn, &template, 0, 0); + let entity = crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0); spawns.guid().delete(guid); spawns.insert(spawn); entities.guid().delete(guid); - crate::creatures::insert_creature_entity(ctx, entity); + crate::package::fixture::insert_creature_entity(ctx, entity); } Ok(()) } @@ -1783,9 +1796,9 @@ pub fn playerbots_quest_loop_fixture_set_partition( character_guid: u64, instance_id: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; - let mut character = crate::helpers::live_entity(ctx, character_guid)?; + let mut character = crate::actor::live_entity(ctx, character_guid)?; character.instance_id = instance_id; ctx.db.game_creature_spline().guid().delete(character_guid); ctx.db.game_world_entity().guid().update(character); @@ -1794,8 +1807,8 @@ pub fn playerbots_quest_loop_fixture_set_partition( #[reducer] pub fn playerbots_quest_loop_fixture_refresh(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; quest_catalog::refresh_catalog(ctx, "unknown"); if ctx .db @@ -1820,8 +1833,8 @@ pub fn playerbots_quest_loop_fixture_set_simple_gameobject_state( ctx: &ReducerContext, state: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; if state > 1 { return Err("simple GameObject fixture state must be 0 or 1".to_string()); } @@ -1840,8 +1853,8 @@ pub fn playerbots_quest_loop_fixture_add_gameobject_alternative( ctx: &ReducerContext, gameobject_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; match gameobject_entry { SEEDED_USE_GAMEOBJECT => { if ctx @@ -1868,7 +1881,7 @@ pub fn playerbots_quest_loop_fixture_stage_corpse_limit( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let retained = ctx .db @@ -1883,7 +1896,7 @@ pub fn playerbots_quest_loop_fixture_stage_corpse_limit( .target .source .ok_or("retained creature-loot source is absent")?; - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let template = ctx .db .game_creature_template() @@ -1894,7 +1907,7 @@ pub fn playerbots_quest_loop_fixture_stage_corpse_limit( let entities = ctx.db.game_world_entity(); for offset in 10..19u64 { let guid = creature_guid(source.entry).saturating_add(offset); - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid, entry: source.entry, map_id: character.map_id, @@ -1902,19 +1915,19 @@ pub fn playerbots_quest_loop_fixture_stage_corpse_limit( y: character.y, z: character.z, orientation: 0.0, - respawn_at: crate::creatures::timer_never(ctx), - despawn_at: crate::creatures::timer_never(ctx), + respawn_at: crate::package::fixture::timer_never(ctx), + despawn_at: crate::package::fixture::timer_never(ctx), movement_type: 0, respawn_secs: 60, life_seq: 1, }; - let mut entity = crate::creatures::build_creature_entity(&spawn, &template, 0, 0); + let mut entity = crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0); entity.health = 0; entity.dead = true; spawns.guid().delete(guid); spawns.insert(spawn); entities.guid().delete(guid); - crate::creatures::insert_creature_entity(ctx, entity); + crate::package::fixture::insert_creature_entity(ctx, entity); } Ok(()) } @@ -1936,7 +1949,7 @@ pub fn playerbots_quest_loop_fixture_stage_corpse_limit_with_live_alternative( .target .source .ok_or("retained creature-loot source is absent")?; - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let template = ctx .db .game_creature_template() @@ -1965,24 +1978,27 @@ pub fn playerbots_quest_loop_fixture_stage_corpse_limit_with_live_alternative( { return Err("alternate creature-loot source is occupied".to_string()); } - crate::creatures::despawn_creature_entity(ctx, source.guid); - let spawn = ctx.db.game_creature_spawn().insert(crate::CreatureSpawn { - guid: alternative_guid, - entry: source.entry, - map_id: character.map_id, - x: character.x + 2.0, - y: character.y, - z: character.z, - orientation: source_spawn.orientation, - respawn_at: crate::creatures::timer_never(ctx), - despawn_at: crate::creatures::timer_never(ctx), - movement_type: source_spawn.movement_type, - respawn_secs: source_spawn.respawn_secs, - life_seq: source_spawn.life_seq, - }); - crate::creatures::insert_creature_entity( + crate::package::fixture::despawn_creature_entity(ctx, source.guid); + let spawn = ctx + .db + .game_creature_spawn() + .insert(crate::tables::CreatureSpawn { + guid: alternative_guid, + entry: source.entry, + map_id: character.map_id, + x: character.x + 2.0, + y: character.y, + z: character.z, + orientation: source_spawn.orientation, + respawn_at: crate::package::fixture::timer_never(ctx), + despawn_at: crate::package::fixture::timer_never(ctx), + movement_type: source_spawn.movement_type, + respawn_secs: source_spawn.respawn_secs, + life_seq: source_spawn.life_seq, + }); + crate::package::fixture::insert_creature_entity( ctx, - crate::creatures::build_creature_entity(&spawn, &template, 0, 0), + crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0), ); Ok(()) } @@ -1993,7 +2009,7 @@ pub fn playerbots_quest_fixture_admit_accept( character_guid: u64, quest_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let prerequisite = ctx .db @@ -2034,7 +2050,7 @@ pub fn playerbots_quest_fixture_turn_in( character_guid: u64, quest_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let admission = quest_catalog::admit_held(ctx, character_guid, quest_entry).map_err( |refusal| match refusal { @@ -2059,9 +2075,9 @@ pub fn playerbots_quest_fixture_kill( character_guid: u64, creature_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let target = match super::quest_loop::live_creature_target(ctx, &character, creature_entry, |_| true) { super::quest_loop::LiveCreatureTarget::Found(target) => target, @@ -2077,14 +2093,15 @@ pub fn playerbots_quest_fixture_kill( } }; super::actions::attack(ctx, character_guid, target.guid).map_err(String::from)?; - let (amount, _) = crate::combat::fold_incoming_damage(ctx, character_guid, target.guid, 10_000); - let damage = crate::combat::final_damage(ctx, target.guid, amount); - let outcome = crate::combat::apply_hit( + let (amount, _) = + crate::package::fixture::fold_incoming_damage(ctx, character_guid, target.guid, 10_000); + let damage = crate::package::fixture::final_damage(ctx, target.guid, amount); + let outcome = crate::package::fixture::apply_hit( ctx, character_guid, target.guid, damage, - crate::combat::Hit::weapon(crate::combat::HitSource::MainHand, false), + crate::package::fixture::Hit::weapon(crate::package::fixture::HitSource::MainHand, false), ); if outcome.killed { Ok(()) @@ -2099,7 +2116,7 @@ pub fn playerbots_quest_fixture_take_creature_loot( character_guid: u64, creature_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let guid = creature_guid(creature_entry); super::actions::open_creature_loot(ctx, character_guid, guid, 33).map_err(String::from)?; @@ -2126,7 +2143,7 @@ pub fn playerbots_quest_fixture_use_gameobject( gameobject_entry: u32, take_loot: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let guid = gameobject_guid(gameobject_entry); super::actions::use_gameobject(ctx, character_guid, guid, DIRECT_GO_QUEST) @@ -2155,7 +2172,7 @@ pub fn playerbots_quest_fixture_fill_inventory( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let source_entry = ctx .db @@ -2174,10 +2191,10 @@ pub fn playerbots_quest_fixture_fill_inventory( filler.max_stack = 1; templates.entry().update(filler); for _ in 0..64 { - if !crate::items::has_free_slot(ctx, character_guid) { + if !crate::actor::has_free_slot(ctx, character_guid) { return Ok(()); } - crate::items::grant_item(ctx, character_guid, INVENTORY_FILLER, 1)?; + crate::package::fixture::grant_item(ctx, character_guid, INVENTORY_FILLER, 1)?; } Err("fixture did not fill bounded inventory space".to_string()) } @@ -2187,7 +2204,7 @@ pub fn playerbots_quest_fixture_try_take_gameobject_loot( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let guid = gameobject_guid(CHEST_ENTRY); let slot = ctx @@ -2211,7 +2228,7 @@ pub fn playerbots_quest_fixture_clear_filler_and_take_gameobject_loot( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let items = ctx.db.game_item_instance(); for item in items @@ -2246,7 +2263,7 @@ pub fn playerbots_quest_fixture_try_use_gameobject( character_guid: u64, gameobject_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let _ = super::actions::use_gameobject( ctx, @@ -2262,7 +2279,7 @@ pub fn playerbots_quest_fixture_direct_gameobject( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; reward_prerequisite(ctx, character_guid, 3903)?; let templates = ctx.db.game_gameobject_template(); @@ -2270,14 +2287,14 @@ pub fn playerbots_quest_fixture_direct_gameobject( .entry() .find(CHEST_ENTRY) .ok_or("GameObject template missing")?; - template.type_id = crate::gameobject::go_type::GOOBER; + template.type_id = crate::actor::go_type::GOOBER; template.data1 = 0; templates.entry().update(template); insert_objective( ctx, DIRECT_GO_QUEST, 0, - crate::quest::objective_kind::USE_GAMEOBJECT, + crate::actor::objective_kind::USE_GAMEOBJECT, CHEST_ENTRY, 1, ); @@ -2302,10 +2319,10 @@ pub fn playerbots_quest_fixture_mixed_unsupported( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; reward_prerequisite(ctx, character_guid, 783)?; - insert_objective(ctx, 7, 1, crate::quest::objective_kind::KILL_CREATURE, 6, 1); + insert_objective(ctx, 7, 1, crate::actor::objective_kind::KILL_CREATURE, 6, 1); quest_catalog::refresh_catalog(ctx, "unknown"); let catalog = ctx.db.pkg_playerbots_catalog_objective(); let id = (u64::from(7u32) << 8) | 1; @@ -2336,14 +2353,14 @@ pub fn playerbots_quest_fixture_mixed_progress( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; reward_prerequisite(ctx, character_guid, 783)?; insert_objective( ctx, 7, 0, - crate::quest::objective_kind::COLLECT_ITEM, + crate::actor::objective_kind::COLLECT_ITEM, 750, 8, ); @@ -2351,13 +2368,13 @@ pub fn playerbots_quest_fixture_mixed_progress( ctx, 7, 1, - crate::quest::objective_kind::COLLECT_ITEM, + crate::actor::objective_kind::COLLECT_ITEM, 752, 12, ); - insert_objective(ctx, 7, 2, crate::quest::objective_kind::KILL_CREATURE, 6, 1); - crate::items::grant_item(ctx, character_guid, 750, 8)?; - crate::items::grant_item(ctx, character_guid, 752, 12)?; + insert_objective(ctx, 7, 2, crate::actor::objective_kind::KILL_CREATURE, 6, 1); + crate::package::fixture::grant_item(ctx, character_guid, 750, 8)?; + crate::package::fixture::grant_item(ctx, character_guid, 752, 12)?; let items = ctx.db.game_item_instance(); let mut banked = items .by_owner_guid() @@ -2424,7 +2441,7 @@ pub fn playerbots_quest_fixture_unsupported( character_guid: u64, unsupported_kind: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; if unsupported_kind == 4 { reward_prerequisite(ctx, character_guid, 3903)?; @@ -2468,7 +2485,7 @@ pub fn playerbots_quest_fixture_level_admission( level: u32, quest_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let rows = ctx.db.game_world_entity(); let mut character = rows @@ -2497,7 +2514,7 @@ pub fn playerbots_quest_fixture_held_becomes_unsupported( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let alternative = quest_catalog::admit_available(ctx, character_guid, 5261).map_err( |refusal| match refusal { @@ -2567,7 +2584,7 @@ pub fn playerbots_quest_fixture_active_log_overflow( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let quests = ctx.db.game_character_quest(); let exemplar = quests @@ -2575,7 +2592,7 @@ pub fn playerbots_quest_fixture_active_log_overflow( .filter((character_guid, false, false)) .next() .ok_or("active fixture quest missing")?; - for offset in 0..crate::quest::MAX_QUEST_LOG_SIZE { + for offset in 0..crate::actor::MAX_QUEST_LOG_SIZE { let quest_entry = ACTIVE_QUEST_OVERFLOW_BASE + offset as u32; if let Some(previous) = quests .by_character_quest() @@ -2584,7 +2601,7 @@ pub fn playerbots_quest_fixture_active_log_overflow( { quests.id().delete(previous.id); } - quests.insert(crate::quest::CharacterQuest { + quests.insert(crate::tables::CharacterQuest { id: 0, character_guid: exemplar.character_guid, owner_identity: exemplar.owner_identity, @@ -2604,7 +2621,7 @@ pub fn playerbots_quest_fixture_lose_provided_item( character_guid: u64, bank_item: bool, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let items = ctx.db.game_item_instance(); let mut item = items @@ -2635,9 +2652,9 @@ pub fn playerbots_quest_fixture_hide_live_target( ctx: &ReducerContext, creature_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; - crate::creatures::despawn_creature_entity(ctx, creature_guid(creature_entry)); + crate::package::fixture::despawn_creature_entity(ctx, creature_guid(creature_entry)); Ok(()) } @@ -2648,7 +2665,7 @@ pub fn playerbots_quest_loop_fixture_respawn_target( character_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let fixture = ctx .db @@ -2677,9 +2694,9 @@ pub fn playerbots_quest_loop_fixture_respawn_target( .entry() .find(spawn.entry) .ok_or("quest target template is absent")?; - crate::creatures::despawn_creature_entity(ctx, target_guid); - let entity = crate::creatures::build_creature_entity(&spawn, &template, 0, 0); - crate::creatures::insert_creature_entity(ctx, entity); + crate::package::fixture::despawn_creature_entity(ctx, target_guid); + let entity = crate::package::fixture::build_creature_entity(&spawn, &template, 0, 0); + crate::package::fixture::insert_creature_entity(ctx, entity); Ok(()) } @@ -2689,7 +2706,7 @@ pub fn playerbots_quest_loop_fixture_prepare_moving_cast( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let bot = ctx .db @@ -2741,14 +2758,14 @@ pub fn playerbots_quest_loop_fixture_prepare_moving_cast( entities.guid().update(target); ctx.db.game_creature_spline().guid().delete(target_guid); - let mut character = crate::helpers::live_entity(ctx, character_guid)?; + let mut character = crate::actor::live_entity(ctx, character_guid)?; for alternative_guid in target_guid + 1..target_guid + 10 { entities .guid() .find(alternative_guid) .filter(|alternative| alternative.entry == 6 && !alternative.dead) .ok_or("moving-cast fixture alternative missing")?; - crate::creatures::despawn_creature_entity(ctx, alternative_guid); + crate::package::fixture::despawn_creature_entity(ctx, alternative_guid); } character.x = target_x - 80.0; character.y = target_y; @@ -2783,7 +2800,7 @@ pub fn playerbots_quest_loop_fixture_start_moving_cast( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let bot = ctx .db @@ -2807,7 +2824,7 @@ pub fn playerbots_quest_loop_fixture_start_moving_cast( .filter(|spell| spell.range_yd == 35 && spell.cast_time_ms == 1_500) .ok_or("moving-cast fixture requires the staged Fireball")?; let target_guid = creature_guid(6); - let target = crate::helpers::live_entity(ctx, target_guid)?; + let target = crate::actor::live_entity(ctx, target_guid)?; if target.dead || (target.map_id, target.instance_id) != (0, 0) { return Err("moving-cast fixture target is unavailable".to_string()); } @@ -2819,7 +2836,7 @@ pub fn playerbots_quest_loop_fixture_start_moving_cast( .ok_or("moving-cast fixture target is not moving forward")?; let caster_x = target.x - 38.5; - let mut character = crate::helpers::live_entity(ctx, character_guid)?; + let mut character = crate::actor::live_entity(ctx, character_guid)?; character.x = caster_x; character.y = target.y; character.z = target.z; @@ -2846,11 +2863,11 @@ pub fn playerbots_quest_loop_fixture_make_target_friendly( character_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let factions = ctx.db.game_faction_template(); if factions.id().find(FRIENDLY_FIXTURE_FACTION).is_none() { - factions.insert(crate::FactionTemplate { + factions.insert(crate::tables::FactionTemplate { id: FRIENDLY_FIXTURE_FACTION, faction: FRIENDLY_FIXTURE_FACTION, faction_group: 1, @@ -2887,7 +2904,7 @@ pub fn playerbots_quest_fixture_deplete_gameobject( ctx: &ReducerContext, gameobject_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let rows = ctx.db.game_gameobject(); let mut row = rows @@ -2905,7 +2922,7 @@ pub fn playerbots_quest_fixture_move_gameobject( gameobject_entry: u32, x: f32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let rows = ctx.db.game_gameobject(); let mut row = rows @@ -2927,7 +2944,7 @@ pub fn playerbots_quest_fixture_move_creature_spawn( creature_entry: u32, x: f32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let rows = ctx.db.game_creature_spawn(); let mut row = rows @@ -2945,9 +2962,9 @@ pub fn playerbots_quest_fixture_assert_no_live_target( character_guid: u64, creature_entry: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; match super::quest_loop::live_creature_target(ctx, &character, creature_entry, |_| true) { super::quest_loop::LiveCreatureTarget::Found(_) => { Err("live target still present".to_string()) @@ -2965,7 +2982,7 @@ pub fn playerbots_quest_fixture_assert_no_live_target( #[reducer] pub fn playerbots_quest_fixture_refresh(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; quest_catalog::refresh_catalog(ctx, "unknown"); Ok(()) @@ -2973,7 +2990,7 @@ pub fn playerbots_quest_fixture_refresh(ctx: &ReducerContext) -> Result<(), Stri #[reducer] pub fn playerbots_quest_fixture_recheck_unchanged(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let headers = ctx.db.pkg_playerbots_quest_catalog(); let mut header = headers @@ -2993,7 +3010,7 @@ pub fn playerbots_recovery_fixture_stage_full_bag( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_quest_loop_fixture_stage_named(ctx, character_guid)?; require_fixture(ctx)?; let objectives = ctx.db.pkg_playerbots_catalog_objective(); @@ -3033,7 +3050,7 @@ pub fn playerbots_recovery_fixture_clear_one_inventory_slot( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; let items = ctx.db.game_item_instance(); let filler = items @@ -3042,7 +3059,7 @@ pub fn playerbots_recovery_fixture_clear_one_inventory_slot( .find(|item| item.entry == INVENTORY_FILLER) .ok_or("inventory filler missing")?; items.guid().delete(filler.guid); - if !crate::items::has_free_slot(ctx, character_guid) { + if !crate::actor::has_free_slot(ctx, character_guid) { return Err("one inventory slot did not become available".to_string()); } Ok(()) @@ -3055,7 +3072,7 @@ pub fn playerbots_recovery_fixture_three_parked_passes( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; require_fixture(ctx)?; for _ in 0..3 { super::fixture::playerbots_fixture_runner_pass_once(ctx, character_guid)?; @@ -3068,8 +3085,8 @@ pub fn playerbots_recovery_fixture_arm_gameobject_respawn( ctx: &ReducerContext, delay_seconds: u32, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; if !(2..=10).contains(&delay_seconds) { return Err("respawn delay must be 2 through 10 seconds".to_string()); } @@ -3090,9 +3107,9 @@ pub fn playerbots_recovery_fixture_position_simple_gameobject( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; - let character = crate::helpers::live_entity(ctx, character_guid)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let rows = ctx.db.game_gameobject(); let mut gameobject = rows .guid() @@ -3111,8 +3128,8 @@ pub fn playerbots_recovery_fixture_position_simple_gameobject( pub fn playerbots_recovery_fixture_remove_simple_gameobject( ctx: &ReducerContext, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; if ctx .db .pkg_playerbots_seeded_quest_fixture() @@ -3136,8 +3153,8 @@ pub fn playerbots_recovery_fixture_restore_simple_gameobject( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; let retained = ctx .db .pkg_playerbots_quest_objective() @@ -3160,14 +3177,14 @@ pub fn playerbots_recovery_fixture_restore_simple_gameobject( .entry() .find(SEEDED_USE_GAMEOBJECT) .filter(|template| { - template.type_id == crate::gameobject::go_type::GOOBER && template.lock_id == 0 + template.type_id == crate::actor::go_type::GOOBER && template.lock_id == 0 }) .ok_or("simple GameObject template differs")?; let rows = ctx.db.game_gameobject(); if rows.guid().find(destination.guid).is_some() { return Err("simple GameObject is already present".to_string()); } - rows.insert(crate::GameObject { + rows.insert(crate::tables::GameObject { guid: destination.guid, template_entry: template.entry, map_id: destination.map_id, @@ -3194,8 +3211,8 @@ pub fn playerbots_recovery_fixture_restore_simple_gameobject( pub fn playerbots_recovery_fixture_remove_simple_gameobject_and_objective( ctx: &ReducerContext, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx)?; + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx)?; if ctx .db .game_quest_objective() @@ -3220,10 +3237,10 @@ pub fn playerbots_recovery_fixture_stage_unreachable_ender( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; playerbots_quest_fixture_stage(ctx, character_guid)?; require_fixture(ctx)?; - let character = crate::helpers::live_entity(ctx, character_guid)?; + let character = crate::actor::live_entity(ctx, character_guid)?; let ender_guid = creature_guid(197); super::fixture::playerbots_fixture_position(ctx, ender_guid, character.x + 40.0)?; let spawns = ctx.db.game_creature_spawn(); diff --git a/playerbots/src/quest_loop.rs b/playerbots/src/quest_loop.rs index dbb07a7..bf3a8ed 100644 --- a/playerbots/src/quest_loop.rs +++ b/playerbots/src/quest_loop.rs @@ -6,7 +6,7 @@ use super::quest_catalog::{ PlayerbotsQuestObjective, RetainedPosition, }; use super::PlayerbotsBot; -use crate::{game_corpse_loot, game_gameobject, game_world_entity}; +use crate::tables::{game_corpse_loot, game_gameobject, game_world_entity}; use spacetimedb::ReducerContext; const SEARCH_RADIUS_YD: f32 = 100.0; @@ -87,12 +87,12 @@ enum Search { } struct EntitySearch { - rows: Vec, + rows: Vec, exhausted: bool, } pub(super) enum LiveCreatureTarget { - Found(crate::WorldEntity), + Found(crate::tables::WorldEntity), Missing, ReadLimit, Deferred, @@ -111,13 +111,13 @@ enum GameObjectSearch { Respawn, } -fn distance_sq(me: &crate::WorldEntity, x: f32, y: f32, z: f32) -> f32 { +fn distance_sq(me: &crate::tables::WorldEntity, x: f32, y: f32, z: f32) -> f32 { (me.x - x).powi(2) + (me.y - y).powi(2) + (me.z - z).powi(2) } fn live_giver( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, destination: &CatalogDestination, ) -> bool { match destination.kind { @@ -145,9 +145,9 @@ fn live_giver( fn search_entities( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, dead: bool, - mut wanted: impl FnMut(&crate::WorldEntity) -> bool, + mut wanted: impl FnMut(&crate::tables::WorldEntity) -> bool, ) -> EntitySearch { let (gx0, gx1, gy0, gy1) = lyracore_shared::spatial::covering_cell_box(me.x, me.y, SEARCH_RADIUS_YD); @@ -186,7 +186,7 @@ fn search_entities( pub(super) fn live_creature_target( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, entry: u32, eligible_work: impl Fn(u64) -> bool, ) -> LiveCreatureTarget { @@ -203,10 +203,10 @@ pub(super) fn live_creature_target( fn preferred_creature( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, source: &CatalogDestination, guid: u64, -) -> Option { +) -> Option { if source.kind == CatalogEntityKind::Creature { ctx.db.game_world_entity().guid().find(guid) } else { @@ -222,7 +222,7 @@ fn preferred_creature( pub(super) fn grind_target( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, preferred: Option, eligible_work: impl Fn(u64) -> bool, ) -> LiveCreatureTarget { @@ -246,7 +246,7 @@ pub(super) fn grind_target( fn select_live_target( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, search: EntitySearch, eligible_work: impl Fn(u64) -> bool, salt: Option, @@ -258,12 +258,12 @@ fn select_live_target( let mut eligible: Vec<_> = search .rows .into_iter() - .filter(|target| crate::combat::validate_attack_target(ctx, me, target.guid).is_ok()) + .filter(|target| crate::actor::attack_target_gate(ctx, me, target.guid).is_ok()) .filter(|target| { - match crate::loot::tag::live_loot_tag_eligibility(ctx, target.guid, me.guid) { - crate::loot::tag::LiveLootTagEligibility::Available => {} - crate::loot::tag::LiveLootTagEligibility::Foreign => return false, - crate::loot::tag::LiveLootTagEligibility::ReadLimit => { + match crate::actor::live_loot_tag_eligibility(ctx, target.guid, me.guid) { + crate::actor::LiveLootTagEligibility::Available => {} + crate::actor::LiveLootTagEligibility::Foreign => return false, + crate::actor::LiveLootTagEligibility::ReadLimit => { read_limited = true; return false; } @@ -298,7 +298,7 @@ fn select_live_target( } break; } - match crate::spell::control_status(ctx, target.guid, CONTROL_AURA_LIMIT) { + match crate::actor::control_status(ctx, target.guid, CONTROL_AURA_LIMIT) { Ok(None) => { if preferred == Some(target.guid) { return LiveCreatureTarget::Found(target); @@ -349,7 +349,7 @@ fn wanted_loot_slot(ctx: &ReducerContext, source_guid: u64, item_entry: u32) -> fn entitled_corpse( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, source_entry: u32, item_entry: u32, ) -> Search<(u64, u8)> { @@ -365,7 +365,7 @@ fn entitled_corpse( }); let inconclusive = search.exhausted || corpses.len() > CORPSE_LIMIT; for corpse in corpses.into_iter().take(CORPSE_LIMIT) { - if crate::loot::corpse_access(ctx, me.guid, corpse.guid).is_err() { + if crate::actor::corpse_access(ctx, me.guid, corpse.guid).is_err() { continue; } match wanted_loot_slot(ctx, corpse.guid, item_entry) { @@ -383,7 +383,7 @@ fn entitled_corpse( fn preferred_creature_plan( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, source: &CatalogDestination, preferred_guid: u64, quest: u32, @@ -393,7 +393,7 @@ fn preferred_creature_plan( let preferred = preferred_creature(ctx, me, source, preferred_guid)?; if preferred.dead { let item = loot_item?; - if crate::loot::corpse_access(ctx, me.guid, preferred.guid).is_err() { + if crate::actor::corpse_access(ctx, me.guid, preferred.guid).is_err() { return None; } return match wanted_loot_slot(ctx, preferred.guid, item) { @@ -424,7 +424,7 @@ fn preferred_creature_plan( fn creature_plan( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, source: &CatalogDestination, active_fight: Option, quest: u32, @@ -453,7 +453,7 @@ fn creature_plan( fn gameobject_work( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, entry: u32, wanted_item: Option, ) -> GameObjectSearch { @@ -518,13 +518,13 @@ fn gameobject_work( pub(super) fn plan( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, retained: &PlayerbotsQuestObjective, active_fight: Option, eligible_fight: impl Fn(u64) -> bool, ) -> QuestPlan { let quest = retained.quest_entry; - let Some(held) = crate::quest::character_quest_row(ctx, me.guid, quest) else { + let Some(held) = crate::actor::character_quest(ctx, me.guid, quest) else { return if live_giver(ctx, me, &retained.destination) { QuestPlan::Accept { quest, @@ -537,7 +537,7 @@ pub(super) fn plan( if held.rewarded || held.failed { return QuestPlan::Wait(WaitReason::MissingTarget); } - if crate::quest::quest_is_complete(ctx, &held) { + if crate::actor::quest_complete(ctx, &held) { return if live_giver(ctx, me, &retained.actual_ender) { QuestPlan::TurnIn { quest, @@ -661,7 +661,7 @@ fn action_node(action: Action, reason: Reason, priority: i32, objective: u64) -> pub(super) fn strategy( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, plan: QuestPlan, objective: u64, away: bool, @@ -836,7 +836,7 @@ pub(super) fn wait_reason(plan: QuestPlan) -> Option { pub(super) fn observe_safe_position( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, retained: &PlayerbotsQuestObjective, ) -> Option { let source = retained.target.source.as_ref()?; @@ -875,7 +875,7 @@ pub(super) fn observe_safe_position( pub(super) fn invalidate_safe_position( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, retained: &mut PlayerbotsQuestObjective, ) { let Some(safe) = retained.safe_position.as_ref() else { @@ -902,7 +902,7 @@ pub(super) fn invalidate_safe_position( } pub(super) fn safe_position( - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, retained: &PlayerbotsQuestObjective, ) -> Option { retained diff --git a/playerbots/src/recovery.rs b/playerbots/src/recovery.rs index 2a22296..b2798b9 100644 --- a/playerbots/src/recovery.rs +++ b/playerbots/src/recovery.rs @@ -3,7 +3,7 @@ use super::actions::{self, ActionKind, ActionOutcome}; use super::decision::{Action, Candidate, CastAction, MoveTarget, QuestInteraction, Reason}; use super::runner::{Destination, ObjectiveKind, PlayerbotsRunner, Running}; -use crate::{game_aura, game_creature_spline, game_gameobject, game_world_entity}; +use crate::tables::{game_aura, game_creature_spline, game_gameobject, game_world_entity}; use spacetimedb::ReducerContext; const CHANGE_APPROACH_MICROS: i64 = 10_000_000; @@ -58,13 +58,13 @@ pub struct Attempt { pub work: Work, pub reason: Reason, pub destination: Destination, - pub geometry: crate::nav::NavigationInputs, + pub geometry: crate::actor::movement::NavigationInputs, pub objective: u64, pub last_observed_micros: i64, pub stalled_micros: i64, pub target_health: Option, pub position: Option, - pub route: Option, + pub route: Option, pub last_movement: Option, pub deferred_until_micros: Option, } @@ -72,7 +72,7 @@ pub struct Attempt { #[derive(spacetimedb::SpacetimeType, Clone, Debug)] pub struct ObservedMovement { pub started_micros: i64, - pub position: crate::nav::RoutePoint, + pub position: crate::actor::movement::RoutePoint, } #[derive(spacetimedb::SpacetimeType, Clone, Debug)] @@ -152,7 +152,7 @@ pub(super) fn work(purpose: Candidate) -> Option { fn destination( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, state: &PlayerbotsRunner, work: Work, ) -> Option { @@ -174,7 +174,7 @@ fn destination( x, y, z, - geometry_revision: crate::nav::coverage_generation(ctx, me.map_id), + geometry_revision: crate::actor::movement::coverage_generation(ctx, me.map_id), }) } else if let Work::AreaTrigger(trigger) = work { crate::actor::area_trigger_route(ctx, trigger).map(|route| Destination { @@ -183,7 +183,7 @@ fn destination( x: route.source_x, y: route.source_y, z: route.source_z, - geometry_revision: crate::nav::coverage_generation(ctx, route.source_map), + geometry_revision: crate::actor::movement::coverage_generation(ctx, route.source_map), }) } else if matches!(work, Work::Destination(_)) { state @@ -198,11 +198,11 @@ fn destination( /// Only observed advancement along the retained Core leg counts. An emitted leg has no progress yet. fn movement_progress( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, state: &PlayerbotsRunner, previous: Option<&ObservedMovement>, last_observed_micros: i64, -) -> Option<(crate::nav::RouteStep, ObservedMovement, bool)> { +) -> Option<(crate::actor::movement::RouteStep, ObservedMovement, bool)> { let foreground = state.foreground.as_ref()?; if foreground.generation != state.generation || (foreground.map_id, foreground.instance_id) != (me.map_id, me.instance_id) @@ -363,7 +363,7 @@ impl Recovery { pub(super) fn active_quest_creature( &self, ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, objective: u64, ) -> Option { let active = self.active?; @@ -383,7 +383,7 @@ impl Recovery { })?; ((attempt.destination.map_id, attempt.destination.instance_id) == (me.map_id, me.instance_id) - && attempt.geometry == crate::nav::inputs(ctx, me.map_id)) + && attempt.geometry == crate::actor::movement::inputs(ctx, me.map_id)) .then_some(target) } @@ -391,11 +391,11 @@ impl Recovery { pub(super) fn observe( &mut self, ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, state: &mut PlayerbotsRunner, now: i64, ) -> Option { - let geometry = crate::nav::inputs(ctx, me.map_id); + let geometry = crate::actor::movement::inputs(ctx, me.map_id); let quest_objective = state .objective .as_ref() @@ -479,12 +479,12 @@ impl Recovery { let fight_ended = matches!((attempt.work, health), (Work::Fight(_), Some(0))); let quest_effect = match (attempt.reason, attempt.work, health) { (Reason::Quest, Work::Fight(target), Some(0)) => { - let recipients = crate::loot::corpse_eligible_recipients(ctx, target); - crate::loot::corpse_eligible_for_access(&recipients, me.guid) + let recipients = crate::actor::corpse_eligible_recipients(ctx, target); + crate::actor::corpse_eligible_for_access(&recipients, me.guid) } (Reason::Quest, Work::Fight(target), Some(_)) if health_progress => matches!( - crate::loot::tag::live_loot_tag_eligibility(ctx, target, me.guid), - crate::loot::tag::LiveLootTagEligibility::Available + crate::actor::live_loot_tag_eligibility(ctx, target, me.guid), + crate::actor::LiveLootTagEligibility::Available ), (Reason::Quest, Work::Fight(_), _) => false, _ => true, @@ -575,7 +575,10 @@ impl Recovery { missing_coverage: attempt.geometry.navigation_enabled && attempt.geometry.coverage_enabled && attempt.route.as_ref().is_some_and(|route| { - matches!(route.coverage, crate::nav::CoverageEvidence::Unknown) + matches!( + route.coverage, + crate::actor::movement::CoverageEvidence::Unknown + ) }), }); } @@ -622,7 +625,7 @@ impl Recovery { pub(super) fn select( &mut self, ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, state: &PlayerbotsRunner, candidate: Candidate, purpose: Candidate, @@ -651,7 +654,7 @@ impl Recovery { work, reason: purpose.id.reason, destination, - geometry: crate::nav::inputs(ctx, me.map_id), + geometry: crate::actor::movement::inputs(ctx, me.map_id), objective: purpose.id.objective, last_observed_micros: now, stalled_micros: 0, @@ -759,7 +762,7 @@ mod tests { z: 0.0, geometry_revision: None, }, - geometry: crate::nav::NavigationInputs { + geometry: crate::actor::movement::NavigationInputs { imported_revision: None, navigation_enabled: true, collision_enabled: false, @@ -824,14 +827,14 @@ mod tests { #[test] fn progress_along_a_partial_leg_counts_once_even_when_it_moves_away_from_the_goal() { - let route = crate::nav::RouteStep { + let route = crate::actor::movement::RouteStep { from: (10.0, 0.0).into(), endpoint: (5.0, 0.0).into(), first_waypoint: Some((0.0, 0.0).into()), - status: crate::nav::RouteStatus::Partial, + status: crate::actor::movement::RouteStatus::Partial, expansions: 4096, clipping: None, - coverage: crate::nav::CoverageEvidence::Unknown, + coverage: crate::actor::movement::CoverageEvidence::Unknown, }; assert!(actions::advanced_on_leg( &route, diff --git a/playerbots/src/runner.rs b/playerbots/src/runner.rs index be8b4b5..9cd9567 100644 --- a/playerbots/src/runner.rs +++ b/playerbots/src/runner.rs @@ -10,8 +10,8 @@ use super::{ pkg_playerbots_bot, pkg_playerbots_personality, pkg_playerbots_rotation, PlayerbotsBot, PlayerbotsRotation, }; -use crate::transfer::game_bot_transfer_intent; -use crate::{ +use crate::tables::game_bot_transfer_intent; +use crate::tables::{ game_character_quest, game_creature_spline, game_gameobject, game_melee_attack, game_spell, game_world_entity, }; @@ -84,10 +84,10 @@ pub enum Failure { Deadline, CastLost, ActionRefused(crate::actor::ActionRefusalKind), - CastRefused(crate::spell::CastRefusalKind), + CastRefused(crate::actor::CastRefusalKind), Decision(DecisionRefusal), PartyFactsUnavailable, - PartyReadUnavailable(crate::group::PartyFactsUnavailableReason), + PartyReadUnavailable(crate::actor::PartyFactsUnavailableReason), RoleFactsUnavailable(super::companion::RoleReadError), QuestTargetMissing, QuestReadLimit, @@ -125,7 +125,7 @@ pub struct MovementRun { #[derive(spacetimedb::SpacetimeType, Clone, Debug)] pub enum Running { Movement(MovementRun), - Cast(crate::spell::CastHandle), + Cast(crate::actor::CastHandle), } #[derive(spacetimedb::SpacetimeType, Clone, Debug)] @@ -211,7 +211,7 @@ pub enum RunnerOutcome { Waiting, Arrived, Accepted, - CastFinished(crate::spell::CastFinish), + CastFinished(crate::actor::CastFinish), Refused(Failure), Cancelled, Frozen, @@ -387,13 +387,13 @@ fn recovery_spell(ctx: &ReducerContext, bot: &PlayerbotsBot) -> RecoveryLookup { let valid = |row: &PlayerbotsRotation| { (row.class, row.role, row.condition) == (bot.class, bot.role, super::cond::ALLY_HP_BELOW_PCT) - && crate::spell::knows_spell(ctx, bot.character_guid, row.spell_id) + && crate::actor::knows_spell(ctx, bot.character_guid, row.spell_id) && ctx .db .game_spell() .spell_id() .find(row.spell_id) - .is_some_and(|spell| spell.cast_flags & crate::spell::SPELL_ATTR_CHANNELED == 0) + .is_some_and(|spell| spell.cast_flags & crate::actor::SPELL_ATTR_CHANNELED == 0) }; if scan.stage == ScanStage::Complete { scan.after_rotation_id = 0; @@ -467,13 +467,13 @@ fn recovery_spell(ctx: &ReducerContext, bot: &PlayerbotsBot) -> RecoveryLookup { fn defense_target( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, target_guid: u64, -) -> Result, crate::spell::ControlReadError> { - let Ok(target) = crate::combat::validate_attack_target(ctx, me, target_guid) else { +) -> Result, crate::actor::ControlReadError> { + let Ok(target) = crate::actor::attack_target_gate(ctx, me, target_guid) else { return Ok(None); }; - if crate::spell::control_status(ctx, target.guid, 64)?.is_some() { + if crate::actor::control_status(ctx, target.guid, 64)?.is_some() { return Ok(None); } Ok(Some(target)) @@ -841,7 +841,7 @@ pub fn playerbots_select_controller( guid: u64, controller: Controller, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if controller == Controller::Legacy { return Err("Legacy controller selection is retired; select Cohort instead".to_string()); } @@ -911,7 +911,7 @@ pub fn playerbots_migrate_legacy_controllers( ctx: &ReducerContext, character_guids: Vec, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if character_guids.len() > CONTROLLER_MIGRATION_BATCH_LIMIT { return Err(format!( "Legacy controller migration batch exceeds {CONTROLLER_MIGRATION_BATCH_LIMIT} Characters" @@ -945,7 +945,7 @@ pub fn playerbots_migrate_legacy_controllers( .is_some(); if transfer_checkpoint || transfer_intent - || crate::helpers::character_by_guid(ctx, guid).is_none() + || crate::actor::character_by_guid(ctx, guid).is_none() || super::goals::legacy_transfer_pending(ctx, guid) { continue; @@ -977,11 +977,11 @@ fn stop(ctx: &ReducerContext, guid: u64, state: &mut PlayerbotsRunner) { HISTORY_LIMIT, ); } - if let Some(cast) = crate::spell::pending_cast(ctx, guid) { + if let Some(cast) = crate::actor::pending_cast(ctx, guid) { if actions::observation(ctx, guid, actions::ActionKind::Cast) .is_some_and(|owned| owned.cast_id == cast.scheduled_id) { - crate::spell::cancel_cast_attempt(ctx, guid, cast.scheduled_id); + crate::actor::cancel_cast_attempt(ctx, guid, cast.scheduled_id); } } if crate::actor::sessionless_action_gate(ctx, guid).is_ok() { @@ -999,24 +999,19 @@ fn stop_movement(ctx: &ReducerContext, guid: u64) { { return; } - if let Some(mut me) = ctx.db.game_world_entity().guid().find(guid) { - crate::creatures::tick::stop_where_rendered(ctx, &mut me); - ctx.db.game_world_entity().guid().update(me); - } else { - ctx.db.game_creature_spline().guid().delete(guid); - } + crate::actor::stop_movement(ctx, guid); } fn geometry_revision(ctx: &ReducerContext, map: u32) -> Option { - crate::nav::coverage_generation(ctx, map) + crate::actor::movement::coverage_generation(ctx, map) } -fn distance(me: &crate::WorldEntity, dest: &Destination) -> f32 { +fn distance(me: &crate::tables::WorldEntity, dest: &Destination) -> f32 { ((me.x - dest.x).powi(2) + (me.y - dest.y).powi(2) + (me.z - dest.z).powi(2)).sqrt() } fn companion_destination( - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: &super::companion::Party, order: Option<&super::orders::CompanionOrderState>, ) -> Option { @@ -1074,7 +1069,7 @@ fn companion_destination( fn objective( ctx: &ReducerContext, bot: &PlayerbotsBot, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&super::companion::Party>, order: Option<&super::orders::CompanionOrderState>, state: &mut PlayerbotsRunner, @@ -1340,7 +1335,12 @@ fn restore_transfer_deferral(state: &mut PlayerbotsRunner, until_micros: i64) { state.next_eligible_micros = until_micros; } -fn observe(ctx: &ReducerContext, me: &crate::WorldEntity, state: &mut PlayerbotsRunner, now: i64) { +fn observe( + ctx: &ReducerContext, + me: &crate::tables::WorldEntity, + state: &mut PlayerbotsRunner, + now: i64, +) { if let Some(fg) = &state.foreground { if fg.generation != state.generation || (fg.map_id, fg.instance_id) != (me.map_id, me.instance_id) @@ -1389,7 +1389,7 @@ fn observe(ctx: &ReducerContext, me: &crate::WorldEntity, state: &mut Playerbots } } Running::Cast(handle) => { - if let Some(current) = crate::spell::pending_cast(ctx, me.guid) + if let Some(current) = crate::actor::pending_cast(ctx, me.guid) .filter(|h| h.scheduled_id == handle.scheduled_id) { // Direct damage moves due_micros without changing the core cast identity. @@ -1454,7 +1454,7 @@ fn admit_exact_target( ) -> Result<(), crate::actor::CommandOutcome> { let target = crate::actor::companion_target_facts(ctx, bot_guid, target_guid)?; if !party.enemies.iter().any(|enemy| enemy.guid == target.guid) { - party.enemies.push(crate::group::PartyEnemyFacts { + party.enemies.push(crate::actor::PartyEnemyFacts { guid: target.guid, map_id: target.map_id, instance_id: target.instance_id, @@ -1939,7 +1939,7 @@ fn run( RecoveryLookup::Spell(row) => Some(row), RecoveryLookup::Pending | RecoveryLookup::Missing => None, }; - let restricted = crate::helpers::live_entity(ctx, me.guid).is_err(); + let restricted = crate::actor::live_entity(ctx, me.guid).is_err(); let facts = decision::Facts { now, restricted, @@ -1979,7 +1979,7 @@ fn run( RecoveryLookup::Pending => {} RecoveryLookup::Missing => recovery.readiness = Readiness::Refused, RecoveryLookup::Spell(spell) => { - let pending = crate::spell::pending_cast(ctx, me.guid); + let pending = crate::actor::pending_cast(ctx, me.guid); let retains_heal = pending.is_some_and(|pending| { pending.spell_id == spell.spell_id && pending.target_guid == me.guid }); @@ -2106,7 +2106,7 @@ fn run( member .unit .as_ref() - .map(|unit| crate::group::PartyPartitionFacts { + .map(|unit| crate::actor::PartyPartitionFacts { map_id: unit.map_id, instance_id: unit.instance_id, locator_revision: 0, @@ -2118,7 +2118,7 @@ fn run( (partition.map_id, partition.instance_id) != (me.map_id, me.instance_id) }) .or_else(|| { - (party.is_none() && !partition_ok).then_some(crate::group::PartyPartitionFacts { + (party.is_none() && !partition_ok).then_some(crate::actor::PartyPartitionFacts { map_id: destination.map_id, instance_id: destination.instance_id, locator_revision: 0, @@ -2518,14 +2518,14 @@ fn run( { if let Some(foreground) = state.foreground.clone() { if let Running::Cast(handle) = foreground.running { - if crate::spell::expire_cast_attempt(ctx, me.guid, handle.scheduled_id, deadline) { + if crate::actor::expire_cast_attempt(ctx, me.guid, handle.scheduled_id, deadline) { state.foreground = None; bounded_push( &mut state.history, Transition { at_micros: now, chosen: Some(foreground.candidate), - outcome: RunnerOutcome::CastFinished(crate::spell::CastFinish::Expired), + outcome: RunnerOutcome::CastFinished(crate::actor::CastFinish::Expired), }, HISTORY_LIMIT, ); @@ -2814,7 +2814,7 @@ fn run( fn execute( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, objective_destination: &Destination, candidate: Candidate, state: &mut PlayerbotsRunner, @@ -2982,8 +2982,8 @@ fn execute( } match super::actions::cast(ctx, me.guid, spell, target) { Ok( - crate::spell::CastStart::Started(handle) - | crate::spell::CastStart::Waiting(handle), + crate::actor::CastStart::Started(handle) + | crate::actor::CastStart::Waiting(handle), ) => { let mut actual = candidate; actual.id.action = Action::Cast(CastAction { @@ -3000,7 +3000,7 @@ fn execute( }); state.last_outcome = RunnerOutcome::Waiting; } - Ok(crate::spell::CastStart::Resolved) => { + Ok(crate::actor::CastStart::Resolved) => { state.cast_progress = Some(CastProgress { observed_micros: now, scheduled_id: 0, @@ -3008,7 +3008,7 @@ fn execute( target, }); state.last_outcome = - RunnerOutcome::CastFinished(crate::spell::CastFinish::Resolved); + RunnerOutcome::CastFinished(crate::actor::CastFinish::Resolved); if state.companion_heal_target_guid == Some(target) { state.companion_heal_target_guid = None; } @@ -3116,7 +3116,7 @@ pub(super) fn fixture_refuse_quest_candidate( quest: u32, target: u64, ) -> Result<(), String> { - let me = crate::helpers::live_entity(ctx, guid)?; + let me = crate::actor::live_entity(ctx, guid)?; let mut state = PlayerbotsRunner::load(ctx, guid).ok_or("runner missing")?; let destination = state .objective @@ -3163,7 +3163,7 @@ crate::game_hook!(on_cast_finished, fn playerbots_runner_cast_finished(ctx, payl if fg.generation != state.generation || handle.scheduled_id != payload.scheduled_id { return; } if !ctx.db.game_world_entity().guid().find(payload.caster_guid).is_some_and(|e| (e.map_id, e.instance_id) == (fg.map_id, fg.instance_id)) { return; } let now = ctx.timestamp.to_micros_since_unix_epoch(); - if matches!(payload.outcome, crate::spell::CastFinish::Resolved) { + if matches!(payload.outcome, crate::actor::CastFinish::Resolved) { state.cast_progress = Some(CastProgress { observed_micros: now, scheduled_id: handle.scheduled_id, spell: handle.spell_id, target: handle.target_guid }); state.retry_candidate = None; } @@ -3214,7 +3214,7 @@ crate::game_hook!(on_group_invite, fn playerbots_auto_accept(ctx, payload) { { return; } - let inviter = crate::helpers::character_by_guid(ctx, payload.inviter_guid) + let inviter = crate::actor::character_by_guid(ctx, payload.inviter_guid) .map(|character| character.name) .unwrap_or_else(|| payload.inviter_guid.to_string()); match crate::actor::accept_group_invite(ctx, payload.target_guid) { diff --git a/playerbots/src/runner/movement.rs b/playerbots/src/runner/movement.rs index f3106e2..1a8a909 100644 --- a/playerbots/src/runner/movement.rs +++ b/playerbots/src/runner/movement.rs @@ -5,7 +5,7 @@ use super::*; pub(super) const INTERVAL: i64 = 500_000; const BATCH_LIMIT: usize = 1_024; const PLAN_LIMIT: usize = 32; -const EXPANSION_LIMIT: u32 = 4 * crate::nav::LEG_MAX_EXPANSIONS; +const EXPANSION_LIMIT: u32 = 4 * crate::actor::movement::LEG_MAX_EXPANSIONS; const RENEWAL_LEAD_MICROS: u64 = 2_000_000; struct PlanningBudget { @@ -16,7 +16,7 @@ struct PlanningBudget { impl PlanningBudget { fn available(&self) -> bool { self.searches < PLAN_LIMIT - && self.expansions <= EXPANSION_LIMIT - crate::nav::LEG_MAX_EXPANSIONS + && self.expansions <= EXPANSION_LIMIT - crate::actor::movement::LEG_MAX_EXPANSIONS } fn record(&mut self, expansions: u32) { @@ -27,7 +27,7 @@ impl PlanningBudget { fn begin( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, destination: &Destination, stand_off: f32, ) -> u32 { @@ -37,7 +37,7 @@ fn begin( let horizontal_stop = (stand_off.powi(2) - (me.z - destination.z).powi(2)) .max(0.0) .sqrt(); - let route = crate::nav::route_path( + let route = crate::actor::movement::route_path( ctx, me.map_id, me.instance_id, @@ -58,8 +58,8 @@ fn begin( distance(me, destination) <= stand_off + 0.05, route.step, ); - if let Ok(mover) = crate::helpers::live_entity(ctx, me.guid) { - crate::creatures::tick::emit_creature_path(ctx, mover, route.points, true); + if let Ok(mover) = crate::actor::live_entity(ctx, me.guid) { + crate::actor::movement::emit_creature_path(ctx, mover, route.points, true); } expansions } @@ -99,7 +99,7 @@ pub(super) fn retained( .find(state.character_guid) .is_some_and(|spline| { spline.path.is_some_and(|path| { - path.navigation == crate::nav::inputs(ctx, destination.map_id) + path.navigation == crate::actor::movement::inputs(ctx, destination.map_id) }) && spline .start_micros .saturating_add(u64::from(spline.dur_ms) * 1000) @@ -328,11 +328,9 @@ fn advance( return true; }; if let Some(spline) = spline { - if spline - .path - .as_ref() - .is_some_and(|path| path.navigation != crate::nav::inputs(ctx, me.map_id)) - { + if spline.path.as_ref().is_some_and(|path| { + path.navigation != crate::actor::movement::inputs(ctx, me.map_id) + }) { stop(ctx, guid, state); state.last_outcome = RunnerOutcome::Cancelled; return true; @@ -401,7 +399,7 @@ fn advance( fn destination( ctx: &ReducerContext, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, state: &PlayerbotsRunner, foreground: &Foreground, movement: &MovementRun, @@ -450,8 +448,8 @@ fn destination( return None; } if foreground.candidate.id.reason == Reason::Quest - && crate::combat::validate_attack_target(ctx, me, guid).is_ok() - && crate::spell::control_status(ctx, guid, 64).ok()?.is_some() + && crate::actor::attack_target_gate(ctx, me, guid).is_ok() + && crate::actor::control_status(ctx, guid, 64).ok()?.is_some() { return None; } diff --git a/playerbots/src/spell_ranks.rs b/playerbots/src/spell_ranks.rs index 84054af..4b3a26e 100644 --- a/playerbots/src/spell_ranks.rs +++ b/playerbots/src/spell_ranks.rs @@ -1,6 +1,6 @@ //! Rank selection for the spell families named by a kit or rotation. -use crate::game_spell_chain; +use crate::tables::game_spell_chain; use spacetimedb::ReducerContext; const FAMILY_LIMIT: usize = 16; @@ -32,6 +32,6 @@ pub(super) fn highest_known(ctx: &ReducerContext, guid: u64, spell: u32) -> Resu Ok(family(ctx, spell)? .into_iter() .rev() - .find(|rank| crate::spell::knows_spell(ctx, guid, *rank)) + .find(|rank| crate::actor::knows_spell(ctx, guid, *rank)) .unwrap_or(spell)) } diff --git a/playerbots/src/target_claims.rs b/playerbots/src/target_claims.rs index 5d7ece4..1cde776 100644 --- a/playerbots/src/target_claims.rs +++ b/playerbots/src/target_claims.rs @@ -4,7 +4,7 @@ use super::decision::Reason; use super::pkg_playerbots_bot; use super::recovery::Work; use super::runner::{pkg_playerbots_runner, Controller, PlayerbotsRunner, RunnerOutcome}; -use crate::{game_group_member, game_world_entity}; +use crate::tables::{game_group_member, game_world_entity}; use spacetimedb::ReducerContext; const OWNER_LIMIT: usize = 16; @@ -94,7 +94,7 @@ pub(super) fn selected(ctx: &ReducerContext, state: &PlayerbotsRunner) -> Option && (owner.map_id, owner.instance_id) == (creature.map_id, creature.instance_id) && (attempt.destination.map_id, attempt.destination.instance_id) == (owner.map_id, owner.instance_id) - && attempt.geometry == crate::nav::inputs(ctx, owner.map_id)) + && attempt.geometry == crate::actor::movement::inputs(ctx, owner.map_id)) .then_some(target) } diff --git a/playerbots/src/transfer.rs b/playerbots/src/transfer.rs index 38b9636..384ab29 100644 --- a/playerbots/src/transfer.rs +++ b/playerbots/src/transfer.rs @@ -29,8 +29,8 @@ pub(super) fn companion_member( pub(super) fn candidate( ctx: &ReducerContext, - me: &crate::WorldEntity, - partition: crate::group::PartyPartitionFacts, + me: &crate::tables::WorldEntity, + partition: crate::actor::PartyPartitionFacts, objective: u64, ) -> ActionNode { let route = SUPPORTED_AREA_TRIGGERS @@ -70,7 +70,7 @@ pub(super) fn candidate( node } -fn distance_sq(me: &crate::WorldEntity, route: &crate::quest::AreaTriggerRoute) -> f32 { +fn distance_sq(me: &crate::tables::WorldEntity, route: &crate::actor::AreaTriggerRoute) -> f32 { (me.x - route.source_x).powi(2) + (me.y - route.source_y).powi(2) + (me.z - route.source_z).powi(2) @@ -79,7 +79,7 @@ fn distance_sq(me: &crate::WorldEntity, route: &crate::quest::AreaTriggerRoute) pub(super) fn normalize( ctx: &ReducerContext, state: &mut PlayerbotsRunner, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, action: TransferAction, intent_id: u64, generation: u64, @@ -129,7 +129,7 @@ pub(super) fn normalize( pub(super) fn execute( ctx: &ReducerContext, state: &mut PlayerbotsRunner, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, action: TransferAction, now: i64, ) -> Result { @@ -280,7 +280,7 @@ pub(super) fn companion_recovery_complete( checkpoint: TransferCheckpoint, objective: Option<&super::runner::Objective>, party: Option<&super::companion::Party>, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, ) -> bool { let Some(TransferPurpose::Companion(CompanionTransferPurpose { member_guid, .. })) = checkpoint.purpose @@ -334,7 +334,11 @@ pub(super) fn retains_quest( ) } -pub(super) fn begin_arrival(state: &mut PlayerbotsRunner, me: &crate::WorldEntity, now: i64) { +pub(super) fn begin_arrival( + state: &mut PlayerbotsRunner, + me: &crate::tables::WorldEntity, + now: i64, +) { let Some(checkpoint) = &mut state.transfer_checkpoint else { return; }; @@ -356,7 +360,7 @@ pub(super) fn arrival_expired(state: &PlayerbotsRunner, now: i64) -> bool { pub(super) fn arrival( ctx: &ReducerContext, state: &PlayerbotsRunner, - me: &crate::WorldEntity, + me: &crate::tables::WorldEntity, party: Option<&super::companion::Party>, now: i64, ) -> Arrival { diff --git a/playerbots/src/transfer_assist_fixture.rs b/playerbots/src/transfer_assist_fixture.rs index b22db25..28671c7 100644 --- a/playerbots/src/transfer_assist_fixture.rs +++ b/playerbots/src/transfer_assist_fixture.rs @@ -6,7 +6,7 @@ use super::orders::{CommandRecord, CompanionOrder, CompanionOrderState}; use super::{ pkg_playerbots_bot, pkg_playerbots_companion_order, pkg_playerbots_runner, Controller, }; -use crate::{ +use crate::tables::{ game_character, game_group, game_group_member, game_group_member_partition, game_group_roster_revision, game_world_entity, }; @@ -88,11 +88,11 @@ struct PartitionSnapshot { map_id: u32, instance_id: u64, locator_revision: u64, - state: crate::PartyPartitionState, + state: crate::tables::PartyPartitionState, } -impl From<&crate::GroupMemberPartition> for PartitionSnapshot { - fn from(row: &crate::GroupMemberPartition) -> Self { +impl From<&crate::tables::GroupMemberPartition> for PartitionSnapshot { + fn from(row: &crate::tables::GroupMemberPartition) -> Self { Self { character_guid: row.character_guid, group_id: row.group_id, @@ -219,7 +219,7 @@ fn source_party( || active_partitions.iter().any(|partition| { !member_guids.contains(&partition.character_guid) || partition.group_id != order.group_id - || partition.state != crate::PartyPartitionState::Known + || partition.state != crate::tables::PartyPartitionState::Known }) { return Err("Assist source fixture party mirror changed".to_string()); @@ -305,7 +305,7 @@ fn relocate_character( guid: u64, destination: (f32, f32, f32, f32), ) -> Result<(), String> { - let mut character = crate::helpers::character_by_guid(ctx, guid) + let mut character = crate::actor::character_by_guid(ctx, guid) .ok_or("Assist source fixture Character disappeared")?; character.map_id = DESTINATION_MAP; character.pending_instance_id = DESTINATION_INSTANCE; @@ -399,7 +399,7 @@ pub fn playerbots_transfer_assist_source_stage( leader_guid: u64, priest_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if bot_guid == 0 || leader_guid == 0 || priest_guid == 0 @@ -441,9 +441,9 @@ pub fn playerbots_transfer_assist_source_stage( return Err("Assist source fixture requires the selected Priest".to_string()); } let party = source_party(ctx, bot_guid, leader_guid, priest_guid)?; - let bot_body = crate::helpers::live_entity(ctx, bot_guid)?; - let leader_body = crate::helpers::live_entity(ctx, leader_guid)?; - let priest_body = crate::helpers::live_entity(ctx, priest_guid)?; + let bot_body = crate::actor::live_entity(ctx, bot_guid)?; + let leader_body = crate::actor::live_entity(ctx, leader_guid)?; + let priest_body = crate::actor::live_entity(ctx, priest_guid)?; let source_partition = (bot_body.map_id, bot_body.instance_id); if !bot_body.is_player() || !leader_body.is_player() @@ -459,7 +459,7 @@ pub fn playerbots_transfer_assist_source_stage( return Err("Assist source fixture requires one local party partition".to_string()); } for guid in [bot_guid, leader_guid, priest_guid] { - let character = crate::helpers::character_by_guid(ctx, guid) + let character = crate::actor::character_by_guid(ctx, guid) .ok_or("Assist source fixture Character is absent")?; if (character.map_id, character.pending_instance_id) != source_partition { return Err("Assist source fixture Character partition changed".to_string()); @@ -468,8 +468,8 @@ pub fn playerbots_transfer_assist_source_stage( let retained_before = retained_work(ctx, bot_guid, party.order.group_id)?; let leader_source = (leader_body.x, leader_body.y, leader_body.z); let priest_source = (priest_body.x, priest_body.y, priest_body.z); - crate::package_fixture::remove_live_character(ctx, leader_guid)?; - crate::package_fixture::remove_live_character(ctx, priest_guid)?; + crate::package::fixture::remove_live_character(ctx, leader_guid)?; + crate::package::fixture::remove_live_character(ctx, priest_guid)?; relocate_character(ctx, leader_guid, LEADER_DESTINATION)?; relocate_character(ctx, priest_guid, PRIEST_DESTINATION)?; let leader_after = relocate_partition(ctx, leader_guid)?; @@ -540,7 +540,7 @@ pub fn playerbots_transfer_assist_destination_stage( priest_guid: u64, unused_mage_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let guids = [bot_guid, leader_guid, priest_guid, unused_mage_guid]; if guids.contains(&0) || guids @@ -550,8 +550,8 @@ pub fn playerbots_transfer_assist_destination_stage( { return Err("Assist destination fixture requires four distinct Characters".to_string()); } - if crate::helpers::character_by_guid(ctx, bot_guid).is_some() - || crate::helpers::character_by_guid(ctx, unused_mage_guid).is_some() + if crate::actor::character_by_guid(ctx, bot_guid).is_some() + || crate::actor::character_by_guid(ctx, unused_mage_guid).is_some() || ctx .db .game_group_member() @@ -591,5 +591,5 @@ pub fn playerbots_transfer_assist_destination_stage( super::fixture::playerbots_fixture_freeze(ctx, priest_guid)?; relocate_live_character(ctx, leader_guid, LEADER_DESTINATION)?; relocate_live_character(ctx, priest_guid, PRIEST_DESTINATION)?; - crate::package_fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(60_000_000)) + crate::package::fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(60_000_000)) } diff --git a/playerbots/src/transfer_destination_fixture.rs b/playerbots/src/transfer_destination_fixture.rs index 910d5a9..70f6645 100644 --- a/playerbots/src/transfer_destination_fixture.rs +++ b/playerbots/src/transfer_destination_fixture.rs @@ -10,7 +10,7 @@ use super::quest_catalog::{ PlayerbotsQuestCatalog, CATALOG_BLUEPRINT_REVISION, CATALOG_NAME, CATALOG_REVISION, }; use super::runner::ObjectiveKind; -use crate::{ +use crate::tables::{ game_character_quest, game_creature_quest, game_creature_spawn, game_creature_template, game_quest_objective, game_quest_template, game_world_entity, }; @@ -30,17 +30,17 @@ fn destination_guid(entry: u32, ordinal: u64) -> u64 { } fn require_private_fixture(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - crate::package_fixture::require_no_imported_content(ctx) + crate::package::require_operator(ctx)?; + crate::package::fixture::require_no_imported_content(ctx) } /// The revision the real Navigation Inputs import advances. It does not depend on the map. fn imported_navigation_revision(ctx: &ReducerContext) -> Option { - crate::nav::inputs(ctx, DESTINATION_MAP).imported_revision + crate::actor::movement::inputs(ctx, DESTINATION_MAP).imported_revision } -fn quest_template(entry: u32, prerequisite: u32) -> crate::QuestTemplate { - crate::QuestTemplate { +fn quest_template(entry: u32, prerequisite: u32) -> crate::tables::QuestTemplate { + crate::tables::QuestTemplate { entry, min_level: 1, quest_level: 1, @@ -85,7 +85,7 @@ fn stage_creature( template.faction_template = if hostile { 14 } else { 35 }; template.aggro_range = 0; let template = ctx.db.game_creature_template().insert(template); - let spawn = crate::CreatureSpawn { + let spawn = crate::tables::CreatureSpawn { guid, entry, map_id, @@ -93,17 +93,18 @@ fn stage_creature( y: at.1, z: at.2, orientation: 0.0, - respawn_at: crate::creatures::timer_never(ctx), - despawn_at: crate::creatures::timer_never(ctx), + respawn_at: crate::package::fixture::timer_never(ctx), + despawn_at: crate::package::fixture::timer_never(ctx), movement_type: 0, respawn_secs: 60, life_seq: 1, }; - let mut entity = crate::creatures::build_creature_entity(&spawn, &template, 0, instance_id); + let mut entity = + crate::package::fixture::build_creature_entity(&spawn, &template, 0, instance_id); ctx.db.game_creature_spawn().insert(spawn); entity.map_id = map_id; entity.instance_id = instance_id; - crate::creatures::insert_creature_entity(ctx, entity); + crate::package::fixture::insert_creature_entity(ctx, entity); Ok(CatalogDestination { kind: CatalogEntityKind::Creature, entry, @@ -117,12 +118,14 @@ fn stage_creature( } fn insert_relation(ctx: &ReducerContext, id: u64, creature_entry: u32, quest_entry: u32, role: u8) { - ctx.db.game_creature_quest().insert(crate::CreatureQuest { - id, - creature_entry, - quest_entry, - role, - }); + ctx.db + .game_creature_quest() + .insert(crate::tables::CreatureQuest { + id, + creature_entry, + quest_entry, + role, + }); } fn insert_header(ctx: &ReducerContext, content_revision: &str) { @@ -330,7 +333,7 @@ pub fn playerbots_transfer_destination_catalogue_stage( mode: u8, ) -> Result<(), String> { require_private_fixture(ctx)?; - if crate::helpers::character_by_guid(ctx, character_guid).is_some() + if crate::actor::character_by_guid(ctx, character_guid).is_some() || ctx .db .game_world_entity() @@ -407,25 +410,27 @@ pub fn playerbots_transfer_destination_catalogue_stage( RELATION_BASE, first.entry, quest_entry, - crate::quest::quest_role::START, + crate::actor::quest_role::START, ); insert_relation( ctx, RELATION_BASE + 1, if mode == 1 { first.entry } else { second.entry }, quest_entry, - crate::quest::quest_role::END, + crate::actor::quest_role::END, ); let (actual_ender, objective) = if mode == 1 { - ctx.db.game_quest_objective().insert(crate::QuestObjective { - id: OBJECTIVE_ID, - quest_entry, - obj_index: 0, - kind: crate::quest::objective_kind::KILL_CREATURE, - target_entry: 6, - required_count: 10, - }); + ctx.db + .game_quest_objective() + .insert(crate::tables::QuestObjective { + id: OBJECTIVE_ID, + quest_entry, + obj_index: 0, + kind: crate::actor::objective_kind::KILL_CREATURE, + target_entry: 6, + required_count: 10, + }); let objective = PlayerbotsCatalogObjective { id: OBJECTIVE_ID, quest_entry, diff --git a/playerbots/src/transfer_fixture.rs b/playerbots/src/transfer_fixture.rs index d1dbb8c..d5de7e6 100644 --- a/playerbots/src/transfer_fixture.rs +++ b/playerbots/src/transfer_fixture.rs @@ -2,7 +2,7 @@ //! Private AreaTrigger staging for durable Transfer cases. -use crate::{ +use crate::tables::{ game_area_trigger, game_areatrigger_teleport, game_character, game_group, game_group_member, game_group_member_partition, game_group_roster_revision, game_world_entity, }; @@ -77,21 +77,23 @@ fn declare_route(ctx: &ReducerContext, route: FixtureRoute) -> Result<(), String route.trigger )); } - ctx.db.game_area_trigger().insert(crate::GameAreaTrigger { - id: route.trigger, - map_id: route.source_map, - x: route.source.0, - y: route.source.1, - z: route.source.2, - radius: route.radius, - box_length: 0.0, - box_width: 0.0, - box_height: 0.0, - box_yaw: 0.0, - }); + ctx.db + .game_area_trigger() + .insert(crate::tables::GameAreaTrigger { + id: route.trigger, + map_id: route.source_map, + x: route.source.0, + y: route.source.1, + z: route.source.2, + radius: route.radius, + box_length: 0.0, + box_width: 0.0, + box_height: 0.0, + box_yaw: 0.0, + }); ctx.db .game_areatrigger_teleport() - .insert(crate::AreatriggerTeleport { + .insert(crate::tables::AreatriggerTeleport { trigger_id: route.trigger, target_map: route.target_map, x: route.landing.0, @@ -134,7 +136,7 @@ fn place_character( landing: (f32, f32, f32, f32), ) -> Result<(), String> { let mut character = - crate::helpers::character_by_guid(ctx, guid).ok_or("Transfer fixture Character missing")?; + crate::actor::character_by_guid(ctx, guid).ok_or("Transfer fixture Character missing")?; character.map_id = map_id; character.pending_instance_id = instance_id; character.x = landing.0; @@ -146,7 +148,7 @@ fn place_character( } fn move_partition( - partitions: &mut [crate::group::GroupMemberPartition], + partitions: &mut [crate::tables::GroupMemberPartition], guid: u64, map_id: u32, instance_id: u64, @@ -175,7 +177,7 @@ pub fn playerbots_transfer_fixture_entry_route_stage( companion_guid: u64, leader_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let group = ctx .db .game_group() @@ -188,28 +190,28 @@ pub fn playerbots_transfer_fixture_entry_route_stage( .game_group_member() .by_group() .filter(&GROUP) - .take(crate::group::GROUP_MAX_MEMBERS + 1) + .take(lyracore_shared::group::GROUP_MAX_MEMBERS + 1) .collect(); - if members.len() > crate::group::GROUP_MAX_MEMBERS + if members.len() > lyracore_shared::group::GROUP_MAX_MEMBERS || !members .iter() .any(|member| member.character_guid == companion_guid) { return Err("Transfer entry fixture companion is outside its bounded party".to_string()); } - let companion = crate::helpers::live_entity(ctx, companion_guid)?; - let leader = crate::helpers::live_entity(ctx, leader_guid)?; + let companion = crate::actor::live_entity(ctx, companion_guid)?; + let leader = crate::actor::live_entity(ctx, leader_guid)?; if (companion.map_id, companion.instance_id) != (leader.map_id, leader.instance_id) { return Err("Transfer entry fixture requires the local command partition".to_string()); } let (route, _) = fixture_route(2)?; declare_route(ctx, route)?; place_live(ctx, companion_guid, 0, 0, ENTRY_SOURCE)?; - let actor = crate::SessionActor { + let actor = crate::package::fixture::SessionActor { guid: leader_guid, ownership: None, }; - crate::package_fixture::admit_to_instance( + crate::package::fixture::admit_to_instance( ctx, group.leader_guid, 36, @@ -229,13 +231,13 @@ pub fn playerbots_transfer_fixture_stage( leader_guid: u64, mode: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; stage_transfer_fixture( ctx, companion_guid, leader_guid, mode, - crate::SessionActor { + crate::package::fixture::SessionActor { guid: leader_guid, ownership: None, }, @@ -249,9 +251,9 @@ pub fn playerbots_transfer_fixture_stage_authenticated( companion_guid: u64, leader_guid: u64, mode: u8, - request_actor: crate::SessionActor, + request_actor: crate::package::fixture::SessionActor, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if request_actor.guid != leader_guid { return Err("Transfer fixture authority does not own the party leader".to_string()); } @@ -263,7 +265,7 @@ fn stage_transfer_fixture( companion_guid: u64, leader_guid: u64, mode: u8, - request_actor: crate::SessionActor, + request_actor: crate::package::fixture::SessionActor, ) -> Result<(), String> { let (route, declared) = fixture_route(mode)?; let group = ctx @@ -278,9 +280,9 @@ fn stage_transfer_fixture( .game_group_member() .by_group() .filter(&GROUP) - .take(crate::group::GROUP_MAX_MEMBERS + 1) + .take(lyracore_shared::group::GROUP_MAX_MEMBERS + 1) .collect(); - if members.len() > crate::group::GROUP_MAX_MEMBERS { + if members.len() > lyracore_shared::group::GROUP_MAX_MEMBERS { return Err("Transfer fixture party exceeds the supported member bound".to_string()); } if !members @@ -305,7 +307,7 @@ fn stage_transfer_fixture( } else { leader_guid }; - crate::package_fixture::admit_to_instance( + crate::package::fixture::admit_to_instance( ctx, bound_guid, 36, @@ -325,7 +327,7 @@ fn stage_transfer_fixture( )?; } - crate::package_fixture::remove_live_character(ctx, leader_guid)?; + crate::package::fixture::remove_live_character(ctx, leader_guid)?; let leader_instance = if route.target_map == 36 { DESTINATION_INSTANCE } else { @@ -373,7 +375,7 @@ fn stage_transfer_fixture( .map(|(_, character_guid)| character_guid) .collect(); let raid_slots = vec![0; accepted_members.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, GROUP, leader_guid, diff --git a/playerbots/src/transfer_gateway_fixture.rs b/playerbots/src/transfer_gateway_fixture.rs index bdd9a00..2dc8617 100644 --- a/playerbots/src/transfer_gateway_fixture.rs +++ b/playerbots/src/transfer_gateway_fixture.rs @@ -3,8 +3,8 @@ //! Private three-database authority and mirror-failure staging for Gateway Transfer cases. use super::pkg_playerbots_bot; -use crate::transfer::game_transfer_in; -use crate::{ +use crate::tables::game_transfer_in; +use crate::tables::{ game_character, game_character_shard, game_group, game_group_member, game_group_member_partition, game_group_roster_revision, game_world_entity, }; @@ -34,7 +34,7 @@ pub struct PlayerbotsTransferGatewayIdentity { /// Record the actual Module identity used by one private Gateway Transfer database. #[reducer] pub fn playerbots_transfer_gateway_identity_stage(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let identities = ctx.db.pkg_playerbots_transfer_gateway_identity(); if identities.id().find(0).is_some() { return Err("Gateway Transfer identity fixture requires a fresh row".to_string()); @@ -51,8 +51,8 @@ fn partition( membership_revision: u64, map_id: u32, instance_id: u64, -) -> crate::GroupMemberPartition { - crate::GroupMemberPartition { +) -> crate::tables::GroupMemberPartition { + crate::tables::GroupMemberPartition { character_guid, group_id: GROUP, membership_revision, @@ -60,12 +60,17 @@ fn partition( map_id, instance_id, locator_revision: 1, - state: crate::PartyPartitionState::Known, + state: crate::tables::PartyPartitionState::Known, } } -fn locator(character_guid: u64, map_id: u32, instance_id: u64, now: i64) -> crate::CharacterShard { - crate::CharacterShard { +fn locator( + character_guid: u64, + map_id: u32, + instance_id: u64, + now: i64, +) -> crate::tables::CharacterShard { + crate::tables::CharacterShard { character_guid, map_id, instance_id, @@ -86,7 +91,7 @@ struct GatewayParty { loot_threshold: u8, master_looter_guid: u64, member_guids: Vec, - partitions: Vec, + partitions: Vec, roster_revision: u64, } @@ -156,7 +161,7 @@ fn exact_gateway_party( }; partition.group_id != GROUP || !partition.member_active - || partition.state != crate::PartyPartitionState::Known + || partition.state != crate::tables::PartyPartitionState::Known || partition.membership_revision != membership_revision }) { @@ -207,7 +212,7 @@ pub fn playerbots_transfer_gateway_realm_stage( destination_map: u32, destination_instance: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let character_guids = [companion_guid, leader_guid, priest_guid, mage_guid]; if character_guids.contains(&0) || character_guids @@ -250,7 +255,7 @@ pub fn playerbots_transfer_gateway_realm_stage( } let locators = ctx.db.game_character_shard(); - ctx.db.game_group().insert(crate::Group { + ctx.db.game_group().insert(crate::tables::Group { group_id: GROUP, leader_guid, loot_method: PARTY_LOOT_METHOD, @@ -260,28 +265,28 @@ pub fn playerbots_transfer_gateway_realm_stage( group_type: 0, }); let members = ctx.db.game_group_member(); - members.insert(crate::GroupMember { + members.insert(crate::tables::GroupMember { id: LEADER_MEMBER, group_id: GROUP, character_guid: leader_guid, owner_identity: Identity::ZERO, raid_slot: 0, }); - members.insert(crate::GroupMember { + members.insert(crate::tables::GroupMember { id: PRIEST_MEMBER, group_id: GROUP, character_guid: priest_guid, owner_identity: Identity::ZERO, raid_slot: 0, }); - members.insert(crate::GroupMember { + members.insert(crate::tables::GroupMember { id: MAGE_MEMBER, group_id: GROUP, character_guid: mage_guid, owner_identity: Identity::ZERO, raid_slot: 0, }); - members.insert(crate::GroupMember { + members.insert(crate::tables::GroupMember { id: COMPANION_MEMBER, group_id: GROUP, character_guid: companion_guid, @@ -314,7 +319,7 @@ pub fn playerbots_transfer_gateway_realm_stage( )); ctx.db .game_group_roster_revision() - .insert(crate::GroupRosterRevision { + .insert(crate::tables::GroupRosterRevision { group_id: GROUP, revision: 1, active: true, @@ -348,7 +353,7 @@ pub fn playerbots_transfer_gateway_realm_stage( .filter(|row| { (row.map_id, row.instance_id) == (leader.map_id, leader.instance_id) && row.locator_revision == leader.revision - && row.state == crate::PartyPartitionState::Known + && row.state == crate::tables::PartyPartitionState::Known }); if leader_partition.is_none() { return Err("Gateway Transfer fixture did not project its leader crossing".to_string()); @@ -370,7 +375,7 @@ pub fn playerbots_transfer_gateway_destination_leader_stage( destination_map: u32, destination_instance: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let guids = [companion_guid, leader_guid, priest_guid, mage_guid]; if guids.contains(&0) || guids @@ -382,7 +387,7 @@ pub fn playerbots_transfer_gateway_destination_leader_stage( } if [companion_guid, priest_guid, mage_guid] .iter() - .any(|guid| crate::helpers::character_by_guid(ctx, *guid).is_some()) + .any(|guid| crate::actor::character_by_guid(ctx, *guid).is_some()) || ctx.db.game_group().group_id().find(GROUP).is_some() || ctx .db @@ -409,7 +414,7 @@ pub fn playerbots_transfer_gateway_destination_leader_stage( ctx.db.pkg_playerbots_bot().id().delete(leader.id); crate::actor::set_sessionless_action_consent(ctx, leader_guid, true); - let mut character = crate::helpers::character_by_guid(ctx, leader_guid) + let mut character = crate::actor::character_by_guid(ctx, leader_guid) .ok_or("Gateway Transfer destination leader Character is absent")?; character.map_id = destination_map; character.pending_instance_id = destination_instance; @@ -431,7 +436,7 @@ pub fn playerbots_transfer_gateway_destination_leader_stage( entity.grid_y = grid_y; entity.cell = lyracore_shared::spatial::grid_cell_id(grid_x, grid_y); entities.guid().update(entity); - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, GROUP, leader_guid, @@ -439,7 +444,7 @@ pub fn playerbots_transfer_gateway_destination_leader_stage( 2, 0, vec![leader_guid, companion_guid, priest_guid, mage_guid], - crate::SessionActor { + crate::package::fixture::SessionActor { guid: leader_guid, ownership: None, }, @@ -484,7 +489,7 @@ fn restage_completed_member_crossing( } let now = ctx.timestamp.to_micros_since_unix_epoch(); locators.insert(locator(character_guid, source_map, source_instance, now)); - crate::package_fixture::record_completed_transfer( + crate::package::fixture::record_completed_transfer( ctx, character_guid, destination_map, @@ -509,7 +514,7 @@ fn restage_completed_member_crossing( partition.map_id = settled.map_id; partition.instance_id = settled.instance_id; partition.locator_revision = settled.revision; - partition.state = crate::PartyPartitionState::Known; + partition.state = crate::tables::PartyPartitionState::Known; partitions.character_guid().update(partition); Ok(()) } @@ -525,7 +530,7 @@ pub fn playerbots_transfer_gateway_exit_realm_stage( priest_guid: u64, mage_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let party = exact_gateway_party(ctx, companion_guid, leader_guid, priest_guid, mage_guid)?; for (guid, map_id, instance_id, revision) in [ (companion_guid, 36, 5_098_078, 2), @@ -550,7 +555,7 @@ pub fn playerbots_transfer_gateway_exit_realm_stage( return Err("Gateway exit fixture Realm location changed".to_string()); } } - crate::package_fixture::record_completed_transfer(ctx, leader_guid, 0, 0); + crate::package::fixture::record_completed_transfer(ctx, leader_guid, 0, 0); let settled = ctx .db .game_character_shard() @@ -569,7 +574,7 @@ pub fn playerbots_transfer_gateway_exit_realm_stage( partition.map_id = settled.map_id; partition.instance_id = settled.instance_id; partition.locator_revision = settled.revision; - partition.state = crate::PartyPartitionState::Known; + partition.state = crate::tables::PartyPartitionState::Known; partitions.character_guid().update(partition); Ok(()) } @@ -584,16 +589,16 @@ pub fn playerbots_transfer_gateway_exit_destination_stage( leader_guid: u64, priest_guid: u64, mage_guid: u64, - request_actor: crate::SessionActor, + request_actor: crate::package::fixture::SessionActor, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let mut party = exact_gateway_party(ctx, companion_guid, leader_guid, priest_guid, mage_guid)?; if request_actor.guid != leader_guid || !exact_known_partition(&party, companion_guid, 36, 5_098_078, 2) || !exact_known_partition(&party, leader_guid, 36, 5_098_078, 2) || !exact_known_partition(&party, priest_guid, 0, 0, 1) || !exact_known_partition(&party, mage_guid, 0, 0, 1) - || crate::helpers::character_by_guid(ctx, companion_guid).is_some() + || crate::actor::character_by_guid(ctx, companion_guid).is_some() || ctx .db .game_world_entity() @@ -610,7 +615,7 @@ pub fn playerbots_transfer_gateway_exit_destination_stage( { return Err("Gateway exit destination is not the exact settled entry source".to_string()); } - let mut leader = crate::helpers::character_by_guid(ctx, leader_guid) + let mut leader = crate::actor::character_by_guid(ctx, leader_guid) .filter(|row| (row.map_id, row.pending_instance_id) == (36, 5_098_078)) .ok_or("Gateway exit destination leader Character changed")?; if ctx @@ -637,7 +642,7 @@ pub fn playerbots_transfer_gateway_exit_destination_stage( leader_partition.instance_id = 0; leader_partition.locator_revision = 3; let party_size = party.member_guids.len(); - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, GROUP, party.leader_guid, @@ -669,7 +674,7 @@ pub fn playerbots_transfer_gateway_assist_realm_stage( destination_map: u32, destination_instance: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let _group = ctx .db .game_group() @@ -724,7 +729,7 @@ pub fn playerbots_transfer_gateway_assist_realm_stage( }; !expected.contains(&partition.character_guid) || !partition.member_active - || partition.state != crate::PartyPartitionState::Known + || partition.state != crate::tables::PartyPartitionState::Known || partition.locator_revision != expected_revision || (partition.map_id, partition.instance_id) != expected_partition }) @@ -779,7 +784,7 @@ pub fn playerbots_transfer_gateway_mirror_fault( priest_guid: u64, mage_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let groups = ctx.db.game_group(); let mut current = groups .group_id() @@ -835,12 +840,13 @@ pub fn playerbots_transfer_gateway_mirror_fault( && ((partition.map_id == 36 && partition.instance_id == 5_098_078 && partition.locator_revision == 2 - && partition.state == crate::PartyPartitionState::Known) + && partition.state == crate::tables::PartyPartitionState::Known) || arrivals.first().is_some_and(|arrival| { partition.map_id == arrival.source_map_id && partition.instance_id == arrival.source_instance_id && partition.locator_revision == arrival.source_locator_revision - && partition.state == crate::PartyPartitionState::PendingTransfer + && partition.state + == crate::tables::PartyPartitionState::PendingTransfer })) }); if ( diff --git a/playerbots/src/transfer_lifecycle_fixture.rs b/playerbots/src/transfer_lifecycle_fixture.rs index c386e43..ef350b6 100644 --- a/playerbots/src/transfer_lifecycle_fixture.rs +++ b/playerbots/src/transfer_lifecycle_fixture.rs @@ -9,12 +9,15 @@ pub fn playerbots_lifecycle_stage_movement( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; use super::pkg_playerbots_bot; if ctx.db.pkg_playerbots_bot().count() != 1 { return Err("movement restart fixture requires one staged bot".to_string()); } - crate::package_fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(30_000_000))?; + crate::package::fixture::declare_next_movement_tick( + ctx, + TimeDuration::from_micros(30_000_000), + )?; super::fixture::playerbots_fixture_runner_stage(ctx, character_guid, false)?; super::fixture::playerbots_fixture_runner_select_cohort(ctx, character_guid)?; super::fixture::playerbots_fixture_runner_pass_once(ctx, character_guid) diff --git a/playerbots/src/transfer_orders_fixture.rs b/playerbots/src/transfer_orders_fixture.rs index 6e2e15e..c11cd94 100644 --- a/playerbots/src/transfer_orders_fixture.rs +++ b/playerbots/src/transfer_orders_fixture.rs @@ -3,7 +3,7 @@ //! Private party inputs for order decisions around the audited Deadmines entrance. use super::pkg_playerbots_bot; -use crate::{ +use crate::tables::{ game_area_trigger, game_areatrigger_teleport, game_character, game_group, game_group_member_partition, game_group_roster_revision, game_world_entity, }; @@ -23,7 +23,7 @@ fn place_body( position: (f32, f32, f32), ) -> Result<(), String> { let (x, y, z) = position; - let mut entity = crate::helpers::live_entity(ctx, guid)?; + let mut entity = crate::actor::live_entity(ctx, guid)?; entity.map_id = map; entity.instance_id = instance; (entity.x, entity.y, entity.z) = (x, y, z); @@ -45,7 +45,7 @@ pub fn playerbots_transfer_orders_stage( second_mage: u64, leader: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let guids = [warrior, priest, mage, second_mage, leader]; if guids .iter() @@ -92,7 +92,7 @@ pub fn playerbots_transfer_orders_stage( .unwrap() .checked_add(1) .ok_or("fixture membership revision exhausted")?; - partitions.push(crate::GroupMemberPartition { + partitions.push(crate::tables::GroupMemberPartition { character_guid: second_mage, group_id: GROUP, membership_revision, @@ -100,7 +100,7 @@ pub fn playerbots_transfer_orders_stage( map_id: 0, instance_id: 0, locator_revision: 1, - state: crate::PartyPartitionState::Known, + state: crate::tables::PartyPartitionState::Known, }); partitions.sort_by_key(|p| p.membership_revision); let group = ctx @@ -119,7 +119,7 @@ pub fn playerbots_transfer_orders_stage( .checked_add(1) .ok_or("fixture roster revision exhausted")?; let raid_slots = vec![0; partitions.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, GROUP, leader, @@ -127,7 +127,7 @@ pub fn playerbots_transfer_orders_stage( group.loot_threshold, group.master_looter_guid, partitions.iter().map(|p| p.character_guid).collect(), - crate::SessionActor { + crate::package::fixture::SessionActor { guid: leader, ownership: None, }, @@ -164,21 +164,23 @@ pub fn playerbots_transfer_orders_stage( (SOURCE.0 + 100.0, SOURCE.1 + index as f32, SOURCE.2), )?; } - ctx.db.game_area_trigger().insert(crate::GameAreaTrigger { - id: 78, - map_id: 0, - x: SOURCE.0, - y: SOURCE.1, - z: SOURCE.2, - radius: 7.0, - box_length: 0.0, - box_width: 0.0, - box_height: 0.0, - box_yaw: 0.0, - }); + ctx.db + .game_area_trigger() + .insert(crate::tables::GameAreaTrigger { + id: 78, + map_id: 0, + x: SOURCE.0, + y: SOURCE.1, + z: SOURCE.2, + radius: 7.0, + box_length: 0.0, + box_width: 0.0, + box_height: 0.0, + box_yaw: 0.0, + }); ctx.db .game_areatrigger_teleport() - .insert(crate::AreatriggerTeleport { + .insert(crate::tables::AreatriggerTeleport { trigger_id: 78, target_map: 36, x: LANDING.0, @@ -188,7 +190,7 @@ pub fn playerbots_transfer_orders_stage( name: "Deadmines - Entering".to_string(), }); // Keep the first real Target leg pending while the caller records and changes partition facts. - crate::package_fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(60_000_000)) + crate::package::fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(60_000_000)) } /// Supply a later Known partition through the normal mirror operation. The Gateway process @@ -196,12 +198,12 @@ pub fn playerbots_transfer_orders_stage( #[reducer] pub fn playerbots_transfer_orders_remote( ctx: &ReducerContext, - request_actor: crate::SessionActor, + request_actor: crate::package::fixture::SessionActor, priest: u64, target: u64, mode: u8, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; if mode > 2 { return Err("unknown order Transfer fixture mode".to_string()); } @@ -223,14 +225,14 @@ pub fn playerbots_transfer_orders_remote( if partitions.len() != 5 || !partitions.iter().any(|p| p.character_guid == priest) { return Err("order Transfer fixture requires its five-member party".to_string()); } - crate::package_fixture::admit_to_instance(ctx, leader, 36, INSTANCE, GROUP, request_actor)?; + crate::package::fixture::admit_to_instance(ctx, leader, 36, INSTANCE, GROUP, request_actor)?; for guid in [Some(leader), (mode == 1).then_some(priest)] .into_iter() .flatten() { - crate::package_fixture::remove_live_character(ctx, guid)?; + crate::package::fixture::remove_live_character(ctx, guid)?; let mut character = - crate::helpers::character_by_guid(ctx, guid).ok_or("fixture Character missing")?; + crate::actor::character_by_guid(ctx, guid).ok_or("fixture Character missing")?; character.map_id = 36; character.pending_instance_id = INSTANCE; (character.x, character.y, character.z) = (LANDING.0, LANDING.1, LANDING.2); @@ -248,7 +250,7 @@ pub fn playerbots_transfer_orders_remote( .ok_or("fixture locator revision exhausted")?; } if mode == 2 { - let entity = crate::helpers::live_entity(ctx, target)?; + let entity = crate::actor::live_entity(ctx, target)?; if entity.entry != 5_098_001 || entity.is_player() { return Err("fixture refuses another designated target".to_string()); } @@ -269,7 +271,7 @@ pub fn playerbots_transfer_orders_remote( .ok_or("fixture roster revision missing")? .revision; let raid_slots = vec![0; partitions.len()]; - crate::group::sync_group_mirror( + crate::package::fixture::sync_group_mirror( ctx, GROUP, leader, diff --git a/playerbots/src/transfer_recovery_fixture.rs b/playerbots/src/transfer_recovery_fixture.rs index 18ed986..986db02 100644 --- a/playerbots/src/transfer_recovery_fixture.rs +++ b/playerbots/src/transfer_recovery_fixture.rs @@ -3,7 +3,7 @@ use super::actions::pkg_playerbots_action; use super::pkg_playerbots_bot; use super::runner::{CompanionTransferPurpose, TransferCheckpoint, TransferPurpose}; -use crate::game_creature_spline; +use crate::tables::game_creature_spline; use spacetimedb::{reducer, ReducerContext}; const STALLED_MICROS: i64 = 30_000_000; @@ -19,7 +19,7 @@ pub fn playerbots_transfer_recovery_fixture_stage_arrival( character_guid: u64, member_guid: u64, ) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; + crate::package::require_operator(ctx)?; let bot = ctx .db .pkg_playerbots_bot() @@ -33,7 +33,7 @@ pub fn playerbots_transfer_recovery_fixture_stage_arrival( ) }) .ok_or("Transfer Recovery fixture requires a Runner owner")?; - let me = crate::helpers::live_entity(ctx, character_guid)?; + let me = crate::actor::live_entity(ctx, character_guid)?; let mut state = super::runner::PlayerbotsRunner::load(ctx, character_guid) .ok_or("Transfer Recovery fixture Runner is absent")?; let objective = state diff --git a/playerbots/testsuite/tests/playerbots_companion.rs b/playerbots/testsuite/tests/playerbots_companion.rs index e0366ff..d42a99d 100644 --- a/playerbots/testsuite/tests/playerbots_companion.rs +++ b/playerbots/testsuite/tests/playerbots_companion.rs @@ -10,21 +10,6 @@ const HEAL: &str = "5090100"; const CHANNEL_HEAL: &str = "5090104"; const LEGACY_COMPARISON_PACKAGE: &str = "50e2cd10870d2cfb4a36018bbbd559f9d07c2062"; const LEGACY_GOALS_BLOB: &str = "e72a7f97552d01edfa101d45e1b24b39196ca405"; -const LEGACY_GOALS_EXPRESSIONS: [&str; 13] = [ - "pub(crate) const FOLLOW_RANGE_YD: f32 = 15.0;", - "const FOLLOW_STAND_OFF_YD: f32 = 8.0;", - "if should_flee(", - "if crate::spell::pending_cast(ctx, me.guid).is_some() {", - "let engaged = combat_target(ctx, &me, party.as_ref());", - "is_bot(ctx, party.leader_guid)", - "if quests_in_this_party {", - "if let Some(target) = engaged {", - "follow_leader(ctx, &me, party.leader_guid);", - "record_goal(ctx, bot.character_guid, goal::FOLLOW, now);", - "if flee_at_pct == 0 || max_health == 0 {", - "if distance_2d(me.x, me.y, leader.x, leader.y) <= FOLLOW_RANGE_YD {", - "FOLLOW_STAND_OFF_YD,", -]; fn record_inputs(node: &Standalone) { let core = support::core_root(); let package = core.join("packages/playerbots"); @@ -306,9 +291,7 @@ fn policy_gameplay_state( }) } -fn verify_legacy_goals_source() { - let core = support::core_root(); - let package = core.join("packages/playerbots"); +fn verify_legacy_comparison() { let collection_path = std::env::var_os("PLAYERBOTS_COMPANION_COMPARISON_COLLECTION") .expect("set PLAYERBOTS_COMPANION_COMPARISON_COLLECTION to the Package checkout"); let collection = std::path::Path::new(&collection_path); @@ -317,41 +300,6 @@ fn verify_legacy_goals_source() { git(collection, &["rev-parse", &committed_goals]), LEGACY_GOALS_BLOB ); - let legacy = std::process::Command::new("git") - .current_dir(collection) - .args(["cat-file", "blob", LEGACY_GOALS_BLOB]) - .output() - .unwrap(); - assert!(legacy.status.success()); - let legacy = String::from_utf8(legacy.stdout).unwrap(); - let source = std::fs::read_to_string(package.join("src/goals.rs")).unwrap(); - assert_eq!(source, without_inline_tests(&legacy)); - let mut remaining = source.as_str(); - for expression in LEGACY_GOALS_EXPRESSIONS { - let start = remaining - .find(expression) - .unwrap_or_else(|| panic!("Legacy comparison expression missing: {expression}")); - remaining = &remaining[start + expression.len()..]; - } -} - -/// The Legacy source as goals.rs holds it now: its inline test module moved to `goals/tests.rs`, -/// and the production code before it is byte-identical. -fn without_inline_tests(legacy: &str) -> String { - const INLINE_TESTS: &str = "#[cfg(test)]\nmod tests {\n"; - let starts: Vec<_> = legacy - .match_indices(INLINE_TESTS) - .map(|(at, _)| at) - .collect(); - let [start] = starts[..] else { - panic!("the Legacy goals.rs must hold exactly one inline test module"); - }; - assert_eq!( - legacy[start..].find("\n}\n"), - Some(legacy.len() - start - 3), - "the Legacy inline test module must end the file" - ); - format!("{}#[cfg(test)]\nmod tests;\n", &legacy[..start]) } fn record_policy_comparison(node: &Standalone, priest: &str, leader: &str, ally: &str) { @@ -415,7 +363,7 @@ fn record_policy_comparison(node: &Standalone, priest: &str, leader: &str, ally: ); assert!(flee_scripts.iter().all(|row| row["enabled"] == "false")); - verify_legacy_goals_source(); + verify_legacy_comparison(); let gameplay_before = policy_gameplay_state(node, priest, leader, ally); for field in ["pending_casts", "melee"] { @@ -510,7 +458,6 @@ fn record_policy_comparison(node: &Standalone, priest: &str, leader: &str, ally: "basis": "SOURCE-DERIVED", "package_commit": LEGACY_COMPARISON_PACKAGE, "goals_blob": LEGACY_GOALS_BLOB, - "source_expressions": LEGACY_GOALS_EXPRESSIONS, "decision": legacy_decision, }, "record_only": { @@ -824,6 +771,10 @@ fn playerbots_non_healer_companion_retains_self_recovery() { #[ignore = "requires SpacetimeDB, Wasm, and the playerbots Package"] fn playerbots_casting_position_retains_one_injured_ally_across_movement_legs() { let (node, bots) = fixture("playerbots-companion-target-retention"); + // Keep a real cooldown active after the five-second fixture cast finishes. + node.assert_sql(&format!( + "UPDATE game_spell SET gcd_ms = 10000 WHERE spell_id = {HEAL}" + )); let (priest, leader, ally) = (&bots[0], &bots[1], &bots[2]); node.assert_call( "playerbots_fixture_companion_move", diff --git a/playerbots/testsuite/tests/playerbots_quest_loops.rs b/playerbots/testsuite/tests/playerbots_quest_loops.rs index 9c1dbe8..1fcb5a2 100644 --- a/playerbots/testsuite/tests/playerbots_quest_loops.rs +++ b/playerbots/testsuite/tests/playerbots_quest_loops.rs @@ -464,9 +464,13 @@ fn record(node: &Standalone, suffix: &str) { .collect(); seeded_content_identity.sort(); seeded_content_identity.dedup(); - let named_loop = seeded_content_identity - .iter() - .any(|identity| identity == "playerbots-loopback-named-q7-ten-targets-v2"); + let named_loop = seeded_content_identity.iter().any(|identity| { + matches!( + identity.as_str(), + "playerbots-loopback-named-q7-ten-targets-v2" + | "playerbots-loopback-named-q7-distant-targets-v1" + ) + }); let smite_header = node.query_rows("SELECT * FROM game_spell WHERE spell_id = 585"); let smite_effect = node.query_rows("SELECT * FROM game_spell_effect WHERE spell_id = 585"); let nav_chunks = node @@ -572,6 +576,21 @@ fn record_declared_targets(node: &Standalone, suffix: &str, targets: &[u64]) { fn playerbots_autonomous_talk_kill_and_collect_loops_run_for_all_starter_classes() { for (class, role, label) in [(1, 0, "warrior"), (5, 1, "priest"), (8, 2, "mage")] { let (node, guid) = fixture(&format!("playerbots-quest-loop-{label}"), class, role, true); + // Ranged casters must also travel beyond the former 150-yard home leash. + let targets = node.query_rows("SELECT guid, x, y FROM game_world_entity WHERE entry = 6"); + assert_eq!(targets.len(), 10); + for target in targets { + let x = target["x"].parse::().unwrap() + 30.0; + node.assert_call( + "playerbots_fixture_companion_move", + &[&target["guid"], &x.to_string(), &target["y"]], + ); + } + node.assert_sql(&format!( + "UPDATE pkg_playerbots_quest_loop_fixture SET content_revision = 'playerbots-loopback-named-q7-distant-targets-v1' WHERE character_guid = {guid}" + )); + node.assert_call("playerbots_quest_fixture_refresh", &[]); + assert!(node .query_rows(&format!( "SELECT * FROM game_character_quest WHERE character_guid = {guid}" @@ -596,7 +615,7 @@ fn playerbots_autonomous_talk_kill_and_collect_loops_run_for_all_starter_classes let mut max_x = f32::NEG_INFINITY; let smite_updates = if class == 5 { - drive_until(&node, &guid, LOOP_TIMEOUT, |node| { + max_x = max_x.max(drive_until(&node, &guid, LOOP_TIMEOUT, |node| { query_one( node, &format!("SELECT x FROM game_world_entity WHERE guid = {guid}"), @@ -604,14 +623,14 @@ fn playerbots_autonomous_talk_kill_and_collect_loops_run_for_all_starter_classes .parse::() .unwrap() >= 1_250.0 - }); + })); node.capture_updates( &format!("SELECT * FROM pkg_playerbots_action WHERE character_guid = {guid} AND spell_id = 585"), 2, || { - max_x = drive_until(&node, &guid, LOOP_TIMEOUT, |node| { + max_x = max_x.max(drive_until(&node, &guid, LOOP_TIMEOUT, |node| { quest(node, &guid, 7).is_some_and(|quest| first_quest_count(&quest) > 0) - }); + })); }, ) } else { @@ -648,7 +667,8 @@ fn playerbots_autonomous_talk_kill_and_collect_loops_run_for_all_starter_classes })); assert!( max_x - before["x"].parse::().unwrap() > 150.0, - "{label} did not reach the work area beyond the former home leash" + "{label} did not reach the work area beyond the former home leash: start={}, max={max_x}", + before["x"] ); let kill = quest(&node, &guid, 7).unwrap(); assert_eq!(first_quest_count(&kill), 10, "{kill:?}"); @@ -1599,7 +1619,7 @@ fn playerbots_timed_quest_cast_approaches_before_a_target_moves_beyond_completio .unwrap(), ) .unwrap(); - let cast_updates = serde_json::to_string(&cast_updates).unwrap(); + let cast_updates_text = serde_json::to_string(&cast_updates).unwrap(); assert_eq!(spell["range_yd"], "35"); assert_eq!(spell["cast_time_ms"], "1500"); assert_eq!(retained["quest_entry"], "7"); @@ -1614,7 +1634,7 @@ fn playerbots_timed_quest_cast_approaches_before_a_target_moves_beyond_completio assert_eq!(started_pending.len(), 1, "{started_pending:?}"); assert_eq!(started_pending[0]["spell_id"], "133"); assert_eq!(started_pending[0]["target_guid"], CREATURE_6.to_string()); - assert!(out_of_range_observed, "{cast_updates}"); + assert!(out_of_range_observed, "{cast_updates_text}"); } assert!(started_pending.is_empty(), "{started_pending:?}"); assert!(started["chosen"].contains("castingPosition"), "{started:?}"); @@ -1636,14 +1656,27 @@ fn playerbots_timed_quest_cast_approaches_before_a_target_moves_beyond_completio "{target_movement:?}" ); assert!(spawn["x"].parse::().unwrap() > target["x"].parse::().unwrap()); - assert!(!cast_updates.contains("outOfRange"), "{cast_updates}"); - assert!(cast_updates.contains("castResolved"), "{cast_updates}"); - assert!(actions(&node, &guid).iter().any(|action| { - action["kind"].contains("cast") - && action["spell_id"] == "133" - && action["target_guid"] == CREATURE_6.to_string() - && action["outcome"].contains("castResolved") - })); + assert!( + !cast_updates_text.contains("outOfRange"), + "{cast_updates_text}" + ); + // A later cast can replace the bounded action row after this completion. + assert!( + cast_updates.iter().any(|update| { + update["pkg_playerbots_action"]["inserts"] + .as_array() + .is_some_and(|rows| { + rows.iter().any(|action| { + action["character_guid"].as_u64() == Some(guid.parse().unwrap()) + && action["kind"].get("cast").is_some() + && action["spell_id"] == 133 + && action["target_guid"].as_u64() == Some(CREATURE_6) + && action["outcome"].get("castResolved").is_some() + }) + }) + }), + "{cast_updates_text}" + ); assert!(first_quest_count(&quest(&node, &guid, 7).unwrap()) > 0); } diff --git a/playerbots/testsuite/tests/playerbots_rewrite.rs b/playerbots/testsuite/tests/playerbots_rewrite.rs index 6bb224f..13b9a6d 100644 --- a/playerbots/testsuite/tests/playerbots_rewrite.rs +++ b/playerbots/testsuite/tests/playerbots_rewrite.rs @@ -983,6 +983,14 @@ fn playerbots_core_cast_replacement_finishes_the_original_handle() { node.assert_call("playerbots_fixture_cast", &[bot, bot]); let original = node.query_rows("SELECT scheduled_id FROM game_pending_cast")[0]["scheduled_id"].clone(); + // Permit a replacement while the original five-second cast is still pending. + node.assert_sql(&format!( + "DELETE FROM game_spell_cooldown WHERE caster_guid = {bot}" + )); + assert_eq!( + node.query_rows("SELECT scheduled_id FROM game_pending_cast")[0]["scheduled_id"], + original + ); node.assert_call("debug_begin_cast", &[bot, "5090100", bot]); assert_ne!( node.query_rows("SELECT scheduled_id FROM game_pending_cast")[0]["scheduled_id"], diff --git a/playerbots/testsuite/tests/playerbots_roles.rs b/playerbots/testsuite/tests/playerbots_roles.rs index db92587..7b27a1c 100644 --- a/playerbots/testsuite/tests/playerbots_roles.rs +++ b/playerbots/testsuite/tests/playerbots_roles.rs @@ -1084,10 +1084,15 @@ fn playerbots_urgent_heal_releases_only_the_interrupted_buff_target() { .unwrap(); evidence(&fixture, "heal-out-of-range-staged"); assert!((mage_x - priest_x).abs() > renew_range); - pass(node, &fixture.priest); + // Fortitude starts its cooldown on admission; an unavailable heal cannot preempt it. + let healing_started = poll_until(POLL_TIMEOUT, || { + pass(node, &fixture.priest); + runner(node, &fixture.priest)["chosen"].contains("castingPosition") + }); evidence(&fixture, "buff-interrupted-by-heal"); let healing = runner(node, &fixture.priest); + assert!(healing_started, "{healing:?}"); assert!(healing["chosen"].contains("castingPosition"), "{healing:?}"); assert!(healing["chosen"].contains(&fixture.mage), "{healing:?}"); assert!( diff --git a/playerbots/testsuite/tests/playerbots_transfer_upgrade.rs b/playerbots/testsuite/tests/playerbots_transfer_upgrade.rs index 4a2b24c..a85725a 100644 --- a/playerbots/testsuite/tests/playerbots_transfer_upgrade.rs +++ b/playerbots/testsuite/tests/playerbots_transfer_upgrade.rs @@ -392,13 +392,22 @@ fn playerbots_transfer_upgrades_populated_predecessor_without_a_checkpoint() { assert_eq!(after[runner]["movement_due_micros"], i64::MAX.to_string()); assert_eq!(after[runner]["path_pending"], "false"); } + // The pinned predecessor predates the persisted watched-faction selection. + let mut expected_characters = before["character"].clone(); + for character in expected_characters.as_array_mut().unwrap() { + assert!(character + .as_object_mut() + .unwrap() + .insert("watched_faction_index".into(), serde_json::json!("-1")) + .is_none()); + } + assert_eq!(expected_characters, after["character"], "character"); for field in [ "orders", "retained_quest", "quests", "pending_cast", "provisioning", - "character", "bot_intents", "source_escrows", "arrivals",