Merge pull request #17 from Gexo91/main
Complete housing visibility integration and remove legacy build
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
name: Windows x64 build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: windows-build-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
windows-build:
|
||||
name: Windows x64 / servers and scripts
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 120
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
steps:
|
||||
- name: Checkout PR merge
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Discover Windows dependencies
|
||||
run: |
|
||||
$mysqlRoot = Get-ChildItem "$env:ProgramFiles/MySQL" -Directory |
|
||||
Where-Object { Test-Path (Join-Path $_.FullName 'lib/libmysql.lib') } |
|
||||
Sort-Object Name -Descending | Select-Object -First 1
|
||||
if (!$mysqlRoot) { throw 'MySQL development installation missing from windows-2022' }
|
||||
$opensslRoot = Join-Path $env:ProgramFiles 'OpenSSL'
|
||||
if (!(Test-Path "$opensslRoot/include/openssl/ssl.h")) { throw 'OpenSSL development headers missing' }
|
||||
"MYSQL_ROOT_DIR=$($mysqlRoot.FullName)" >> $env:GITHUB_ENV
|
||||
"OPENSSL_ROOT_DIR=$opensslRoot" >> $env:GITHUB_ENV
|
||||
& "$opensslRoot/bin/openssl.exe" version
|
||||
if ($LASTEXITCODE -ne 0) { throw 'OpenSSL validation failed' }
|
||||
& "$($mysqlRoot.FullName)/bin/mysql.exe" --version
|
||||
if ($LASTEXITCODE -ne 0) { throw 'MySQL validation failed' }
|
||||
|
||||
- name: Install Boost 1.89.0
|
||||
id: boost
|
||||
uses: MarkusJx/install-boost@f9d34f6fe427b8f2ae424d7bf2a3ebccb0dae5a3 # v2.6.0
|
||||
with:
|
||||
boost_version: 1.89.0
|
||||
platform_version: 2022
|
||||
toolset: msvc
|
||||
link: static
|
||||
arch: x86
|
||||
|
||||
- name: Initialize MSVC x64
|
||||
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
||||
with:
|
||||
arch: amd64
|
||||
|
||||
- name: Check housing migrations and data preservation
|
||||
run: ./contrib/ci/Test-HousingSql.ps1 -MySqlRoot $env:MYSQL_ROOT_DIR
|
||||
|
||||
- name: Configure servers and all static scripts
|
||||
env:
|
||||
BOOST_ROOT: ${{ steps.boost.outputs.BOOST_ROOT }}
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DCMAKE_NINJA_FORCE_RESPONSE_FILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSERVERS=ON -DSCRIPTS=static -DTOOLS=OFF -DBUILD_TESTING=OFF -DUSE_COREPCH=ON -DUSE_SCRIPTPCH=ON
|
||||
if ($LASTEXITCODE -ne 0) { throw 'CMake configuration failed' }
|
||||
|
||||
- name: Build Windows binaries
|
||||
run: |
|
||||
cmake --build build --parallel 2 -- -k 20
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Windows build failed' }
|
||||
|
||||
- name: Copy runtime dependencies and check binaries
|
||||
run: |
|
||||
$outputDir = Join-Path $env:GITHUB_WORKSPACE 'build/bin/RelWithDebInfo'
|
||||
Copy-Item "$env:MYSQL_ROOT_DIR/lib/libmysql.dll" $outputDir
|
||||
Get-ChildItem "$env:MYSQL_ROOT_DIR/bin" -File |
|
||||
Where-Object { $_.Name -match '^(libcrypto|libssl).*\.dll$' } |
|
||||
Copy-Item -Destination $outputDir
|
||||
Get-ChildItem "$env:OPENSSL_ROOT_DIR/bin" -File |
|
||||
Where-Object { $_.Name -match '^(libcrypto|libssl|legacy).*\.dll$' } |
|
||||
Copy-Item -Destination $outputDir
|
||||
Push-Location $outputDir
|
||||
try {
|
||||
& ./bnetserver.exe --version
|
||||
if ($LASTEXITCODE -ne 0) { throw 'bnetserver version check failed' }
|
||||
& ./worldserver.exe --version
|
||||
if ($LASTEXITCODE -ne 0) { throw 'worldserver version check failed' }
|
||||
} finally { Pop-Location }
|
||||
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: WowCommunityProject-Windows-x64-${{ github.sha }}
|
||||
path: build/bin/RelWithDebInfo/
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload configuration logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Windows-build-diagnostics-${{ github.sha }}
|
||||
path: |
|
||||
build/CMakeCache.txt
|
||||
build/CMakeFiles/CMakeConfigureLog.yaml
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,93 @@
|
||||
param([Parameter(Mandatory = $true)][string]$MySqlRoot)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = (Resolve-Path "$PSScriptRoot/../..").Path
|
||||
$mysql = Join-Path $MySqlRoot 'bin/mysql.exe'
|
||||
$mysqld = Join-Path $MySqlRoot 'bin/mysqld.exe'
|
||||
$mysqladmin = Join-Path $MySqlRoot 'bin/mysqladmin.exe'
|
||||
$taskDir = Join-Path ([IO.Path]::GetTempPath()) ("housing-sql-" + [guid]::NewGuid())
|
||||
$dataDir = Join-Path $taskDir 'data'
|
||||
New-Item -ItemType Directory -Path $taskDir | Out-Null
|
||||
$server = $null
|
||||
function Invoke-HousingSql([string]$Sql) {
|
||||
# Read SQL on stdin: MySQL 8.0 has no --abort-source-on-error option, and
|
||||
# SOURCE file errors are not a reliable process exit-code boundary.
|
||||
$Sql | & $mysql --protocol=TCP --host=127.0.0.1 --port=33307 --user=root --batch --default-character-set=utf8mb4
|
||||
if ($LASTEXITCODE -ne 0) { throw "Housing SQL check failed (mysql exit $LASTEXITCODE)" }
|
||||
}
|
||||
function Import-HousingSql([string]$Database, [string]$RelativePath) {
|
||||
$sourcePath = Join-Path $repoRoot $RelativePath
|
||||
Invoke-HousingSql ("USE $Database;" + [Environment]::NewLine + [IO.File]::ReadAllText($sourcePath))
|
||||
}
|
||||
function Test-HousingPreparedStatements([string]$Database, [string]$Implementation) {
|
||||
$source = [IO.File]::ReadAllText((Join-Path $repoRoot "src/server/database/Database/Implementation/${Implementation}Database.cpp"))
|
||||
$pattern = '(?s)PrepareStatement\((?<name>(?:CHAR|HOTFIX)_\w+),\s*(?<sql>(?:"(?:[^"\\]|\\.)*"\s*)+)\s*,\s*CONNECTION_\w+\)'
|
||||
$count = 0
|
||||
$checks = [Collections.Generic.List[string]]::new()
|
||||
$checks.Add("USE $Database;")
|
||||
foreach ($statement in [regex]::Matches($source, $pattern)) {
|
||||
$name = $statement.Groups['name'].Value
|
||||
if ($name -notmatch 'HOUSING|HOUSE|NEIGHBORHOOD|DECOR|DYE_COLOR|EXTERIOR_COMPONENT|ROOM_|INITIATIVE') { continue }
|
||||
$query = ([regex]::Matches($statement.Groups['sql'].Value, '"((?:[^"\\]|\\.)*)"') |
|
||||
ForEach-Object { [regex]::Unescape($_.Groups[1].Value) }) -join ''
|
||||
$escaped = $query.Replace('\', '\\').Replace("'", "''")
|
||||
$checks.Add("-- $name")
|
||||
$checks.Add("PREPARE housing_ci_stmt FROM '$escaped'; DEALLOCATE PREPARE housing_ci_stmt;")
|
||||
$count++
|
||||
}
|
||||
if ($count -lt 30) { throw "Expected housing prepared statements were not found in $Implementation ($count)" }
|
||||
Invoke-HousingSql ($checks -join [Environment]::NewLine)
|
||||
Write-Host "Validated $count $Implementation housing prepared statements against MySQL."
|
||||
}
|
||||
try {
|
||||
& $mysqld --no-defaults --initialize-insecure "--basedir=$MySqlRoot" "--datadir=$dataDir" --console
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not initialize isolated MySQL instance' }
|
||||
$server = Start-Process -PassThru -FilePath $mysqld -ArgumentList @(
|
||||
'--no-defaults', ('--basedir="{0}"' -f $MySqlRoot), ('--datadir="{0}"' -f $dataDir),
|
||||
'--port=33307', '--bind-address=127.0.0.1', '--mysqlx=OFF', '--console',
|
||||
'--innodb-flush-log-at-trx-commit=2', '--sync-binlog=0'
|
||||
) -RedirectStandardOutput (Join-Path $taskDir 'server.stdout.log') -RedirectStandardError (Join-Path $taskDir 'server.stderr.log')
|
||||
$ready = $false
|
||||
for ($attempt = 0; $attempt -lt 60; $attempt++) {
|
||||
& $mysqladmin --protocol=TCP --host=127.0.0.1 --port=33307 --user=root ping 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) { $ready = $true; break }
|
||||
if ($server.HasExited) { throw 'Isolated MySQL server exited before becoming ready' }
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
if (!$ready) { throw 'Isolated MySQL server did not become ready' }
|
||||
Invoke-HousingSql 'CREATE DATABASE housing_ci_characters; CREATE DATABASE housing_ci_world; CREATE DATABASE housing_ci_hotfixes; CREATE DATABASE housing_ci_auth;'
|
||||
Import-HousingSql 'housing_ci_characters' 'sql/base/characters_database.sql'
|
||||
Import-HousingSql 'housing_ci_characters' 'tests/housing/sql/legacy-characters.sql'
|
||||
Import-HousingSql 'housing_ci_world' 'sql/base/dev/world_database.sql'
|
||||
Import-HousingSql 'housing_ci_hotfixes' 'sql/base/dev/hotfixes_database.sql'
|
||||
Import-HousingSql 'housing_ci_characters' 'tests/housing/sql/fixtures.sql'
|
||||
foreach ($database in @('characters','hotfixes','world','auth')) {
|
||||
Import-HousingSql "housing_ci_$database" "sql/updates/$database/master/2026_10_06_00_${database}_roleplaycore_housing.sql"
|
||||
}
|
||||
Import-HousingSql 'housing_ci_characters' 'tests/housing/sql/new-data.sql'
|
||||
foreach ($database in @('characters','hotfixes','world','auth')) {
|
||||
Import-HousingSql "housing_ci_$database" "sql/updates/$database/master/2026_10_06_00_${database}_roleplaycore_housing.sql"
|
||||
}
|
||||
Import-HousingSql 'housing_ci_characters' 'tests/housing/sql/assertions.sql'
|
||||
Test-HousingPreparedStatements 'housing_ci_characters' 'Character'
|
||||
Test-HousingPreparedStatements 'housing_ci_hotfixes' 'Hotfix'
|
||||
|
||||
}
|
||||
finally {
|
||||
if ($server -and !$server.HasExited) {
|
||||
& $mysqladmin --protocol=TCP --host=127.0.0.1 --port=33307 --user=root shutdown 2>$null | Out-Null
|
||||
if (!$server.WaitForExit(60000)) {
|
||||
Stop-Process -Id $server.Id -Force
|
||||
if (!$server.WaitForExit(10000)) { throw 'Isolated MySQL process did not terminate' }
|
||||
}
|
||||
}
|
||||
if ($server) { $server.Dispose() }
|
||||
if (Test-Path (Join-Path $taskDir 'server.stderr.log')) { Get-Content (Join-Path $taskDir 'server.stderr.log') }
|
||||
# Antivirus/file-system handles can outlive a stopped process briefly on Windows.
|
||||
for ($attempt = 0; $attempt -lt 5; $attempt++) {
|
||||
try { Remove-Item -Recurse -Force $taskDir; break }
|
||||
catch {
|
||||
if ($attempt -eq 4) { Write-Warning "Temporary SQL directory could not be removed: $taskDir" }
|
||||
else { Start-Sleep -Seconds 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# RoleplayCore housing replacement
|
||||
|
||||
This port replaces the previous housing core, handlers, entities, packets and scripts in one integration PR. The donor is [RoleplayCore housing_test](https://github.com/KamiliaBlow/RoleplayCore/tree/b5295b63ed25c8c7b1e535d0091d03a0b10477bb), commit `b5295b63ed25c8c7b1e535d0091d03a0b10477bb`. The target baseline is WowCommunityProject `03314ed3c9daf86e4363843b1f2eaf7662de98e7`.
|
||||
|
||||
The imported implementation covers neighborhoods, house purchase and interiors, rooms and decor, blueprints, initiatives, charter founding and housing administrator commands. Its behavior follows the donor implementation; donor integration is not a guarantee of retail feature completeness. Unrelated target systems, including multiple garrisons, covenants, Mythic+, delves, archaeology and shop integration, remain part of the target core.
|
||||
|
||||
## Database cutover
|
||||
|
||||
Stop both servers. Take consistent backups of auth, characters, world and hotfixes, including updater metadata, before starting. MySQL DDL is not transactional: the archive tables are a recovery aid, not a substitute for backups. Test the cutover against copies of the deployment databases first.
|
||||
|
||||
Use the four `2026_10_06_00_*_roleplaycore_housing.sql` updates, in this order: auth, characters, hotfixes, world. The normal database updaters discover these files in their respective master directories. Run the updated binaries only after all four databases have completed the cutover. Do not mix old binaries with the new schema.
|
||||
|
||||
Characters: existing legacy tables are renamed to `rp_housing_legacy_<original_name>` before the new schema is installed. The old ownership GUIDs, room entities and decor records have a different model. They remain available in the archives; this port does not automatically convert them into new houses. Players obtain new houses through the new purchase flow.
|
||||
|
||||
Hotfixes: housing DB2 override tables are archived and recreated with the donor layouts. Their initial contents are empty; housing data comes from matching client DB2 files. Old custom overrides must be reviewed and converted before being restored. Existing unrelated map difficulty IDs in the reserved housing range cause the migration to stop rather than overwrite them.
|
||||
|
||||
World: the donor housing seed replaces content on maps 2735 and 2736 on its first application. Rows touched by deletes or updates are saved in `rp_housing_legacy_*` tables. Spawn and text ID allocations are persisted in `rp_housing_world_ids`, so reapplication uses the original allocations. Later content on housing maps outside the imported ID ranges survives reapplication. The donor's RoleplayCore-only spawn size, visibility and gameobject RequiredLevel columns are omitted to match the target schema. NULL values in required spawn fields use the target defaults. A signed SMALLINT animation kit column is widened to INT to accept retail ID 34313 without discarding existing values.
|
||||
|
||||
The migration journal `rp_housing_migration` makes archive stages resumable and prevents archiving the new schema on reapplication. Do not delete journal or allocation rows, reclaim imported ID ranges, or modify archive tables while the migration is running. Reapplication restores the imported world seed; it may overwrite edits to those imported rows.
|
||||
|
||||
Administrator commands use permissions 10000–10005 and inherit through Administrator Commands role 196. Existing permission IDs remain unchanged. An unexpected permission collision stops the auth migration.
|
||||
|
||||
Rollback requires stopping both servers, restoring all four pre-cutover backups and their updater metadata, and restoring the previous binaries together. Renaming a single character table back is insufficient because world, hotfixes and core formats also change.
|
||||
|
||||
## Client data and configuration
|
||||
|
||||
Use the supported client build 69933 with matching extracted DB2, maps, vmaps and mmaps, including neighborhood maps 2735/2736 and interior map 2783. Existing server files from a different client build do not validate housing. Review the `Housing.*` settings in the distributed worldserver configuration, particularly purchase, tutorials, house limits and charter requirements.
|
||||
|
||||
## Validation
|
||||
|
||||
The sole CI workflow runs on Windows x64. It initializes an isolated MySQL instance, applies the cutover to legacy fixtures, reapplies it after inserting new housing data, and checks archives, ownership, unlocks, map difficulties, stable spawn allocations, charter quest links and unrelated rows. It also prepares the housing character and hotfix queries against those schemas. It then builds both servers with all static scripts and checks their version commands.
|
||||
|
||||
CI cannot verify client behavior. Before deployment, exercise this checklist on a test realm with the matching client:
|
||||
|
||||
- Alliance and Horde: start the housing tutorial, reach the steward and purchase a plot; reject insufficient funds and unavailable plots without losing money.
|
||||
- Enter and leave the house through its door; reconnect and restart the server while inside and outside.
|
||||
- Add, move, rotate and remove rooms and decor; verify saved placement and storage quantities after reconnecting.
|
||||
- Use another character on the same Battle.net account; verify account collection and ownership limits.
|
||||
- Invite a visitor; verify permissions, editor restrictions and neighborhood changes without leaking another owner's objects.
|
||||
- Create and import a blueprint; verify a failed import leaves the previous house intact.
|
||||
- Found a charter neighborhood, gather signatures, finalize it and verify persistence.
|
||||
- Verify initiative progress, milestones and rewards persist and cannot be claimed twice.
|
||||
- Check existing garrisons, covenants, Mythic+, delves, archaeology, shop and non-housing maps for regressions.
|
||||
|
||||
Record actual results in the PR. A successful build alone does not mark this checklist as passed.
|
||||
@@ -0,0 +1,26 @@
|
||||
-- Housing permissions use IDs outside WowCommunityProject's existing command range.
|
||||
DROP PROCEDURE IF EXISTS `rp_housing_permissions`;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `rp_housing_permissions`()
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM `rbac_permissions` WHERE `id` BETWEEN 10000 AND 10005
|
||||
AND `name` <> CASE `id`
|
||||
WHEN 10000 THEN 'Command: housing'
|
||||
WHEN 10001 THEN 'Command: housing setlevel'
|
||||
WHEN 10002 THEN 'Command: housing delete'
|
||||
WHEN 10003 THEN 'Command: housing charter create'
|
||||
WHEN 10004 THEN 'Command: housing charter type'
|
||||
WHEN 10005 THEN 'Command: housing charter delete' END) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing RBAC IDs already allocated to other permissions';
|
||||
END IF;
|
||||
INSERT IGNORE INTO `rbac_permissions` (`id`,`name`) VALUES
|
||||
(10000,'Command: housing'), (10001,'Command: housing setlevel'),
|
||||
(10002,'Command: housing delete'), (10003,'Command: housing charter create'),
|
||||
(10004,'Command: housing charter type'), (10005,'Command: housing charter delete');
|
||||
-- Administrator Commands, inherited by the Administrator security role.
|
||||
INSERT IGNORE INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES
|
||||
(196,10000),(196,10001),(196,10002),(196,10003),(196,10004),(196,10005);
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL `rp_housing_permissions`();
|
||||
DROP PROCEDURE `rp_housing_permissions`;
|
||||
@@ -0,0 +1,386 @@
|
||||
-- RoleplayCore housing cutover. Stop both servers and back up all databases first.
|
||||
CREATE TABLE IF NOT EXISTS `rp_housing_migration` (
|
||||
`stage` varchar(32) NOT NULL, `completedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`stage`)
|
||||
) ENGINE=InnoDB;
|
||||
DROP PROCEDURE IF EXISTS `rp_housing_archive`;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `rp_housing_archive`()
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM `rp_housing_migration` WHERE `stage`='characters_archived') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='character_housing' AND column_name='guid')
|
||||
AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='character_housing' AND column_name='OwnerGuid') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Unmanaged new housing schema: verify migration history';
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='character_housing') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_character_housing') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: character_housing';
|
||||
END IF;
|
||||
RENAME TABLE `character_housing` TO `rp_housing_legacy_character_housing`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_neighborhood') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_neighborhood') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_neighborhood';
|
||||
END IF;
|
||||
RENAME TABLE `housing_neighborhood` TO `rp_housing_legacy_housing_neighborhood`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_plot_template') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_plot_template') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_plot_template';
|
||||
END IF;
|
||||
RENAME TABLE `housing_plot_template` TO `rp_housing_legacy_housing_plot_template`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_plot_reservation') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_plot_reservation') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_plot_reservation';
|
||||
END IF;
|
||||
RENAME TABLE `housing_plot_reservation` TO `rp_housing_legacy_housing_plot_reservation`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_plot_transaction') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_plot_transaction') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_plot_transaction';
|
||||
END IF;
|
||||
RENAME TABLE `housing_plot_transaction` TO `rp_housing_legacy_housing_plot_transaction`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='character_housing_map_context') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_character_housing_map_context') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: character_housing_map_context';
|
||||
END IF;
|
||||
RENAME TABLE `character_housing_map_context` TO `rp_housing_legacy_character_housing_map_context`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_map_instance') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_map_instance') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_map_instance';
|
||||
END IF;
|
||||
RENAME TABLE `housing_map_instance` TO `rp_housing_legacy_housing_map_instance`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='character_housing_neighborhood_invite') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_character_housing_neighborhood_invite') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: character_housing_neighborhood_invite';
|
||||
END IF;
|
||||
RENAME TABLE `character_housing_neighborhood_invite` TO `rp_housing_legacy_character_housing_neighborhood_invite`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='character_housing_neighborhood_role') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_character_housing_neighborhood_role') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: character_housing_neighborhood_role';
|
||||
END IF;
|
||||
RENAME TABLE `character_housing_neighborhood_role` TO `rp_housing_legacy_character_housing_neighborhood_role`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='character_housing_finder_ignore') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_character_housing_finder_ignore') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: character_housing_finder_ignore';
|
||||
END IF;
|
||||
RENAME TABLE `character_housing_finder_ignore` TO `rp_housing_legacy_character_housing_finder_ignore`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='character_housing_return_location') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_character_housing_return_location') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: character_housing_return_location';
|
||||
END IF;
|
||||
RENAME TABLE `character_housing_return_location` TO `rp_housing_legacy_character_housing_return_location`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='character_housing_permission') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_character_housing_permission') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: character_housing_permission';
|
||||
END IF;
|
||||
RENAME TABLE `character_housing_permission` TO `rp_housing_legacy_character_housing_permission`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_room_entity') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_room_entity') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_room_entity';
|
||||
END IF;
|
||||
RENAME TABLE `housing_room_entity` TO `rp_housing_legacy_housing_room_entity`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_room_component_entity') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_room_component_entity') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_room_component_entity';
|
||||
END IF;
|
||||
RENAME TABLE `housing_room_component_entity` TO `rp_housing_legacy_housing_room_component_entity`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_room_connection') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_room_connection') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_room_connection';
|
||||
END IF;
|
||||
RENAME TABLE `housing_room_connection` TO `rp_housing_legacy_housing_room_connection`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_fixture_entity') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_fixture_entity') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_fixture_entity';
|
||||
END IF;
|
||||
RENAME TABLE `housing_fixture_entity` TO `rp_housing_legacy_housing_fixture_entity`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_placed_decor') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_placed_decor') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_placed_decor';
|
||||
END IF;
|
||||
RENAME TABLE `housing_placed_decor` TO `rp_housing_legacy_housing_placed_decor`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_decor_instance') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_decor_instance') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_decor_instance';
|
||||
END IF;
|
||||
RENAME TABLE `housing_decor_instance` TO `rp_housing_legacy_housing_decor_instance`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='account_housing_decor') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_account_housing_decor') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: account_housing_decor';
|
||||
END IF;
|
||||
RENAME TABLE `account_housing_decor` TO `rp_housing_legacy_account_housing_decor`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_entity_quarantine') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_entity_quarantine') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_entity_quarantine';
|
||||
END IF;
|
||||
RENAME TABLE `housing_entity_quarantine` TO `rp_housing_legacy_housing_entity_quarantine`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_blueprint') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_blueprint') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_blueprint';
|
||||
END IF;
|
||||
RENAME TABLE `housing_blueprint` TO `rp_housing_legacy_housing_blueprint`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_blueprint_item') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_blueprint_item') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_blueprint_item';
|
||||
END IF;
|
||||
RENAME TABLE `housing_blueprint_item` TO `rp_housing_legacy_housing_blueprint_item`;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='housing_deferred_decor_redemption') THEN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='rp_housing_legacy_housing_deferred_decor_redemption') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Housing archive collision: housing_deferred_decor_redemption';
|
||||
END IF;
|
||||
RENAME TABLE `housing_deferred_decor_redemption` TO `rp_housing_legacy_housing_deferred_decor_redemption`;
|
||||
END IF;
|
||||
INSERT INTO `rp_housing_migration` (`stage`) VALUES ('characters_archived');
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL `rp_housing_archive`();
|
||||
DROP PROCEDURE `rp_housing_archive`;
|
||||
-- Housing: player houses, rooms, decor, catalog, blueprints and neighborhoods
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `account_housing_blueprint` (
|
||||
`id` bigint unsigned NOT NULL,
|
||||
`uuid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`bnetAccountId` int unsigned NOT NULL,
|
||||
`exporterGuid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Character that exported it',
|
||||
`name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`type` tinyint unsigned NOT NULL COMMENT 'HousingBlueprintType: 1 House, 2 Room, 3 Interior, 4 Exterior',
|
||||
`flags` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'HousingBlueprintFlag: 1 AutomaticBackup',
|
||||
`createTime` bigint NOT NULL DEFAULT '0',
|
||||
`content` mediumblob NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_uuid` (`uuid`),
|
||||
KEY `idx_bnetAccountId` (`bnetAccountId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `account_housing_room` (
|
||||
`bnetAccountId` int unsigned NOT NULL,
|
||||
`roomId` int unsigned NOT NULL COMMENT 'HouseRoom.db2 ID unlocked for the account (level award rooms)',
|
||||
PRIMARY KEY (`bnetAccountId`,`roomId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `account_housing_house_type` (
|
||||
`bnetAccountId` int unsigned NOT NULL,
|
||||
`houseExteriorWmoDataId` int unsigned NOT NULL COMMENT 'HouseExteriorWmoData.db2 ID unlocked for the account (item facades)',
|
||||
PRIMARY KEY (`bnetAccountId`,`houseExteriorWmoDataId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `character_housing` (
|
||||
`guid` bigint unsigned NOT NULL COMMENT 'Player GUID',
|
||||
`houseId` int unsigned NOT NULL DEFAULT '0' COMMENT 'House DB2 entry ID',
|
||||
`neighborhoodGuid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'FK to neighborhoods.guid',
|
||||
`plotIndex` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Plot within neighborhood (0..MAX_NEIGHBORHOOD_PLOTS-1)',
|
||||
`houseLevel` int unsigned NOT NULL DEFAULT '1' COMMENT 'Current upgrade level',
|
||||
`favor` int unsigned NOT NULL DEFAULT '0' COMMENT 'Accumulated favor currency',
|
||||
`settingsFlags` int unsigned NOT NULL DEFAULT '0' COMMENT 'Bitmask of HouseSettingsFlags',
|
||||
`exteriorLocked` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Whether exterior editing is locked (1=locked, 0=unlocked)',
|
||||
`houseSize` tinyint unsigned NOT NULL DEFAULT '2' COMMENT 'HousingFixtureSize: 1=Any, 2=Small, 3=Medium, 4=Large',
|
||||
`houseType` int unsigned NOT NULL DEFAULT '0' COMMENT 'HouseExteriorWmoData DB2 entry ID (architectural style)',
|
||||
`createTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp of house creation',
|
||||
`posX` float NOT NULL DEFAULT '0' COMMENT 'House X position on plot',
|
||||
`posY` float NOT NULL DEFAULT '0' COMMENT 'House Y position on plot',
|
||||
`posZ` float NOT NULL DEFAULT '0' COMMENT 'House Z position on plot',
|
||||
`facing` float NOT NULL DEFAULT '0' COMMENT 'House facing angle on plot',
|
||||
`houseName` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Player-set house display name',
|
||||
`houseDescription` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Player-set house description',
|
||||
PRIMARY KEY (`guid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `character_housing_catalog` (
|
||||
`ownerGuid` bigint unsigned NOT NULL COMMENT 'Player GUID (account-wide tracking)',
|
||||
`houseDecorId` int unsigned NOT NULL COMMENT 'HouseDecor DB2 entry ID',
|
||||
`quantity` int unsigned NOT NULL DEFAULT '1' COMMENT 'Number of this decor owned/available',
|
||||
`acquiredTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when first acquired',
|
||||
`sourceType` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'DecorSourceType: 0=Standard, 3=Deferred, 5=Spell, 6=Item',
|
||||
`sourceValue` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Source context (spell ID, item GUID, etc.)',
|
||||
PRIMARY KEY (`ownerGuid`,`houseDecorId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `character_housing_decor` (
|
||||
`ownerGuid` bigint unsigned NOT NULL COMMENT 'FK to character_housing.guid',
|
||||
`id` bigint unsigned NOT NULL COMMENT 'Decor instance ID (unique per owner)',
|
||||
`houseDecorId` int unsigned NOT NULL COMMENT 'HouseDecor DB2 entry ID',
|
||||
`posX` float NOT NULL DEFAULT '0' COMMENT 'X position in room/house coordinates',
|
||||
`posY` float NOT NULL DEFAULT '0' COMMENT 'Y position in room/house coordinates',
|
||||
`posZ` float NOT NULL DEFAULT '0' COMMENT 'Z position in room/house coordinates',
|
||||
`rotX` float NOT NULL DEFAULT '0' COMMENT 'Quaternion rotation X component',
|
||||
`rotY` float NOT NULL DEFAULT '0' COMMENT 'Quaternion rotation Y component',
|
||||
`rotZ` float NOT NULL DEFAULT '0' COMMENT 'Quaternion rotation Z component',
|
||||
`rotW` float NOT NULL DEFAULT '1' COMMENT 'Quaternion rotation W component',
|
||||
`scale` float NOT NULL DEFAULT '1',
|
||||
`dyeSlot0` int unsigned NOT NULL DEFAULT '0' COMMENT 'Dye color ID for slot 0',
|
||||
`dyeSlot1` int unsigned NOT NULL DEFAULT '0' COMMENT 'Dye color ID for slot 1',
|
||||
`dyeSlot2` int unsigned NOT NULL DEFAULT '0' COMMENT 'Dye color ID for slot 2',
|
||||
`roomGuid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'FK to character_housing_rooms.id (0 = outdoor/unassigned)',
|
||||
`locked` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Whether the decor item is locked in place (1=locked, 0=unlocked)',
|
||||
`placementTime` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when decor was placed (for refund window)',
|
||||
`sourceType` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'DecorSourceType: 0=Standard, 3=Deferred, 5=Spell, 6=Item',
|
||||
`sourceValue` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Source context (spell ID, item GUID, etc.)',
|
||||
`petGuid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Battle pet counter bound to this decor slot (0 = none), HighGuid::BattlePet',
|
||||
`petFlag` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Client-sent flag accompanying the pet binding (CMSG_HOUSING_DECOR_SET_PET)',
|
||||
`parentDecorGuid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Counter of the decor this item is stacked on (0 = none)',
|
||||
PRIMARY KEY (`ownerGuid`,`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `character_housing_fixtures` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique fixture assignment ID',
|
||||
`ownerGuid` bigint unsigned NOT NULL COMMENT 'FK to character_housing.guid',
|
||||
`fixturePointId` int unsigned NOT NULL COMMENT 'Predefined fixture point identifier',
|
||||
`fixtureOptionId` int unsigned NOT NULL DEFAULT '0' COMMENT 'Selected fixture option (0 = default)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_owner` (`ownerGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `character_housing_ignored_neighborhood` (
|
||||
`ownerGuid` bigint unsigned NOT NULL COMMENT 'Player character GUID counter',
|
||||
`neighborhoodGuid` bigint unsigned NOT NULL COMMENT 'Ignored neighborhood GUID counter',
|
||||
PRIMARY KEY (`ownerGuid`,`neighborhoodGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `character_housing_rooms` (
|
||||
`ownerGuid` bigint unsigned NOT NULL COMMENT 'FK to character_housing.guid',
|
||||
`id` bigint unsigned NOT NULL COMMENT 'Room instance ID (unique per owner)',
|
||||
`houseRoomId` int unsigned NOT NULL COMMENT 'HouseRoom DB2 entry ID',
|
||||
`slotIndex` int unsigned NOT NULL DEFAULT '0' COMMENT 'Room slot within the house layout',
|
||||
`gridX` int NOT NULL DEFAULT '0',
|
||||
`gridY` int NOT NULL DEFAULT '0',
|
||||
`floorIndex` int NOT NULL DEFAULT '0',
|
||||
`orientation` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Room rotation orientation value',
|
||||
`mirrored` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Boolean: 1 = room layout is mirrored',
|
||||
`themeId` int unsigned NOT NULL DEFAULT '0' COMMENT 'Visual theme applied to the room',
|
||||
`wallTextureId` int unsigned NOT NULL DEFAULT '0' COMMENT 'RoomComponentTexture ID for walls',
|
||||
`floorTextureId` int unsigned NOT NULL DEFAULT '0' COMMENT 'RoomComponentTexture ID for floors',
|
||||
`ceilingTextureId` int unsigned NOT NULL DEFAULT '0' COMMENT 'RoomComponentTexture ID for ceilings',
|
||||
`colorOverride` int NOT NULL DEFAULT '-1' COMMENT 'Color override for materials (-1 = default)',
|
||||
`doorTypeId` int unsigned NOT NULL DEFAULT '0' COMMENT 'Door type for the room',
|
||||
`doorSlot` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Door slot index within the room',
|
||||
`ceilingTypeId` int unsigned NOT NULL DEFAULT '0' COMMENT 'Ceiling type for the room',
|
||||
`ceilingSlot` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Ceiling slot index within the room',
|
||||
`wallThemeId` int unsigned NOT NULL DEFAULT '0',
|
||||
`floorThemeId` int unsigned NOT NULL DEFAULT '0',
|
||||
`ceilingThemeId` int unsigned NOT NULL DEFAULT '0',
|
||||
`doorTypes` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`componentStyles` varchar(1024) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`ownerGuid`,`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_charter_signatures` (
|
||||
`charterId` bigint unsigned NOT NULL COMMENT 'FK to neighborhood_charters.id',
|
||||
`signerGuid` bigint unsigned NOT NULL COMMENT 'Player GUID of the signer',
|
||||
`signTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when the signature was made',
|
||||
PRIMARY KEY (`charterId`,`signerGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_charters` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique charter ID',
|
||||
`creatorGuid` bigint unsigned NOT NULL COMMENT 'Player GUID of the charter creator',
|
||||
`name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Proposed neighborhood name (max HOUSING_MAX_NAME_LENGTH)',
|
||||
`neighborhoodMapId` int unsigned NOT NULL COMMENT 'NeighborhoodMap DB2 entry ID for the target map',
|
||||
`factionFlags` int unsigned NOT NULL DEFAULT '0' COMMENT 'Faction restriction flags for the neighborhood',
|
||||
`isGuild` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Boolean: 1 = guild-associated neighborhood',
|
||||
`createTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp of charter creation',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_creator` (`creatorGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_initiative_contributions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`initiativeDbId` bigint unsigned NOT NULL COMMENT 'FK to neighborhood_initiatives.id',
|
||||
`playerGuid` bigint unsigned NOT NULL COMMENT 'Player character GUID',
|
||||
`taskId` int unsigned NOT NULL COMMENT 'InitiativeTask DB2 entry ID',
|
||||
`amount` int unsigned NOT NULL DEFAULT '0' COMMENT 'Cumulative contribution to this task',
|
||||
`lastUpdated` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp of last contribution',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_initiative_player_task` (`initiativeDbId`,`playerGuid`,`taskId`),
|
||||
KEY `idx_player` (`playerGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_initiative_milestones` (
|
||||
`initiativeDbId` bigint unsigned NOT NULL COMMENT 'FK to neighborhood_initiatives.id',
|
||||
`milestoneIndex` int unsigned NOT NULL COMMENT 'Milestone index (0, 1, 2)',
|
||||
`reached` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '1 = milestone has been reached',
|
||||
`reachedTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when milestone was reached',
|
||||
PRIMARY KEY (`initiativeDbId`,`milestoneIndex`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_initiative_reward_claims` (
|
||||
`initiativeDbId` bigint unsigned NOT NULL COMMENT 'FK to neighborhood_initiatives.id',
|
||||
`milestoneIndex` int unsigned NOT NULL COMMENT 'Milestone index (0, 1, 2)',
|
||||
`playerGuid` bigint unsigned NOT NULL COMMENT 'Player character GUID',
|
||||
`claimTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when reward was claimed',
|
||||
PRIMARY KEY (`initiativeDbId`,`milestoneIndex`,`playerGuid`),
|
||||
KEY `idx_player` (`playerGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_initiative_task_progress` (
|
||||
`initiativeDbId` bigint unsigned NOT NULL COMMENT 'FK to neighborhood_initiatives.id',
|
||||
`taskId` int unsigned NOT NULL COMMENT 'InitiativeTask DB2 entry ID',
|
||||
`progress` int unsigned NOT NULL DEFAULT '0' COMMENT 'Current progress count towards TargetCount',
|
||||
`status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '0=NOT_STARTED, 1=IN_PROGRESS, 2=COMPLETE',
|
||||
PRIMARY KEY (`initiativeDbId`,`taskId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_initiatives` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique initiative instance ID',
|
||||
`neighborhoodGuid` bigint unsigned NOT NULL COMMENT 'FK to neighborhoods.guid',
|
||||
`initiativeId` int unsigned NOT NULL COMMENT 'NeighborhoodInitiative DB2 entry ID',
|
||||
`startTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when the initiative began',
|
||||
`progress` float NOT NULL DEFAULT '0' COMMENT 'Completion progress (0.0 to 1.0)',
|
||||
`completed` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Boolean: 1 = initiative completed',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_neighborhood` (`neighborhoodGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_invites` (
|
||||
`neighborhoodGuid` bigint unsigned NOT NULL COMMENT 'FK to neighborhoods.guid',
|
||||
`inviteeGuid` bigint unsigned NOT NULL COMMENT 'Player GUID of the invited player',
|
||||
`inviterGuid` bigint unsigned NOT NULL COMMENT 'Player GUID of the player who sent the invite',
|
||||
`inviteTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when the invite was sent',
|
||||
PRIMARY KEY (`neighborhoodGuid`,`inviteeGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhood_members` (
|
||||
`neighborhoodGuid` bigint unsigned NOT NULL COMMENT 'FK to neighborhoods.guid',
|
||||
`playerGuid` bigint unsigned NOT NULL COMMENT 'Player GUID of the member',
|
||||
`role` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'NeighborhoodMemberRole: 0=Resident, 1=Manager, 2=Owner',
|
||||
`joinTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp when the player joined',
|
||||
`plotIndex` tinyint unsigned NOT NULL DEFAULT '255' COMMENT 'Assigned plot index (255 = INVALID_PLOT_INDEX, no plot)',
|
||||
PRIMARY KEY (`neighborhoodGuid`,`playerGuid`),
|
||||
KEY `idx_player` (`playerGuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `neighborhoods` (
|
||||
`guid` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique neighborhood instance ID',
|
||||
`name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Neighborhood display name (max HOUSING_MAX_NAME_LENGTH)',
|
||||
`neighborhoodMapId` int unsigned NOT NULL COMMENT 'NeighborhoodMap DB2 entry ID',
|
||||
`ownerGuid` bigint unsigned NOT NULL COMMENT 'Player GUID of the neighborhood founder/owner',
|
||||
`factionRestriction` int NOT NULL DEFAULT '0' COMMENT 'NeighborhoodFactionRestriction: 0=None, 1=Horde, 2=Alliance',
|
||||
`isPublic` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Boolean: 1 = publicly listed and joinable',
|
||||
`createTime` int unsigned NOT NULL DEFAULT '0' COMMENT 'Unix timestamp of neighborhood creation',
|
||||
`guildId` int unsigned NOT NULL DEFAULT '0' COMMENT 'M8: owning guild id for guild neighborhoods (0 = not guild-linked)',
|
||||
PRIMARY KEY (`guid`),
|
||||
KEY `idx_owner` (`ownerGuid`),
|
||||
KEY `idx_map` (`neighborhoodMapId`),
|
||||
KEY `idx_guild` (`guildId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
-- Phase 1: expose the existing housing quest and native cornerstone purchase UI.
|
||||
-- Requires the deployment's populated world database; does not fabricate quest
|
||||
-- templates, objectives, NPC spawns or client-build-dependent display IDs.
|
||||
|
||||
UPDATE creature_template SET npcflag = npcflag | 3, ScriptName = 'npc_housing_steward'
|
||||
WHERE entry IN (233063, 233708, 256078);
|
||||
|
||||
-- 91863 must be reachable directly through the explicit tutorial action.
|
||||
-- Preserve all unrelated addon fields (mail, restrictions, script, rewards).
|
||||
INSERT INTO quest_template_addon (ID, PrevQuestID)
|
||||
SELECT ID, 0 FROM quest_template WHERE ID = 91863
|
||||
ON DUPLICATE KEY UPDATE PrevQuestID = VALUES(PrevQuestID);
|
||||
|
||||
INSERT IGNORE INTO creature_queststarter (id, quest)
|
||||
SELECT c.entry, q.ID FROM creature_template c JOIN quest_template q ON q.ID = 91863
|
||||
WHERE c.entry IN (233063, 233708);
|
||||
INSERT IGNORE INTO creature_questender (id, quest)
|
||||
SELECT c.entry, q.ID FROM creature_template c JOIN quest_template q ON q.ID = 91863
|
||||
WHERE c.entry IN (233063, 233708);
|
||||
|
||||
-- The existing generic template is the visual fallback for DB2-specific entries.
|
||||
UPDATE gameobject_template SET Data0 = 4, Data7 = 70
|
||||
WHERE entry = 457142 AND type = 43;
|
||||
|
||||
-- Keep objective amounts/flags and exploration/location requirements untouched.
|
||||
File diff suppressed because one or more lines are too long
@@ -1090,6 +1090,117 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_SEL_SHOP_BOOST_ACCOUNT, "SELECT b.guid, b.trial FROM character_shop_boost b INNER JOIN characters c ON c.guid = b.guid WHERE c.account = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_VAS_ACCOUNT_CHARACTER_LIST, "SELECT guid, name FROM characters WHERE account = ? AND deleteInfos_Name IS NULL ORDER BY slot ASC", CONNECTION_SYNCH);
|
||||
// WowCommunity end
|
||||
// Housing
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING, "SELECT houseId, neighborhoodGuid, plotIndex, houseLevel, favor, settingsFlags, exteriorLocked, houseSize, houseType, createTime, posX, posY, posZ, facing, houseName, houseDescription FROM character_housing WHERE guid = ?", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING, "INSERT INTO character_housing (guid, houseId, neighborhoodGuid, plotIndex, houseLevel, favor, settingsFlags, exteriorLocked, houseSize, houseType, createTime, posX, posY, posZ, facing, houseName, houseDescription) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UNIX_TIMESTAMP(), ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING, "DELETE FROM character_housing WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING_DECOR, "SELECT id, houseDecorId, posX, posY, posZ, rotX, rotY, rotZ, rotW, scale, dyeSlot0, dyeSlot1, dyeSlot2, roomGuid, locked, placementTime, sourceType, sourceValue, petGuid, petFlag, parentDecorGuid FROM character_housing_decor WHERE ownerGuid = ?", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING_DECOR, "INSERT INTO character_housing_decor (ownerGuid, id, houseDecorId, posX, posY, posZ, rotX, rotY, rotZ, rotW, scale, dyeSlot0, dyeSlot1, dyeSlot2, roomGuid, locked, placementTime, sourceType, sourceValue, petGuid, petFlag, parentDecorGuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING_DECOR, "DELETE FROM character_housing_decor WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING_ROOMS, "SELECT id, houseRoomId, slotIndex, gridX, gridY, floorIndex, orientation, mirrored, themeId, wallTextureId, floorTextureId, ceilingTextureId, colorOverride, doorTypeId, doorSlot, ceilingTypeId, ceilingSlot, wallThemeId, floorThemeId, ceilingThemeId, doorTypes, componentStyles FROM character_housing_rooms WHERE ownerGuid = ?", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING_ROOMS, "INSERT INTO character_housing_rooms (ownerGuid, id, houseRoomId, slotIndex, gridX, gridY, floorIndex, orientation, mirrored, themeId, wallTextureId, floorTextureId, ceilingTextureId, colorOverride, doorTypeId, doorSlot, ceilingTypeId, ceilingSlot, wallThemeId, floorThemeId, ceilingThemeId, doorTypes, componentStyles) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING_ROOMS, "DELETE FROM character_housing_rooms WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING_FIXTURES, "SELECT fixturePointId, fixtureOptionId FROM character_housing_fixtures WHERE ownerGuid = ?", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING_FIXTURES, "INSERT INTO character_housing_fixtures (ownerGuid, fixturePointId, fixtureOptionId) VALUES (?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING_FIXTURES, "DELETE FROM character_housing_fixtures WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING_CATALOG, "SELECT houseDecorId, quantity, sourceType, sourceValue FROM character_housing_catalog WHERE ownerGuid = ?", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING_CATALOG, "INSERT INTO character_housing_catalog (ownerGuid, houseDecorId, quantity, sourceType, sourceValue) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING_CATALOG, "DELETE FROM character_housing_catalog WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_SETTINGS, "UPDATE character_housing SET settingsFlags = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_EXTERIOR_LOCKED, "UPDATE character_housing SET exteriorLocked = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_HOUSE_SIZE, "UPDATE character_housing SET houseSize = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_HOUSE_TYPE, "UPDATE character_housing SET houseType = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_POSITION, "UPDATE character_housing SET posX = ?, posY = ?, posZ = ?, facing = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_DECOR_POSITION, "UPDATE character_housing_decor SET posX = ?, posY = ?, posZ = ?, rotX = ?, rotY = ?, rotZ = ?, rotW = ?, scale = ? WHERE ownerGuid = ? AND id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_DECOR_DYES, "UPDATE character_housing_decor SET dyeSlot0 = ?, dyeSlot1 = ?, dyeSlot2 = ? WHERE ownerGuid = ? AND id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_DECOR_LOCKED, "UPDATE character_housing_decor SET locked = ? WHERE ownerGuid = ? AND id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_DECOR_PET, "UPDATE character_housing_decor SET petGuid = ?, petFlag = ? WHERE ownerGuid = ? AND id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_DECOR_PARENT, "UPDATE character_housing_decor SET parentDecorGuid = ? WHERE ownerGuid = ? AND id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING_IGNORED_NEIGHBORHOOD, "SELECT neighborhoodGuid FROM character_housing_ignored_neighborhood WHERE ownerGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING_IGNORED_NEIGHBORHOOD, "INSERT IGNORE INTO character_housing_ignored_neighborhood (ownerGuid, neighborhoodGuid) VALUES (?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING_IGNORED_NEIGHBORHOOD, "DELETE FROM character_housing_ignored_neighborhood WHERE ownerGuid = ? AND neighborhoodGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_ACCOUNT_HOUSING_BLUEPRINTS, "SELECT id, uuid, bnetAccountId, exporterGuid, name, type, flags, createTime, content FROM account_housing_blueprint", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_ACCOUNT_HOUSING_BLUEPRINT, "INSERT INTO account_housing_blueprint (id, uuid, bnetAccountId, exporterGuid, name, type, flags, createTime, content) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_ACCOUNT_HOUSING_BLUEPRINT_NAME, "UPDATE account_housing_blueprint SET name = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_ACCOUNT_HOUSING_BLUEPRINT, "DELETE FROM account_housing_blueprint WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_ACCOUNT_HOUSING_ROOMS, "SELECT roomId FROM account_housing_room WHERE bnetAccountId = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_ACCOUNT_HOUSING_OWNERS, "SELECT ch.guid FROM character_housing ch JOIN characters c ON c.guid = ch.guid WHERE c.account = ? AND ch.guid <> ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_ACCOUNT_HOUSING_ROOM, "INSERT IGNORE INTO account_housing_room (bnetAccountId, roomId) VALUES (?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_ACCOUNT_HOUSING_HOUSE_TYPES, "SELECT houseExteriorWmoDataId FROM account_housing_house_type WHERE bnetAccountId = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_ACCOUNT_HOUSING_HOUSE_TYPE, "INSERT IGNORE INTO account_housing_house_type (bnetAccountId, houseExteriorWmoDataId) VALUES (?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_OWNER, "UPDATE character_housing SET guid = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_DECOR_OWNER, "UPDATE character_housing_decor SET ownerGuid = ? WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_ROOMS_OWNER, "UPDATE character_housing_rooms SET ownerGuid = ? WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_FIXTURES_OWNER, "UPDATE character_housing_fixtures SET ownerGuid = ? WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_CATALOG_OWNER, "UPDATE character_housing_catalog SET ownerGuid = ? WHERE ownerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_MEMBER_PLAYER, "UPDATE neighborhood_members SET playerGuid = ? WHERE neighborhoodGuid = ? AND playerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_ROOM, "UPDATE character_housing_rooms SET slotIndex = ?, gridX = ?, gridY = ?, floorIndex = ?, orientation = ?, mirrored = ?, themeId = ?, wallTextureId = ?, floorTextureId = ?, ceilingTextureId = ?, colorOverride = ?, doorTypeId = ?, doorSlot = ?, ceilingTypeId = ?, ceilingSlot = ?, wallThemeId = ?, floorThemeId = ?, ceilingThemeId = ?, doorTypes = ?, componentStyles = ? WHERE ownerGuid = ? AND id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_FIXTURE, "UPDATE character_housing_fixtures SET fixtureOptionId = ? WHERE ownerGuid = ? AND fixturePointId = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING_FIXTURE_SINGLE, "DELETE FROM character_housing_fixtures WHERE ownerGuid = ? AND fixturePointId = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_REP_CHARACTER_HOUSING_CATALOG, "REPLACE INTO character_housing_catalog (ownerGuid, houseDecorId, quantity, sourceType, sourceValue) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_CATALOG_COUNT, "UPDATE character_housing_catalog SET quantity = ? WHERE ownerGuid = ? AND houseDecorId = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING_DECOR_SINGLE, "DELETE FROM character_housing_decor WHERE ownerGuid = ? AND id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_NAME_DESC, "UPDATE character_housing SET houseName = ?, houseDescription = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHARACTER_HOUSING_LEVEL_FAVOR, "UPDATE character_housing SET houseLevel = ?, favor = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
|
||||
// Neighborhoods
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD, "SELECT guid, name, neighborhoodMapId, ownerGuid, factionRestriction, isPublic, createTime, guildId FROM neighborhoods WHERE guid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_NEIGHBORHOOD, "INSERT INTO neighborhoods (guid, name, neighborhoodMapId, ownerGuid, factionRestriction, isPublic, createTime, guildId) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_REP_NEIGHBORHOOD, "REPLACE INTO neighborhoods (guid, name, neighborhoodMapId, ownerGuid, factionRestriction, isPublic, createTime, guildId) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_PUBLIC, "UPDATE neighborhoods SET isPublic = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_OWNER, "UPDATE neighborhoods SET ownerGuid = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_GUID_RACE_BY_NAME, "SELECT guid, race FROM characters WHERE name = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_NAME, "UPDATE neighborhoods SET name = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD, "DELETE FROM neighborhoods WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_MEMBERS, "SELECT nm.playerGuid, nm.role, nm.joinTime, nm.plotIndex, ch.houseId, c.account, ch.houseLevel, ch.favor, ch.houseName, ch.houseType, ch.settingsFlags, ch.posX, ch.posY, ch.posZ, ch.facing FROM neighborhood_members nm LEFT JOIN character_housing ch ON nm.playerGuid = ch.guid LEFT JOIN characters c ON nm.playerGuid = c.guid WHERE nm.neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
// Owner-keyed batch fetches of member housing data, joined to neighborhood_members
|
||||
// so every occupied plot renders at neighborhood init even when the owner is offline
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_MEMBER_FIXTURES, "SELECT f.ownerGuid, f.fixturePointId, f.fixtureOptionId FROM character_housing_fixtures f INNER JOIN neighborhood_members nm ON nm.playerGuid = f.ownerGuid WHERE nm.neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_MEMBER_DECOR, "SELECT d.id, d.ownerGuid, d.houseDecorId, d.posX, d.posY, d.posZ, d.rotX, d.rotY, d.rotZ, d.rotW, d.scale, d.dyeSlot0, d.dyeSlot1, d.dyeSlot2, d.roomGuid, d.locked, d.placementTime, d.sourceType, d.sourceValue FROM character_housing_decor d INNER JOIN neighborhood_members nm ON nm.playerGuid = d.ownerGuid WHERE nm.neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_MEMBER_ROOMS, "SELECT r.ownerGuid, r.id, r.houseRoomId, r.slotIndex, r.gridX, r.gridY, r.floorIndex, r.orientation, r.mirrored, r.themeId, r.wallTextureId, r.floorTextureId, r.ceilingTextureId, r.colorOverride, r.doorTypeId, r.doorSlot, r.ceilingTypeId, r.ceilingSlot, r.wallThemeId, r.floorThemeId, r.ceilingThemeId, r.doorTypes, r.componentStyles FROM character_housing_rooms r INNER JOIN neighborhood_members nm ON nm.playerGuid = r.ownerGuid WHERE nm.neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_NEIGHBORHOOD_MEMBER, "INSERT INTO neighborhood_members (neighborhoodGuid, playerGuid, role, joinTime, plotIndex) VALUES (?, ?, ?, ?, ?)", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_MEMBERS, "DELETE FROM neighborhood_members WHERE neighborhoodGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_MEMBER, "DELETE FROM neighborhood_members WHERE neighborhoodGuid = ? AND playerGuid = ?", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_MEMBER_PLOT, "UPDATE neighborhood_members SET plotIndex = ? WHERE neighborhoodGuid = ? AND playerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_MEMBER_ROLE, "UPDATE neighborhood_members SET role = ? WHERE neighborhoodGuid = ? AND playerGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_INVITES, "SELECT inviteeGuid, inviterGuid, inviteTime FROM neighborhood_invites WHERE neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_NEIGHBORHOOD_INVITE, "INSERT INTO neighborhood_invites (neighborhoodGuid, inviteeGuid, inviterGuid, inviteTime) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_INVITES, "DELETE FROM neighborhood_invites WHERE neighborhoodGuid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_INVITE, "DELETE FROM neighborhood_invites WHERE neighborhoodGuid = ? AND inviteeGuid = ?", CONNECTION_ASYNC);
|
||||
|
||||
// Neighborhood Charters
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_CHARTER, "SELECT id, creatorGuid, name, neighborhoodMapId, factionFlags, isGuild, createTime FROM neighborhood_charters WHERE id = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_CHARTER_SIGNATURES, "SELECT signerGuid FROM neighborhood_charter_signatures WHERE charterId = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_REP_NEIGHBORHOOD_CHARTER, "REPLACE INTO neighborhood_charters (id, creatorGuid, name, neighborhoodMapId, factionFlags, isGuild, createTime) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_CHARTER, "DELETE FROM neighborhood_charters WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_NEIGHBORHOOD_CHARTER_SIGNATURE, "INSERT INTO neighborhood_charter_signatures (charterId, signerGuid, signTime) VALUES (?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_CHARTER_SIGNATURES, "DELETE FROM neighborhood_charter_signatures WHERE charterId = ?", CONNECTION_ASYNC);
|
||||
|
||||
// Neighborhood Initiatives
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_INITIATIVES, "SELECT id, neighborhoodGuid, initiativeId, startTime, progress, completed FROM neighborhood_initiatives WHERE neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_INITIATIVE, "SELECT id, neighborhoodGuid, initiativeId, startTime, progress, completed FROM neighborhood_initiatives WHERE id = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_NEIGHBORHOOD_INITIATIVE, "INSERT INTO neighborhood_initiatives (neighborhoodGuid, initiativeId, startTime, progress, completed) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_INITIATIVE, "UPDATE neighborhood_initiatives SET progress = ?, completed = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_INITIATIVE, "DELETE FROM neighborhood_initiatives WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD_INITIATIVES, "DELETE FROM neighborhood_initiatives WHERE neighborhoodGuid = ?", CONNECTION_ASYNC);
|
||||
|
||||
// Neighborhood Initiative Task Progress
|
||||
PrepareStatement(CHAR_SEL_INITIATIVE_TASK_PROGRESS, "SELECT taskId, progress, status FROM neighborhood_initiative_task_progress WHERE initiativeDbId = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_REP_INITIATIVE_TASK_PROGRESS, "REPLACE INTO neighborhood_initiative_task_progress (initiativeDbId, taskId, progress, status) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
|
||||
// Neighborhood Initiative Milestones
|
||||
PrepareStatement(CHAR_SEL_INITIATIVE_MILESTONES, "SELECT milestoneIndex, reached, reachedTime FROM neighborhood_initiative_milestones WHERE initiativeDbId = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_REP_INITIATIVE_MILESTONE, "REPLACE INTO neighborhood_initiative_milestones (initiativeDbId, milestoneIndex, reached, reachedTime) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
|
||||
// Neighborhood Initiative Reward Claims
|
||||
PrepareStatement(CHAR_SEL_INITIATIVE_REWARD_CLAIMS, "SELECT milestoneIndex, playerGuid, claimTime FROM neighborhood_initiative_reward_claims WHERE initiativeDbId = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_INITIATIVE_REWARD_CLAIM_PLAYER, "SELECT milestoneIndex FROM neighborhood_initiative_reward_claims WHERE initiativeDbId = ? AND playerGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_INITIATIVE_REWARD_CLAIM, "INSERT IGNORE INTO neighborhood_initiative_reward_claims (initiativeDbId, milestoneIndex, playerGuid, claimTime) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
|
||||
// Neighborhood Initiative Contributions
|
||||
PrepareStatement(CHAR_INS_INITIATIVE_CONTRIBUTION, "INSERT INTO neighborhood_initiative_contributions (initiativeDbId, playerGuid, taskId, amount, lastUpdated) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE amount = amount + VALUES(amount), lastUpdated = VALUES(lastUpdated)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_INITIATIVE_CONTRIBUTIONS, "SELECT playerGuid, taskId, amount, lastUpdated FROM neighborhood_initiative_contributions WHERE initiativeDbId = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_PLAYER_INITIATIVE_FAVOR, "SELECT COALESCE(SUM(amount), 0) FROM neighborhood_initiative_contributions WHERE initiativeDbId = ? AND playerGuid = ?", CONNECTION_SYNCH);
|
||||
}
|
||||
|
||||
CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo, ConnectionFlags connectionFlags) : MySQLConnection(connInfo, connectionFlags)
|
||||
@@ -1099,3 +1210,7 @@ CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& co
|
||||
CharacterDatabaseConnection::~CharacterDatabaseConnection()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -921,6 +921,100 @@ enum CharacterDatabaseStatements : uint32
|
||||
CHAR_INS_CRAFTING_ORDER_REPORT,
|
||||
//WowCommunity end
|
||||
|
||||
// Housing
|
||||
CHAR_SEL_CHARACTER_HOUSING,
|
||||
CHAR_INS_CHARACTER_HOUSING,
|
||||
CHAR_DEL_CHARACTER_HOUSING,
|
||||
CHAR_SEL_CHARACTER_HOUSING_DECOR,
|
||||
CHAR_INS_CHARACTER_HOUSING_DECOR,
|
||||
CHAR_DEL_CHARACTER_HOUSING_DECOR,
|
||||
CHAR_SEL_CHARACTER_HOUSING_ROOMS,
|
||||
CHAR_INS_CHARACTER_HOUSING_ROOMS,
|
||||
CHAR_DEL_CHARACTER_HOUSING_ROOMS,
|
||||
CHAR_SEL_CHARACTER_HOUSING_FIXTURES,
|
||||
CHAR_INS_CHARACTER_HOUSING_FIXTURES,
|
||||
CHAR_DEL_CHARACTER_HOUSING_FIXTURES,
|
||||
CHAR_SEL_CHARACTER_HOUSING_CATALOG,
|
||||
CHAR_INS_CHARACTER_HOUSING_CATALOG,
|
||||
CHAR_DEL_CHARACTER_HOUSING_CATALOG,
|
||||
CHAR_UPD_CHARACTER_HOUSING_SETTINGS,
|
||||
CHAR_UPD_CHARACTER_HOUSING_EXTERIOR_LOCKED,
|
||||
CHAR_UPD_CHARACTER_HOUSING_HOUSE_SIZE,
|
||||
CHAR_UPD_CHARACTER_HOUSING_HOUSE_TYPE,
|
||||
CHAR_UPD_CHARACTER_HOUSING_POSITION,
|
||||
CHAR_UPD_CHARACTER_HOUSING_DECOR_POSITION,
|
||||
CHAR_UPD_CHARACTER_HOUSING_DECOR_DYES,
|
||||
CHAR_UPD_CHARACTER_HOUSING_DECOR_LOCKED,
|
||||
CHAR_UPD_CHARACTER_HOUSING_DECOR_PET,
|
||||
CHAR_UPD_CHARACTER_HOUSING_DECOR_PARENT,
|
||||
CHAR_SEL_CHARACTER_HOUSING_IGNORED_NEIGHBORHOOD,
|
||||
CHAR_INS_CHARACTER_HOUSING_IGNORED_NEIGHBORHOOD,
|
||||
CHAR_DEL_CHARACTER_HOUSING_IGNORED_NEIGHBORHOOD,
|
||||
CHAR_SEL_ACCOUNT_HOUSING_BLUEPRINTS,
|
||||
CHAR_INS_ACCOUNT_HOUSING_BLUEPRINT,
|
||||
CHAR_UPD_ACCOUNT_HOUSING_BLUEPRINT_NAME,
|
||||
CHAR_DEL_ACCOUNT_HOUSING_BLUEPRINT,
|
||||
CHAR_SEL_ACCOUNT_HOUSING_ROOMS,
|
||||
CHAR_SEL_ACCOUNT_HOUSING_OWNERS,
|
||||
CHAR_INS_ACCOUNT_HOUSING_ROOM,
|
||||
CHAR_SEL_ACCOUNT_HOUSING_HOUSE_TYPES,
|
||||
CHAR_INS_ACCOUNT_HOUSING_HOUSE_TYPE,
|
||||
CHAR_UPD_CHARACTER_HOUSING_OWNER,
|
||||
CHAR_UPD_CHARACTER_HOUSING_DECOR_OWNER,
|
||||
CHAR_UPD_CHARACTER_HOUSING_ROOMS_OWNER,
|
||||
CHAR_UPD_CHARACTER_HOUSING_FIXTURES_OWNER,
|
||||
CHAR_UPD_CHARACTER_HOUSING_CATALOG_OWNER,
|
||||
CHAR_UPD_NEIGHBORHOOD_MEMBER_PLAYER,
|
||||
CHAR_UPD_CHARACTER_HOUSING_ROOM,
|
||||
CHAR_UPD_CHARACTER_HOUSING_FIXTURE,
|
||||
CHAR_DEL_CHARACTER_HOUSING_FIXTURE_SINGLE,
|
||||
CHAR_REP_CHARACTER_HOUSING_CATALOG,
|
||||
CHAR_UPD_CHARACTER_HOUSING_CATALOG_COUNT,
|
||||
CHAR_DEL_CHARACTER_HOUSING_DECOR_SINGLE,
|
||||
CHAR_UPD_CHARACTER_HOUSING_NAME_DESC,
|
||||
CHAR_UPD_CHARACTER_HOUSING_LEVEL_FAVOR,
|
||||
|
||||
// Neighborhoods
|
||||
CHAR_SEL_NEIGHBORHOOD,
|
||||
CHAR_INS_NEIGHBORHOOD,
|
||||
CHAR_REP_NEIGHBORHOOD,
|
||||
CHAR_UPD_NEIGHBORHOOD_PUBLIC,
|
||||
CHAR_UPD_NEIGHBORHOOD_OWNER,
|
||||
CHAR_SEL_CHARACTER_GUID_RACE_BY_NAME,
|
||||
CHAR_UPD_NEIGHBORHOOD_NAME,
|
||||
CHAR_DEL_NEIGHBORHOOD,
|
||||
CHAR_SEL_NEIGHBORHOOD_MEMBERS,
|
||||
CHAR_SEL_NEIGHBORHOOD_MEMBER_FIXTURES,
|
||||
CHAR_SEL_NEIGHBORHOOD_MEMBER_DECOR,
|
||||
CHAR_SEL_NEIGHBORHOOD_MEMBER_ROOMS,
|
||||
CHAR_INS_NEIGHBORHOOD_MEMBER,
|
||||
CHAR_DEL_NEIGHBORHOOD_MEMBERS,
|
||||
CHAR_DEL_NEIGHBORHOOD_MEMBER,
|
||||
CHAR_UPD_NEIGHBORHOOD_MEMBER_PLOT,
|
||||
CHAR_UPD_NEIGHBORHOOD_MEMBER_ROLE,
|
||||
CHAR_SEL_NEIGHBORHOOD_INVITES,
|
||||
CHAR_INS_NEIGHBORHOOD_INVITE,
|
||||
CHAR_DEL_NEIGHBORHOOD_INVITES,
|
||||
CHAR_DEL_NEIGHBORHOOD_INVITE,
|
||||
|
||||
// Neighborhood Charters
|
||||
CHAR_SEL_NEIGHBORHOOD_CHARTER,
|
||||
CHAR_SEL_NEIGHBORHOOD_CHARTER_SIGNATURES,
|
||||
CHAR_REP_NEIGHBORHOOD_CHARTER,
|
||||
CHAR_DEL_NEIGHBORHOOD_CHARTER,
|
||||
CHAR_INS_NEIGHBORHOOD_CHARTER_SIGNATURE,
|
||||
CHAR_DEL_NEIGHBORHOOD_CHARTER_SIGNATURES,
|
||||
|
||||
// Neighborhood Initiatives
|
||||
|
||||
// Neighborhood Initiative Task Progress
|
||||
|
||||
// Neighborhood Initiative Milestones
|
||||
|
||||
// Neighborhood Initiative Reward Claims
|
||||
|
||||
// Neighborhood Initiative Contributions
|
||||
|
||||
MAX_CHARACTERDATABASE_STATEMENTS
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,96 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
"Points, Flags, UiOrder, IconFileID, RewardItemID, CriteriaTree, SharesCriteria, CovenantID, HiddenBeforeDisplaySeason, LegacyAfterTimeEvent"
|
||||
" FROM achievement WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ACHIEVEMENT, "SELECT MAX(ID) + 1 FROM achievement", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_ROOM_COMPONENT_TEXTURE, "SELECT ID, Name_lang FROM room_component_texture_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN, "SELECT ID, Prefix_lang, Suffix_lang, FullName_lang FROM neighborhood_name_gen_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE, "SELECT ID, Name_lang, Description_lang FROM neighborhood_initiative_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_INITIATIVE_TASK, "SELECT ID, Name_lang, Description_lang FROM initiative_task_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_INITIATIVE_REWARD, "SELECT ID, Name_lang, Description_lang FROM initiative_reward_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_THEME, "SELECT ID, Name_lang FROM house_theme_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_ROOM, "SELECT ID, Name_lang FROM house_room_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO, "SELECT ID, Name_lang, Description_lang FROM house_level_reward_info_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA, "SELECT ID, Name_lang FROM house_exterior_wmo_data_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_DECOR_THEME_SET, "SELECT ID, Name_lang FROM house_decor_theme_set_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_DECOR, "SELECT ID, Name_lang FROM house_decor_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE, "SELECT ID, Name_lang FROM exterior_component_type_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT, "SELECT ID, Name_lang FROM exterior_component_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_DYE_COLOR, "SELECT ID, Name_lang FROM dye_color_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_DECOR_SUBCATEGORY, "SELECT ID, Name_lang FROM decor_subcategory_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_DECOR_CATEGORY, "SELECT ID, Name_lang FROM decor_category_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_ACHIEVEMENT, "SELECT ID, Description_lang, Title_lang, Reward_lang FROM achievement_locale"
|
||||
" WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
@@ -162,9 +252,8 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ARTIFACT_UNLOCK, "SELECT MAX(ID) + 1 FROM artifact_unlock", CONNECTION_SYNCH);
|
||||
|
||||
// AuctionHouse.db2
|
||||
PrepareStatement(HOTFIX_SEL_AUCTION_HOUSE, "SELECT ID, Name, FactionID, DepositRate, ConsignmentRate FROM auction_house"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_AUCTION_HOUSE, "SELECT MAX(ID) + 1 FROM auction_house", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_AUCTION_HOUSE, "SELECT ID, Name_lang FROM auction_house_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// AzeriteEmpoweredItem.db2
|
||||
@@ -597,31 +686,13 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_CURVE_POINT, "SELECT MAX(ID) + 1 FROM curve_point", CONNECTION_SYNCH);
|
||||
|
||||
// DataTagXHouseDecorRecord.db2
|
||||
PrepareStatement(HOTFIX_SEL_DATA_TAG_X_HOUSE_DECOR_RECORD, "SELECT ID, DataTagID, HouseDecorID FROM data_tag_x_house_decor_record"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DATA_TAG_X_HOUSE_DECOR_RECORD, "SELECT MAX(ID) + 1 FROM data_tag_x_house_decor_record", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// DecorCategory.db2
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_CATEGORY, "SELECT Name, ID, IconFileDataID, DisplayIndex FROM decor_category"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_CATEGORY, "SELECT MAX(ID) + 1 FROM decor_category", CONNECTION_SYNCH);
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_DECOR_CATEGORY, "SELECT ID, Name_lang FROM decor_category_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// DecorDyeSlot.db2
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_DYE_SLOT, "SELECT ID, SlotIndex, HouseDecorID, DyeChannelType, DefaultDyeRecordID FROM decor_dye_slot"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_DYE_SLOT, "SELECT MAX(ID) + 1 FROM decor_dye_slot", CONNECTION_SYNCH);
|
||||
|
||||
// DecorSubcategory.db2
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_SUBCATEGORY, "SELECT Name, ID, IconFileDataID, DecorCategoryID, DisplayIndex FROM decor_subcategory"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_SUBCATEGORY, "SELECT MAX(ID) + 1 FROM decor_subcategory", CONNECTION_SYNCH);
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_DECOR_SUBCATEGORY, "SELECT ID, Name_lang FROM decor_subcategory_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// DecorXDecorSubcategory.db2
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY, "SELECT ID, DecorSubcategoryID, HouseDecorID FROM decor_x_decor_subcategory"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY, "SELECT MAX(ID) + 1 FROM decor_x_decor_subcategory", CONNECTION_SYNCH);
|
||||
// DyeColorCategory.db2
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_DYE_COLOR_CATEGORY, "SELECT ID, Name_lang FROM dye_color_category_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// DestructibleModelData.db2
|
||||
PrepareStatement(HOTFIX_SEL_DESTRUCTIBLE_MODEL_DATA, "SELECT ID, State0ImpactEffectDoodadSet, State0AmbientDoodadSet, State1Wmo, "
|
||||
@@ -685,43 +756,35 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXPECTED_STAT_MOD, "SELECT MAX(ID) + 1 FROM expected_stat_mod", CONNECTION_SYNCH);
|
||||
|
||||
// ExteriorComponent.db2
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT, "SELECT Name, Position1, Position2, Position3, ID, Size, HouseExteriorWmoDataID, "
|
||||
"ParentComponentID, ModelFileDataID, Flags, Field_12_0_0_63534_007, Type, Field_12_0_0_63534_009, GameObjectID, Field_11_2_7_64044_011, "
|
||||
"ItemID FROM exterior_component WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT, "SELECT MAX(ID) + 1 FROM exterior_component", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT, "SELECT ID, Name_lang FROM exterior_component_locale WHERE (`VerifiedBuild` > 0) = ?"
|
||||
" AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// ExteriorComponentExitPoint.db2
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT, "SELECT PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, ID, "
|
||||
"ExteriorComponentID FROM exterior_component_exit_point WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT, "SELECT MAX(ID) + 1 FROM exterior_component_exit_point", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// ExteriorComponentGroup.db2
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP, "SELECT PositionX, PositionY, PositionZ, ID, ExteriorComponentID"
|
||||
" FROM exterior_component_group WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP, "SELECT MAX(ID) + 1 FROM exterior_component_group", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// ExteriorComponentGroupXHook.db2
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK, "SELECT ID, ExteriorComponentGroupID, ExteriorComponentHookID"
|
||||
" FROM exterior_component_group_x_hook WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK, "SELECT MAX(ID) + 1 FROM exterior_component_group_x_hook", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// ExteriorComponentHook.db2
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK, "SELECT PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, ID, "
|
||||
"ExteriorComponentTypeID, ExteriorComponentID FROM exterior_component_hook WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK, "SELECT MAX(ID) + 1 FROM exterior_component_hook", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// ExteriorComponentType.db2
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE, "SELECT Name, ID, Flags FROM exterior_component_type"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE, "SELECT MAX(ID) + 1 FROM exterior_component_type", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE, "SELECT ID, Name_lang FROM exterior_component_type_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// ExteriorComponentXGroup.db2
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP, "SELECT ID, ExteriorComponentID, ExteriorComponentGroupID"
|
||||
" FROM exterior_component_x_group WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP, "SELECT MAX(ID) + 1 FROM exterior_component_x_group", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// Faction.db2
|
||||
PrepareStatement(HOTFIX_SEL_FACTION, "SELECT ID, Name, Description, ReputationIndex, ParentFactionID, Expansion, FriendshipRepID, Flags, "
|
||||
@@ -1169,56 +1232,45 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOLIDAYS, "SELECT MAX(ID) + 1 FROM holidays", CONNECTION_SYNCH);
|
||||
|
||||
// House.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE, "SELECT ID, InternalName, HouseTypeID, MapID, Flags FROM house"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE, "SELECT MAX(ID) + 1 FROM house", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// HouseDecor.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_DECOR, "SELECT Name, InitialRotationX, InitialRotationY, InitialRotationZ, ID, Field_003, GameObjectID, Flags, "
|
||||
"Type, ModelType, ModelFileDataID, ThumbnailFileDataID, WeightCost, ItemID, InitialScale, "
|
||||
"FirstAcquisitionBonus, OrderIndex, Size, StartingQuantity, UiModelSceneID FROM house_decor"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_DECOR, "SELECT MAX(ID) + 1 FROM house_decor", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_DECOR, "SELECT ID, Name_lang FROM house_decor_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// HouseDecorMaterial.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_DECOR_MATERIAL, "SELECT ID, MaterialGUID, HouseDecorID, MaterialIndex, DefaultDyeID, AllowedDyeMask"
|
||||
" FROM house_decor_material WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_DECOR_MATERIAL, "SELECT MAX(ID) + 1 FROM house_decor_material", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// HouseDecorThemeSet.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_DECOR_THEME_SET, "SELECT ID, Name, ThemeID, IconFileDataID FROM house_decor_theme_set"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_DECOR_THEME_SET, "SELECT MAX(ID) + 1 FROM house_decor_theme_set", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_DECOR_THEME_SET, "SELECT ID, Name_lang FROM house_decor_theme_set_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// HouseExteriorWmoData.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA, "SELECT Name, ID, Flags FROM house_exterior_wmo_data"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA, "SELECT MAX(ID) + 1 FROM house_exterior_wmo_data", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA, "SELECT ID, Name_lang FROM house_exterior_wmo_data_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// HouseLevelData.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_LEVEL_DATA, "SELECT ID, Level, QuestID FROM house_level_data"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_LEVEL_DATA, "SELECT MAX(ID) + 1 FROM house_level_data", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// HouseLevelRewardInfo.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO, "SELECT Name, Description, ID, HouseLevelID, RewardType, RewardValue"
|
||||
" FROM house_level_reward_info WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO, "SELECT MAX(ID) + 1 FROM house_level_reward_info", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO, "SELECT ID, Name_lang, Description_lang FROM house_level_reward_info_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// HouseRoom.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_ROOM, "SELECT ID, Name, Size, Flags, Field_002, RoomWmoDataID, UiTextureAtlasElementID, WeightCost"
|
||||
" FROM house_room WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_ROOM, "SELECT MAX(ID) + 1 FROM house_room", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_ROOM, "SELECT ID, Name_lang FROM house_room_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// HouseTheme.db2
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_THEME, "SELECT Name, ID, Flags, ParentThemeID FROM house_theme"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_THEME, "SELECT MAX(ID) + 1 FROM house_theme", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_HOUSE_THEME, "SELECT ID, Name_lang FROM house_theme_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// ImportPriceArmor.db2
|
||||
@@ -1239,34 +1291,34 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_IMPORT_PRICE_WEAPON, "SELECT MAX(ID) + 1 FROM import_price_weapon", CONNECTION_SYNCH);
|
||||
|
||||
// InitiativeCycle.db2
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_CYCLE, "SELECT ID, RewardGroupID, CycleIndex, StartDay, Duration, InitiativeID FROM initiative_cycle WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_CYCLE, "SELECT MAX(ID) + 1 FROM initiative_cycle", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// InitiativeCyclePriority.db2
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY, "SELECT ID, Priority, Weight, Flags, InitiativeCycleID FROM initiative_cycle_priority WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY, "SELECT MAX(ID) + 1 FROM initiative_cycle_priority", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// InitiativeMilestone.db2
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_MILESTONE, "SELECT ID, MilestoneIndex, ProgressRequired, Flags, InitiativeCycleID FROM initiative_milestone WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_MILESTONE, "SELECT MAX(ID) + 1 FROM initiative_milestone", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// InitiativeReward.db2
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_REWARD, "SELECT RewardData, Name, Description, ID, RewardType, RewardAmount, CurrencyID, ItemID, Flags FROM initiative_reward WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_REWARD, "SELECT MAX(ID) + 1 FROM initiative_reward", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_INITIATIVE_REWARD, "SELECT ID, Name_lang, Description_lang FROM initiative_reward_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// InitiativeRewardXMilestone.db2
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE, "SELECT ID, InitiativeRewardID, InitiativeMilestoneID FROM initiative_reward_x_milestone WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE, "SELECT MAX(ID) + 1 FROM initiative_reward_x_milestone", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// InitiativeTask.db2
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_TASK, "SELECT Name, Description, ID, TaskType, TargetCount, CriteriaTreeID, SortOrder, Flags, UiTextureKitID FROM initiative_task WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_TASK, "SELECT MAX(ID) + 1 FROM initiative_task", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_INITIATIVE_TASK, "SELECT ID, Name_lang, Description_lang FROM initiative_task_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// InitiativeXTask.db2
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_X_TASK, "SELECT ID, InitiativeTaskID, SortOrder, NeighborhoodInitiativeID FROM initiative_x_task WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_X_TASK, "SELECT MAX(ID) + 1 FROM initiative_x_task", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// Item.db2
|
||||
PrepareStatement(HOTFIX_SEL_ITEM, "SELECT ID, ClassID, SubclassID, Material, InventoryType, SheatheType, SoundOverrideSubclassID, IconFileDataID, "
|
||||
@@ -1697,34 +1749,23 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NAMES_RESERVED_LOCALE, "SELECT MAX(ID) + 1 FROM names_reserved_locale", CONNECTION_SYNCH);
|
||||
|
||||
// NeighborhoodInitiative.db2
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE, "SELECT Name, Description, ID, InitiativeType, Duration, RequiredParticipants, "
|
||||
"RewardCurrencyID FROM neighborhood_initiative"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE, "SELECT MAX(ID) + 1 FROM neighborhood_initiative", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE, "SELECT ID, Name_lang, Description_lang FROM neighborhood_initiative_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
// NeighborhoodMap.db2
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_MAP, "SELECT PositionX, PositionY, PositionZ, ID, MapID, Radius, PlotCount, FactionRestriction"
|
||||
" FROM neighborhood_map WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_MAP, "SELECT MAX(ID) + 1 FROM neighborhood_map", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// NeighborhoodNameGen.db2
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN, "SELECT ID, Prefix, Suffix, FullName, NeighborhoodMapID"
|
||||
" FROM neighborhood_name_gen WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN, "SELECT MAX(ID) + 1 FROM neighborhood_name_gen", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN, "SELECT ID, Prefix_lang, Suffix_lang, FullName_lang FROM neighborhood_name_gen_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// NeighborhoodPlot.db2
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_PLOT, "SELECT Cost, Name, "
|
||||
"HousePositionX, HousePositionY, HousePositionZ, HouseRotationX, HouseRotationY, HouseRotationZ, "
|
||||
"CornerstonePositionX, CornerstonePositionY, CornerstonePositionZ, "
|
||||
"CornerstoneRotationX, CornerstoneRotationY, CornerstoneRotationZ, "
|
||||
"TeleportPositionX, TeleportPositionY, TeleportPositionZ, "
|
||||
"ID, NeighborhoodMapID, Field_010, CornerstoneGameObjectID, "
|
||||
"PlotIndex, WorldState, PlotGameObjectID, TeleportFacing, Field_016"
|
||||
" FROM neighborhood_plot WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_PLOT, "SELECT MAX(ID) + 1 FROM neighborhood_plot", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// NumTalentsAtLevel.db2
|
||||
PrepareStatement(HOTFIX_SEL_NUM_TALENTS_AT_LEVEL, "SELECT ID, NumTalents, NumTalentsDeathKnight, NumTalentsDemonHunter, Unknown1115"
|
||||
@@ -1973,30 +2014,25 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_REWARD_PACK_X_ITEM, "SELECT MAX(ID) + 1 FROM reward_pack_x_item", CONNECTION_SYNCH);
|
||||
|
||||
// RoomComponent.db2
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT, "SELECT OffsetPosX, OffsetPosY, OffsetPosZ, OffsetRotX, OffsetRotY, OffsetRotZ, ID, RoomWmoDataID, "
|
||||
"ModelFileDataID, Type, MeshStyleFilterID, ConnectionType, Flags FROM room_component WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT, "SELECT MAX(ID) + 1 FROM room_component", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// RoomComponentOption.db2
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT_OPTION, "SELECT ID, Type, SubType, ModelFileDataID, RoomComponentID, MeshStyleFilterID, HouseThemeID, Flags"
|
||||
" FROM room_component_option WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT_OPTION, "SELECT MAX(ID) + 1 FROM room_component_option", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// RoomComponentOptionTexture.db2
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE, "SELECT ID, RoomComponentOptionID, RoomComponentTextureID, Flags"
|
||||
" FROM room_component_option_texture WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE, "SELECT MAX(ID) + 1 FROM room_component_option_texture", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// RoomComponentTexture.db2
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT_TEXTURE, "SELECT Name, ID, Type, FileDataID, Flags, UiOrder, RoomComponentID"
|
||||
" FROM room_component_texture WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT_TEXTURE, "SELECT MAX(ID) + 1 FROM room_component_texture", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_ROOM_COMPONENT_TEXTURE, "SELECT ID, Name_lang FROM room_component_texture_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// RoomWmoData.db2
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_WMO_DATA, "SELECT ID, BoundingBoxMinX, BoundingBoxMinY, BoundingBoxMinZ, BoundingBoxMaxX, BoundingBoxMaxY, "
|
||||
"BoundingBoxMaxZ, Height FROM room_wmo_data WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_WMO_DATA, "SELECT MAX(ID) + 1 FROM room_wmo_data", CONNECTION_SYNCH);
|
||||
|
||||
|
||||
|
||||
// Scenario.db2
|
||||
PrepareStatement(HOTFIX_SEL_SCENARIO, "SELECT ID, Name, AreaTableID, Type, Flags, UiTextureKitID, UiScenarioDisplayInfoID FROM scenario"
|
||||
@@ -3053,4 +3089,136 @@ HotfixDatabaseConnection::HotfixDatabaseConnection(MySQLConnectionInfo& connInfo
|
||||
|
||||
HotfixDatabaseConnection::~HotfixDatabaseConnection()
|
||||
{
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_WMO_DATA, "SELECT ID, BoundingBoxMinX, BoundingBoxMinY, BoundingBoxMinZ, BoundingBoxMaxX, BoundingBoxMaxY, "
|
||||
"BoundingBoxMaxZ, Height FROM room_wmo_data WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_WMO_DATA, "SELECT MAX(ID) + 1 FROM room_wmo_data", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_GRID_LINE, "SELECT OffsetX, OffsetY, OffsetZ, ID, SizeX, SizeY, RoomWmoDataID FROM room_grid_line"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_GRID_LINE, "SELECT MAX(ID) + 1 FROM room_grid_line", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT_TEXTURE, "SELECT Name, ID, Type, FileDataID, Flags, UiOrder, RoomComponentID"
|
||||
" FROM room_component_texture WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT_TEXTURE, "SELECT MAX(ID) + 1 FROM room_component_texture", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT, "SELECT OffsetPosX, OffsetPosY, OffsetPosZ, OffsetRotX, OffsetRotY, OffsetRotZ, ID, RoomWmoDataID, "
|
||||
"ModelFileDataID, Type, MeshStyleFilterID, ConnectionType, Flags FROM room_component WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT, "SELECT MAX(ID) + 1 FROM room_component", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE, "SELECT ID, RoomComponentOptionID, RoomComponentTextureID, Flags"
|
||||
" FROM room_component_option_texture WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE, "SELECT MAX(ID) + 1 FROM room_component_option_texture", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_ROOM_COMPONENT_OPTION, "SELECT ID, Type, SubType, ModelFileDataID, RoomComponentID, MeshStyleFilterID, HouseThemeID, Flags"
|
||||
" FROM room_component_option WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_ROOM_COMPONENT_OPTION, "SELECT MAX(ID) + 1 FROM room_component_option", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_PLOT, "SELECT Cost, Name, "
|
||||
"HousePositionX, HousePositionY, HousePositionZ, HouseRotationX, HouseRotationY, HouseRotationZ, "
|
||||
"CornerstonePositionX, CornerstonePositionY, CornerstonePositionZ, "
|
||||
"CornerstoneRotationX, CornerstoneRotationY, CornerstoneRotationZ, "
|
||||
"TeleportPositionX, TeleportPositionY, TeleportPositionZ, "
|
||||
"ID, NeighborhoodMapID, Field_010, CornerstoneGameObjectID, "
|
||||
"PlotIndex, WorldState, PlotGameObjectID, TeleportFacing, Field_016"
|
||||
" FROM neighborhood_plot WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_PLOT, "SELECT MAX(ID) + 1 FROM neighborhood_plot", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN, "SELECT ID, Prefix, Suffix, FullName, NeighborhoodMapID"
|
||||
" FROM neighborhood_name_gen WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN, "SELECT MAX(ID) + 1 FROM neighborhood_name_gen", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_MAP, "SELECT PositionX, PositionY, PositionZ, ID, MapID, Radius, PlotCount, FactionRestriction"
|
||||
" FROM neighborhood_map WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_MAP, "SELECT MAX(ID) + 1 FROM neighborhood_map", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE, "SELECT Name, Description, ID, InitiativeType, Duration, RequiredParticipants, "
|
||||
"RewardCurrencyID FROM neighborhood_initiative"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE, "SELECT MAX(ID) + 1 FROM neighborhood_initiative", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_X_TASK, "SELECT ID, InitiativeTaskID, SortOrder, NeighborhoodInitiativeID FROM initiative_x_task WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_X_TASK, "SELECT MAX(ID) + 1 FROM initiative_x_task", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_TASK, "SELECT Name, Description, ID, TaskType, TargetCount, CriteriaTreeID, SortOrder, Flags, UiTextureKitID FROM initiative_task WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_TASK, "SELECT MAX(ID) + 1 FROM initiative_task", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE, "SELECT ID, InitiativeRewardID, InitiativeMilestoneID FROM initiative_reward_x_milestone WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE, "SELECT MAX(ID) + 1 FROM initiative_reward_x_milestone", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_REWARD, "SELECT RewardData, Name, Description, ID, RewardType, RewardAmount, CurrencyID, ItemID, Flags FROM initiative_reward WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_REWARD, "SELECT MAX(ID) + 1 FROM initiative_reward", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_MILESTONE, "SELECT ID, MilestoneIndex, ProgressRequired, Flags, InitiativeCycleID FROM initiative_milestone WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_MILESTONE, "SELECT MAX(ID) + 1 FROM initiative_milestone", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_CYCLE, "SELECT ID, RewardGroupID, CycleIndex, StartDay, Duration, InitiativeID FROM initiative_cycle WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_CYCLE, "SELECT MAX(ID) + 1 FROM initiative_cycle", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY, "SELECT ID, Priority, Weight, Flags, InitiativeCycleID FROM initiative_cycle_priority WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY, "SELECT MAX(ID) + 1 FROM initiative_cycle_priority", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_THEME, "SELECT Name, ID, Flags, ParentThemeID FROM house_theme"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_THEME, "SELECT MAX(ID) + 1 FROM house_theme", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_ROOM, "SELECT ID, Name, Size, Flags, Field_002, RoomWmoDataID, UiTextureAtlasElementID, WeightCost"
|
||||
" FROM house_room WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_ROOM, "SELECT MAX(ID) + 1 FROM house_room", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO, "SELECT Name, Description, ID, HouseLevelID, RewardType, RewardValue"
|
||||
" FROM house_level_reward_info WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO, "SELECT MAX(ID) + 1 FROM house_level_reward_info", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_LEVEL_DATA, "SELECT ID, Level, QuestID FROM house_level_data"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_LEVEL_DATA, "SELECT MAX(ID) + 1 FROM house_level_data", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA, "SELECT Name, ID, Flags FROM house_exterior_wmo_data"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA, "SELECT MAX(ID) + 1 FROM house_exterior_wmo_data", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_DECOR_THEME_SET, "SELECT ID, Name, ThemeID, IconFileDataID FROM house_decor_theme_set"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_DECOR_THEME_SET, "SELECT MAX(ID) + 1 FROM house_decor_theme_set", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_DECOR, "SELECT Name, InitialRotationX, InitialRotationY, InitialRotationZ, ID, Field_003, GameObjectID, Flags, "
|
||||
"Type, ModelType, ModelFileDataID, ThumbnailFileDataID, WeightCost, ItemID, InitialScale, "
|
||||
"FirstAcquisitionBonus, OrderIndex, Size, StartingQuantity, UiModelSceneID FROM house_decor"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_DECOR, "SELECT MAX(ID) + 1 FROM house_decor", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE_DECOR_MATERIAL, "SELECT ID, MaterialGUID, HouseDecorID, MaterialIndex, DefaultDyeID, AllowedDyeMask"
|
||||
" FROM house_decor_material WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE_DECOR_MATERIAL, "SELECT MAX(ID) + 1 FROM house_decor_material", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP, "SELECT ID, ExteriorComponentID, ExteriorComponentGroupID"
|
||||
" FROM exterior_component_x_group WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP, "SELECT MAX(ID) + 1 FROM exterior_component_x_group", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE, "SELECT Name, ID, Flags FROM exterior_component_type"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE, "SELECT MAX(ID) + 1 FROM exterior_component_type", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT, "SELECT Name, PositionX, PositionY, PositionZ, ID, Size, "
|
||||
"HouseExteriorWmoDataID, ParentComponentID, ModelFileDataID, Flags, Field_7, Type, Field_9, GameObjectID, Field_11, ItemID "
|
||||
"FROM exterior_component"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT, "SELECT MAX(ID) + 1 FROM exterior_component", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK, "SELECT PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, ID, "
|
||||
"ExteriorComponentTypeID, ExteriorComponentID FROM exterior_component_hook WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK, "SELECT MAX(ID) + 1 FROM exterior_component_hook", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK, "SELECT ID, ExteriorComponentGroupID, ExteriorComponentHookID"
|
||||
" FROM exterior_component_group_x_hook WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK, "SELECT MAX(ID) + 1 FROM exterior_component_group_x_hook", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP, "SELECT PositionX, PositionY, PositionZ, ID, ExteriorComponentID"
|
||||
" FROM exterior_component_group WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP, "SELECT MAX(ID) + 1 FROM exterior_component_group", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT, "SELECT PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, ID, "
|
||||
"ExteriorComponentID FROM exterior_component_exit_point WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT, "SELECT MAX(ID) + 1 FROM exterior_component_exit_point", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_DYE_COLOR, "SELECT Name, ID, DyeColorCategoryID, GradientTextureIndex, ItemID, SwatchColorStart, SwatchColorEnd, SortOrder FROM dye_color"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DYE_COLOR, "SELECT MAX(ID) + 1 FROM dye_color", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY, "SELECT ID, DecorSubcategoryID, HouseDecorID FROM decor_x_decor_subcategory"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY, "SELECT MAX(ID) + 1 FROM decor_x_decor_subcategory", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_SUBCATEGORY, "SELECT Name, ID, IconFileDataID, DecorCategoryID, DisplayIndex FROM decor_subcategory"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_SUBCATEGORY, "SELECT MAX(ID) + 1 FROM decor_subcategory", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_DYE_SLOT, "SELECT ID, SlotIndex, HouseDecorID, DyeChannelType, DefaultDyeRecordID FROM decor_dye_slot"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_DYE_SLOT, "SELECT MAX(ID) + 1 FROM decor_dye_slot", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_DECOR_CATEGORY, "SELECT Name, ID, IconFileDataID, DisplayIndex FROM decor_category"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DECOR_CATEGORY, "SELECT MAX(ID) + 1 FROM decor_category", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_AUCTION_HOUSE, "SELECT ID, Name, FactionID, DepositRate, ConsignmentRate FROM auction_house"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_AUCTION_HOUSE, "SELECT MAX(ID) + 1 FROM auction_house", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_DATA_TAG_X_HOUSE_DECOR_RECORD, "SELECT ID, DataTagID, HouseDecorID FROM data_tag_x_house_decor_record"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DATA_TAG_X_HOUSE_DECOR_RECORD, "SELECT MAX(ID) + 1 FROM data_tag_x_house_decor_record", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_DYE_COLOR_CATEGORY, "SELECT Name, ID FROM dye_color_category"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_DYE_COLOR_CATEGORY, "SELECT MAX(ID) + 1 FROM dye_color_category", CONNECTION_SYNCH);
|
||||
PrepareStatement(HOTFIX_SEL_HOUSE, "SELECT ID, InternalName, HouseTypeID, MapID, Flags FROM house"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_HOUSE, "SELECT MAX(ID) + 1 FROM house", CONNECTION_SYNCH);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,99 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_ACHIEVEMENT,
|
||||
HOTFIX_SEL_ACHIEVEMENT_MAX_ID,
|
||||
HOTFIX_SEL_ACHIEVEMENT_LOCALE,
|
||||
HOTFIX_SEL_ROOM_WMO_DATA,
|
||||
HOTFIX_SEL_ROOM_WMO_DATA_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_GRID_LINE,
|
||||
HOTFIX_SEL_ROOM_GRID_LINE_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_TEXTURE,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_TEXTURE_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_TEXTURE_LOCALE,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE_MAX_ID,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_PLOT,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_PLOT_MAX_ID,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN_MAX_ID,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN_LOCALE,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_MAP,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_MAP_MAX_ID,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE_MAX_ID,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE_LOCALE,
|
||||
HOTFIX_SEL_INITIATIVE_X_TASK,
|
||||
HOTFIX_SEL_INITIATIVE_X_TASK_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_TASK,
|
||||
HOTFIX_SEL_INITIATIVE_TASK_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_TASK_LOCALE,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_LOCALE,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_MILESTONE,
|
||||
HOTFIX_SEL_INITIATIVE_MILESTONE_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE,
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY,
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_THEME,
|
||||
HOTFIX_SEL_HOUSE_THEME_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_THEME_LOCALE,
|
||||
HOTFIX_SEL_HOUSE_ROOM,
|
||||
HOTFIX_SEL_HOUSE_ROOM_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_ROOM_LOCALE,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO_LOCALE,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_DATA,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_DATA_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA,
|
||||
HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA_LOCALE,
|
||||
HOTFIX_SEL_HOUSE_DECOR,
|
||||
HOTFIX_SEL_HOUSE_DECOR_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_DECOR_LOCALE,
|
||||
HOTFIX_SEL_HOUSE_DECOR_THEME_SET,
|
||||
HOTFIX_SEL_HOUSE_DECOR_THEME_SET_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_DECOR_THEME_SET_LOCALE,
|
||||
HOTFIX_SEL_HOUSE_DECOR_MATERIAL,
|
||||
HOTFIX_SEL_HOUSE_DECOR_MATERIAL_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_LOCALE,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE_LOCALE,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT_MAX_ID,
|
||||
HOTFIX_SEL_DYE_COLOR,
|
||||
HOTFIX_SEL_DYE_COLOR_MAX_ID,
|
||||
HOTFIX_SEL_DYE_COLOR_LOCALE,
|
||||
HOTFIX_SEL_DYE_COLOR_CATEGORY,
|
||||
HOTFIX_SEL_DYE_COLOR_CATEGORY_MAX_ID,
|
||||
HOTFIX_SEL_DYE_COLOR_CATEGORY_LOCALE,
|
||||
HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY,
|
||||
HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY_MAX_ID,
|
||||
HOTFIX_SEL_DECOR_SUBCATEGORY,
|
||||
HOTFIX_SEL_DECOR_SUBCATEGORY_MAX_ID,
|
||||
HOTFIX_SEL_DECOR_SUBCATEGORY_LOCALE,
|
||||
HOTFIX_SEL_DECOR_DYE_SLOT,
|
||||
HOTFIX_SEL_DECOR_DYE_SLOT_MAX_ID,
|
||||
HOTFIX_SEL_DECOR_CATEGORY,
|
||||
HOTFIX_SEL_DECOR_CATEGORY_MAX_ID,
|
||||
HOTFIX_SEL_DECOR_CATEGORY_LOCALE,
|
||||
|
||||
HOTFIX_SEL_ACHIEVEMENT_CATEGORY,
|
||||
HOTFIX_SEL_ACHIEVEMENT_CATEGORY_MAX_ID,
|
||||
@@ -342,20 +435,6 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_DATA_TAG_X_HOUSE_DECOR_RECORD,
|
||||
HOTFIX_SEL_DATA_TAG_X_HOUSE_DECOR_RECORD_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_DECOR_CATEGORY,
|
||||
HOTFIX_SEL_DECOR_CATEGORY_MAX_ID,
|
||||
HOTFIX_SEL_DECOR_CATEGORY_LOCALE,
|
||||
|
||||
HOTFIX_SEL_DECOR_DYE_SLOT,
|
||||
HOTFIX_SEL_DECOR_DYE_SLOT_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_DECOR_SUBCATEGORY,
|
||||
HOTFIX_SEL_DECOR_SUBCATEGORY_MAX_ID,
|
||||
HOTFIX_SEL_DECOR_SUBCATEGORY_LOCALE,
|
||||
|
||||
HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY,
|
||||
HOTFIX_SEL_DECOR_X_DECOR_SUBCATEGORY_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_DESTRUCTIBLE_MODEL_DATA,
|
||||
HOTFIX_SEL_DESTRUCTIBLE_MODEL_DATA_MAX_ID,
|
||||
|
||||
@@ -388,28 +467,12 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_EXPECTED_STAT_MOD,
|
||||
HOTFIX_SEL_EXPECTED_STAT_MOD_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_LOCALE,
|
||||
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_EXIT_POINT_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP_X_HOOK_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_HOOK_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE_MAX_ID,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_TYPE_LOCALE,
|
||||
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP,
|
||||
HOTFIX_SEL_EXTERIOR_COMPONENT_X_GROUP_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_FACTION,
|
||||
HOTFIX_SEL_FACTION_MAX_ID,
|
||||
@@ -530,39 +593,6 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_HOUSE,
|
||||
HOTFIX_SEL_HOUSE_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_HOUSE_DECOR,
|
||||
HOTFIX_SEL_HOUSE_DECOR_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_DECOR_LOCALE,
|
||||
|
||||
HOTFIX_SEL_HOUSE_DECOR_MATERIAL,
|
||||
HOTFIX_SEL_HOUSE_DECOR_MATERIAL_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_HOUSE_DECOR_THEME_SET,
|
||||
HOTFIX_SEL_HOUSE_DECOR_THEME_SET_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_DECOR_THEME_SET_LOCALE,
|
||||
|
||||
HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA,
|
||||
HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA_LOCALE,
|
||||
|
||||
HOTFIX_SEL_HOUSE_LEVEL_DATA,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_DATA_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_REWARD_INFO_LOCALE,
|
||||
|
||||
HOTFIX_SEL_HOUSE_ROOM,
|
||||
HOTFIX_SEL_HOUSE_ROOM_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_ROOM_LOCALE,
|
||||
|
||||
HOTFIX_SEL_HOUSE_THEME,
|
||||
HOTFIX_SEL_HOUSE_THEME_MAX_ID,
|
||||
HOTFIX_SEL_HOUSE_THEME_LOCALE,
|
||||
|
||||
HOTFIX_SEL_PERKS_VENDOR_ITEM,
|
||||
HOTFIX_SEL_PERKS_VENDOR_ITEM_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_IMPORT_PRICE_ARMOR,
|
||||
HOTFIX_SEL_IMPORT_PRICE_ARMOR_MAX_ID,
|
||||
|
||||
@@ -575,28 +605,12 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_IMPORT_PRICE_WEAPON,
|
||||
HOTFIX_SEL_IMPORT_PRICE_WEAPON_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE,
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY,
|
||||
HOTFIX_SEL_INITIATIVE_CYCLE_PRIORITY_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_INITIATIVE_MILESTONE,
|
||||
HOTFIX_SEL_INITIATIVE_MILESTONE_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_INITIATIVE_REWARD,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_LOCALE,
|
||||
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE,
|
||||
HOTFIX_SEL_INITIATIVE_REWARD_X_MILESTONE_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_INITIATIVE_TASK,
|
||||
HOTFIX_SEL_INITIATIVE_TASK_MAX_ID,
|
||||
HOTFIX_SEL_INITIATIVE_TASK_LOCALE,
|
||||
|
||||
HOTFIX_SEL_INITIATIVE_X_TASK,
|
||||
HOTFIX_SEL_INITIATIVE_X_TASK_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_ITEM,
|
||||
HOTFIX_SEL_ITEM_MAX_ID,
|
||||
@@ -847,20 +861,10 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_NAMES_RESERVED_LOCALE,
|
||||
HOTFIX_SEL_NAMES_RESERVED_LOCALE_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE_MAX_ID,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_INITIATIVE_LOCALE,
|
||||
|
||||
|
||||
HOTFIX_SEL_NEIGHBORHOOD_MAP,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_MAP_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN_MAX_ID,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_NAME_GEN_LOCALE,
|
||||
|
||||
HOTFIX_SEL_NEIGHBORHOOD_PLOT,
|
||||
HOTFIX_SEL_NEIGHBORHOOD_PLOT_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_NUM_TALENTS_AT_LEVEL,
|
||||
HOTFIX_SEL_NUM_TALENTS_AT_LEVEL_MAX_ID,
|
||||
@@ -978,17 +982,6 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_REWARD_PACK_X_ITEM,
|
||||
HOTFIX_SEL_REWARD_PACK_X_ITEM_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_ROOM_COMPONENT,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_OPTION_TEXTURE_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_TEXTURE,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_TEXTURE_MAX_ID,
|
||||
HOTFIX_SEL_ROOM_COMPONENT_TEXTURE_LOCALE,
|
||||
HOTFIX_SEL_ROOM_WMO_DATA,
|
||||
HOTFIX_SEL_ROOM_WMO_DATA_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_SCENARIO,
|
||||
HOTFIX_SEL_SCENARIO_MAX_ID,
|
||||
@@ -1773,6 +1766,9 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_BATTLE_PET_STATE,
|
||||
HOTFIX_SEL_BATTLE_PET_STATE_MAX_ID,
|
||||
//WowCommunity end
|
||||
HOTFIX_SEL_PERKS_VENDOR_ITEM,
|
||||
HOTFIX_SEL_PERKS_VENDOR_ITEM_MAX_ID,
|
||||
|
||||
MAX_HOTFIXDATABASE_STATEMENTS
|
||||
};
|
||||
|
||||
|
||||
@@ -753,6 +753,13 @@ enum RBACPermissions
|
||||
RBAC_PERM_COMMAND_BG_START = 884,
|
||||
RBAC_PERM_COMMAND_BG_STOP = 885,
|
||||
RBAC_PERM_COMMAND_RELOAD_JUMP_CHARGE_PARAMS = 886,
|
||||
|
||||
RBAC_PERM_COMMAND_HOUSING = 10000,
|
||||
RBAC_PERM_COMMAND_HOUSING_SET_LEVEL = 10001,
|
||||
RBAC_PERM_COMMAND_HOUSING_DELETE = 10002,
|
||||
RBAC_PERM_COMMAND_HOUSING_CHARTER_CREATE = 10003,
|
||||
RBAC_PERM_COMMAND_HOUSING_CHARTER_TYPE = 10004,
|
||||
RBAC_PERM_COMMAND_HOUSING_CHARTER_DELETE = 10005,
|
||||
//
|
||||
// IF YOU ADD NEW PERMISSIONS, ADD THEM IN 3.3.5 BRANCH AS WELL!
|
||||
//
|
||||
@@ -760,7 +767,7 @@ enum RBACPermissions
|
||||
RBAC_PERM_USE_COMMENTATOR_MODE = 1000,
|
||||
RBAC_PERM_COMMAND_RELOAD_SHOP_CATALOG = 1001,
|
||||
RBAC_PERM_COMMAND_SHOP = 1002,
|
||||
RBAC_PERM_MAX
|
||||
RBAC_PERM_MAX = 10006
|
||||
};
|
||||
|
||||
enum RBACCommandResult
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "Housing.h"
|
||||
#include "InitiativeManager.h"
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
@@ -514,8 +516,8 @@ void CriteriaHandler::UpdateCriteria(Criteria const* criteria, uint64 miscValue1
|
||||
return;
|
||||
|
||||
// Housing initiative hook: notify InitiativeManager when validated criteria fires
|
||||
// if (referencePlayer)
|
||||
//sInitiativeManager.OnCriteriaProgress(referencePlayer, criteria->ID);
|
||||
if (referencePlayer)
|
||||
sInitiativeManager.OnCriteriaProgress(referencePlayer, criteria->ID);
|
||||
|
||||
switch (CriteriaType(criteria->Entry->Type))
|
||||
{
|
||||
@@ -1863,6 +1865,10 @@ bool CriteriaHandler::RequirementsSatisfied(Criteria const* criteria, uint64 mis
|
||||
case CriteriaType::SocketAnySoulbindConduit:
|
||||
// No asset - the ModifierTree does the discriminating, but it needs a real subject id in
|
||||
// miscValue1 (GarrFollower / GarrTalent / SoulbindConduit / GarrBuilding).
|
||||
// No asset - the ModifierTree discriminates, but it needs a real HouseDecor entry in miscValue1.
|
||||
case CriteriaType::PlaceDecor:
|
||||
case CriteriaType::RemoveDecor:
|
||||
case CriteriaType::CollectUniqueDecor:
|
||||
if (!miscValue1)
|
||||
return false;
|
||||
break;
|
||||
@@ -4520,6 +4526,10 @@ bool CriteriaHandler::ModifierSatisfied(ModifierTreeEntry const* modifier, uint6
|
||||
if (!referencePlayer->GetGuildId())
|
||||
return false;
|
||||
break;
|
||||
case ModifierTreeType::PlayerHousesCountEqualOrGreaterThan: // 419
|
||||
if (referencePlayer->GetAllHousings().size() < reqValue)
|
||||
return false;
|
||||
break;
|
||||
case ModifierTreeType::PlayerMoneyIsRelOp: // 417
|
||||
switch (reqValue)
|
||||
{
|
||||
@@ -5514,3 +5524,7 @@ std::span<CriteriaType const> CriteriaMgr::GetRetroactivelyUpdateableCriteriaTyp
|
||||
|
||||
return Types;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2044,22 +2044,22 @@ struct ExteriorComponentLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[16] =
|
||||
{
|
||||
{.IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{.IsSigned = false, .Type = FT_FLOAT, .Name = "Position1" },
|
||||
{.IsSigned = false, .Type = FT_FLOAT, .Name = "Position2" },
|
||||
{.IsSigned = false, .Type = FT_FLOAT, .Name = "Position3" },
|
||||
{.IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{.IsSigned = false, .Type = FT_BYTE, .Name = "Size" },
|
||||
{.IsSigned = false, .Type = FT_INT, .Name = "HouseExteriorWmoDataID" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "ParentComponentID" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "ModelFileDataID" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "Flags" },
|
||||
{.IsSigned = false, .Type = FT_BYTE, .Name = "Field_12_0_0_63534_007" },
|
||||
{.IsSigned = false, .Type = FT_BYTE, .Name = "Type" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "Field_12_0_0_63534_009" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "GameObjectID" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "Field_11_2_7_64044_011" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "ItemID" },
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionX" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionY" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionZ" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_BYTE, .Name = "Size" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "HouseExteriorWmoDataID" }, // ParentIndexField - must be unsigned
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "ParentComponentID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "ModelFileDataID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Flags" },
|
||||
{ .IsSigned = false, .Type = FT_BYTE, .Name = "Field_7" },
|
||||
{ .IsSigned = false, .Type = FT_BYTE, .Name = "Type" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_9" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "GameObjectID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_11" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "ItemID" }, // references Item.ID
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 16, &ExteriorComponentMeta::Instance, HOTFIX_SEL_EXTERIOR_COMPONENT };
|
||||
@@ -2067,7 +2067,6 @@ struct ExteriorComponentLoadInfo
|
||||
|
||||
struct ExteriorComponentExitPointLoadInfo
|
||||
{
|
||||
// Field order must match metadata (IndexField=2, ID is 3rd meta field)
|
||||
static constexpr DB2FieldMeta Fields[8] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionX" },
|
||||
@@ -2085,14 +2084,13 @@ struct ExteriorComponentExitPointLoadInfo
|
||||
|
||||
struct ExteriorComponentGroupLoadInfo
|
||||
{
|
||||
// Field order must match metadata (IndexField=1, ID is 2nd meta field)
|
||||
static constexpr DB2FieldMeta Fields[5] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionX" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionY" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionZ" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "HouseExteriorWmoDataID" }, // ParentIndexField - references HouseExteriorWmoData
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "HouseExteriorWmoDataID" }, // ParentIndexField
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 5, &ExteriorComponentGroupMeta::Instance, HOTFIX_SEL_EXTERIOR_COMPONENT_GROUP };
|
||||
@@ -2112,7 +2110,6 @@ struct ExteriorComponentGroupXHookLoadInfo
|
||||
|
||||
struct ExteriorComponentHookLoadInfo
|
||||
{
|
||||
// Field order must match metadata (IndexField=2, ID is 3rd meta field)
|
||||
static constexpr DB2FieldMeta Fields[9] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "PositionX" },
|
||||
@@ -2131,7 +2128,6 @@ struct ExteriorComponentHookLoadInfo
|
||||
|
||||
struct ExteriorComponentTypeLoadInfo
|
||||
{
|
||||
// Field order must match metadata (IndexField=1, ID is 2nd meta field)
|
||||
static constexpr DB2FieldMeta Fields[3] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
@@ -2946,14 +2942,13 @@ struct HouseLoadInfo
|
||||
|
||||
struct HouseDecorLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[20] =
|
||||
static constexpr DB2FieldMeta Fields[19] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "InitialRotationX" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "InitialRotationY" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "InitialRotationZ" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_003" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "GameObjectID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Flags" },
|
||||
{ .IsSigned = false, .Type = FT_BYTE, .Name = "Type" },
|
||||
@@ -2970,7 +2965,7 @@ struct HouseDecorLoadInfo
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "UiModelSceneID" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 20, &HouseDecorMeta::Instance, HOTFIX_SEL_HOUSE_DECOR };
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 19, &HouseDecorMeta::Instance, HOTFIX_SEL_HOUSE_DECOR };
|
||||
};
|
||||
|
||||
struct HouseDecorMaterialLoadInfo
|
||||
@@ -3003,15 +2998,13 @@ struct HouseDecorThemeSetLoadInfo
|
||||
|
||||
struct HouseExteriorWmoDataLoadInfo
|
||||
{
|
||||
// 12.0.5.66330 added Field_003 + Field_004 (both int32). WoWDBDefs layout 0x95E7A088
|
||||
// shows them as Field_12_0_5_66330_003 / _004 with no confirmed semantic name yet.
|
||||
static constexpr DB2FieldMeta Fields[5] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Flags" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_003" }, // NEW in 12.0.5.66330
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_004" }, // NEW in 12.0.5.66330
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_003" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_004" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 5, &HouseExteriorWmoDataMeta::Instance, HOTFIX_SEL_HOUSE_EXTERIOR_WMO_DATA };
|
||||
@@ -3024,7 +3017,7 @@ struct HouseLevelDataLoadInfo
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Level" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "QuestID" },
|
||||
{.IsSigned = true, .Type = FT_INT, .Name = "Field_12_0_7_67808_003" }, // 12.0.7 (build 67808): new field
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_12_0_7_67808_003" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 4, &HouseLevelDataMeta::Instance, HOTFIX_SEL_HOUSE_LEVEL_DATA };
|
||||
@@ -3047,22 +3040,21 @@ struct HouseLevelRewardInfoLoadInfo
|
||||
|
||||
struct HouseRoomLoadInfo
|
||||
{
|
||||
// 12.0.5.66330 added Field_007 (int32). WoWDBDefs layout 0xFC6C2118 shows it as
|
||||
// Field_12_0_5_66330_007 with no confirmed semantic name yet.
|
||||
static constexpr DB2FieldMeta Fields[9] =
|
||||
static constexpr DB2FieldMeta Fields[10] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = true, .Type = FT_BYTE, .Name = "Size" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Flags" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_002" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "RoomWmoDataID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "UiTextureAtlasElementID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "WeightCost" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_007" }, // NEW in 12.0.5.66330
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "ItemID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "SortPriority" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 9, &HouseRoomMeta::Instance, HOTFIX_SEL_HOUSE_ROOM };
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 10, &HouseRoomMeta::Instance, HOTFIX_SEL_HOUSE_ROOM };
|
||||
};
|
||||
|
||||
struct HouseThemeLoadInfo
|
||||
@@ -9633,4 +9625,50 @@ struct BattlePetStateLoadInfo
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 4, &BattlePetStateMeta::Instance, HOTFIX_SEL_BATTLE_PET_STATE };
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct RoomGridLineLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[7] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "OffsetX" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "OffsetY" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "OffsetZ" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "SizeX" },
|
||||
{ .IsSigned = false, .Type = FT_FLOAT, .Name = "SizeY" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "RoomWmoDataID" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 7, &RoomGridLineMeta::Instance, HOTFIX_SEL_ROOM_GRID_LINE };
|
||||
};
|
||||
|
||||
struct DyeColorLoadInfo
|
||||
{
|
||||
// DyeColorCategoryID is the ParentIndexField; must be unsigned or DB2 loading is aborted
|
||||
static constexpr DB2FieldMeta Fields[8] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "DyeColorCategoryID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "GradientTextureIndex" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "ItemID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "SwatchColorStart" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "SwatchColorEnd" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "SortOrder" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 8, &DyeColorMeta::Instance, HOTFIX_SEL_DYE_COLOR };
|
||||
};
|
||||
|
||||
struct DyeColorCategoryLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[2] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 2, &DyeColorCategoryMeta::Instance, HOTFIX_SEL_DYE_COLOR_CATEGORY };
|
||||
};
|
||||
#endif // TRINITYCORE_DB2_LOAD_INFO_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,45 @@
|
||||
#include <type_traits>
|
||||
//WowCommunity
|
||||
|
||||
DB2Storage<RoomWmoDataEntry> sRoomWmoDataStore("RoomWmoData.db2", &RoomWmoDataLoadInfo::Instance);
|
||||
DB2Storage<RoomGridLineEntry> sRoomGridLineStore("RoomGridLine.db2", &RoomGridLineLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentTextureEntry> sRoomComponentTextureStore("RoomComponentTexture.db2", &RoomComponentTextureLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentEntry> sRoomComponentStore("RoomComponent.db2", &RoomComponentLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentOptionTextureEntry> sRoomComponentOptionTextureStore("RoomComponentOptionTexture.db2", &RoomComponentOptionTextureLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentOptionEntry> sRoomComponentOptionStore("RoomComponentOption.db2", &RoomComponentOptionLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodPlotEntry> sNeighborhoodPlotStore("NeighborhoodPlot.db2", &NeighborhoodPlotLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodNameGenEntry> sNeighborhoodNameGenStore("NeighborhoodNameGen.db2", &NeighborhoodNameGenLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodMapEntry> sNeighborhoodMapStore("NeighborhoodMap.db2", &NeighborhoodMapLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodInitiativeEntry> sNeighborhoodInitiativeStore("NeighborhoodInitiative.db2", &NeighborhoodInitiativeLoadInfo::Instance);
|
||||
DB2Storage<InitiativeXTaskEntry> sInitiativeXTaskStore("InitiativeXTask.db2", &InitiativeXTaskLoadInfo::Instance);
|
||||
DB2Storage<InitiativeTaskEntry> sInitiativeTaskStore("InitiativeTask.db2", &InitiativeTaskLoadInfo::Instance);
|
||||
DB2Storage<InitiativeRewardXMilestoneEntry> sInitiativeRewardXMilestoneStore("InitiativeRewardXMilestone.db2", &InitiativeRewardXMilestoneLoadInfo::Instance);
|
||||
DB2Storage<InitiativeRewardEntry> sInitiativeRewardStore("InitiativeReward.db2", &InitiativeRewardLoadInfo::Instance);
|
||||
DB2Storage<InitiativeMilestoneEntry> sInitiativeMilestoneStore("InitiativeMilestone.db2", &InitiativeMilestoneLoadInfo::Instance);
|
||||
DB2Storage<InitiativeCycleEntry> sInitiativeCycleStore("InitiativeCycle.db2", &InitiativeCycleLoadInfo::Instance);
|
||||
DB2Storage<InitiativeCyclePriorityEntry> sInitiativeCyclePriorityStore("InitiativeCyclePriority.db2", &InitiativeCyclePriorityLoadInfo::Instance);
|
||||
DB2Storage<HouseThemeEntry> sHouseThemeStore("HouseTheme.db2", &HouseThemeLoadInfo::Instance);
|
||||
DB2Storage<HouseRoomEntry> sHouseRoomStore("HouseRoom.db2", &HouseRoomLoadInfo::Instance);
|
||||
DB2Storage<HouseLevelRewardInfoEntry> sHouseLevelRewardInfoStore("HouseLevelRewardInfo.db2", &HouseLevelRewardInfoLoadInfo::Instance);
|
||||
DB2Storage<HouseLevelDataEntry> sHouseLevelDataStore("HouseLevelData.db2", &HouseLevelDataLoadInfo::Instance);
|
||||
DB2Storage<HouseExteriorWmoDataEntry> sHouseExteriorWmoDataStore("HouseExteriorWmoData.db2", &HouseExteriorWmoDataLoadInfo::Instance);
|
||||
DB2Storage<HouseDecorThemeSetEntry> sHouseDecorThemeSetStore("HouseDecorThemeSet.db2", &HouseDecorThemeSetLoadInfo::Instance);
|
||||
DB2Storage<HouseDecorEntry> sHouseDecorStore("HouseDecor.db2", &HouseDecorLoadInfo::Instance);
|
||||
DB2Storage<HouseDecorMaterialEntry> sHouseDecorMaterialStore("HouseDecorMaterial.db2", &HouseDecorMaterialLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentXGroupEntry> sExteriorComponentXGroupStore("ExteriorComponentXGroup.db2", &ExteriorComponentXGroupLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentTypeEntry> sExteriorComponentTypeStore("ExteriorComponentType.db2", &ExteriorComponentTypeLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentEntry> sExteriorComponentStore("ExteriorComponent.db2", &ExteriorComponentLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentHookEntry> sExteriorComponentHookStore("ExteriorComponentHook.db2", &ExteriorComponentHookLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentGroupXHookEntry> sExteriorComponentGroupXHookStore("ExteriorComponentGroupXHook.db2", &ExteriorComponentGroupXHookLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentGroupEntry> sExteriorComponentGroupStore("ExteriorComponentGroup.db2", &ExteriorComponentGroupLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentExitPointEntry> sExteriorComponentExitPointStore("ExteriorComponentExitPoint.db2", &ExteriorComponentExitPointLoadInfo::Instance);
|
||||
DB2Storage<DyeColorEntry> sDyeColorStore("DyeColor.db2", &DyeColorLoadInfo::Instance);
|
||||
DB2Storage<DecorXDecorSubcategoryEntry> sDecorXDecorSubcategoryStore("DecorXDecorSubcategory.db2", &DecorXDecorSubcategoryLoadInfo::Instance);
|
||||
DB2Storage<DecorSubcategoryEntry> sDecorSubcategoryStore("DecorSubcategory.db2", &DecorSubcategoryLoadInfo::Instance);
|
||||
DB2Storage<DecorDyeSlotEntry> sDecorDyeSlotStore("DecorDyeSlot.db2", &DecorDyeSlotLoadInfo::Instance);
|
||||
DB2Storage<DecorCategoryEntry> sDecorCategoryStore("DecorCategory.db2", &DecorCategoryLoadInfo::Instance);
|
||||
DB2Storage<DataTagXHouseDecorRecordEntry> sDataTagXHouseDecorRecordStore("DataTagXHouseDecorRecord.db2", &DataTagXHouseDecorRecordLoadInfo::Instance);
|
||||
DB2Storage<DyeColorCategoryEntry> sDyeColorCategoryStore("DyeColorCategory.db2", &DyeColorCategoryLoadInfo::Instance);
|
||||
DB2Storage<AchievementEntry> sAchievementStore("Achievement.db2", &AchievementLoadInfo::Instance);
|
||||
DB2Storage<Achievement_CategoryEntry> sAchievementCategoryStore("Achievement_Category.db2", &AchievementCategoryLoadInfo::Instance);
|
||||
DB2Storage<AdventureJournalEntry> sAdventureJournalStore("AdventureJournal.db2", &AdventureJournalLoadInfo::Instance);
|
||||
@@ -147,11 +186,6 @@ DB2Storage<CurrencyContainerEntry> sCurrencyContainerStore("Currenc
|
||||
DB2Storage<CurrencyTypesEntry> sCurrencyTypesStore("CurrencyTypes.db2", &CurrencyTypesLoadInfo::Instance);
|
||||
DB2Storage<CurveEntry> sCurveStore("Curve.db2", &CurveLoadInfo::Instance);
|
||||
DB2Storage<CurvePointEntry> sCurvePointStore("CurvePoint.db2", &CurvePointLoadInfo::Instance);
|
||||
DB2Storage<DataTagXHouseDecorRecordEntry> sDataTagXHouseDecorRecordStore("DataTagXHouseDecorRecord.db2", &DataTagXHouseDecorRecordLoadInfo::Instance);
|
||||
DB2Storage<DecorCategoryEntry> sDecorCategoryStore("DecorCategory.db2", &DecorCategoryLoadInfo::Instance);
|
||||
DB2Storage<DecorDyeSlotEntry> sDecorDyeSlotStore("DecorDyeSlot.db2", &DecorDyeSlotLoadInfo::Instance);
|
||||
DB2Storage<DecorSubcategoryEntry> sDecorSubcategoryStore("DecorSubcategory.db2", &DecorSubcategoryLoadInfo::Instance);
|
||||
DB2Storage<DecorXDecorSubcategoryEntry> sDecorXDecorSubcategoryStore("DecorXDecorSubcategory.db2", &DecorXDecorSubcategoryLoadInfo::Instance);
|
||||
DB2Storage<DestructibleModelDataEntry> sDestructibleModelDataStore("DestructibleModelData.db2", &DestructibleModelDataLoadInfo::Instance);
|
||||
DB2Storage<DifficultyEntry> sDifficultyStore("Difficulty.db2", &DifficultyLoadInfo::Instance);
|
||||
DB2Storage<DungeonEncounterEntry> sDungeonEncounterStore("DungeonEncounter.db2", &DungeonEncounterLoadInfo::Instance);
|
||||
@@ -162,13 +196,6 @@ DB2Storage<EmotesTextEntry> sEmotesTextStore("EmotesText.db2
|
||||
DB2Storage<EmotesTextSoundEntry> sEmotesTextSoundStore("EmotesTextSound.db2", &EmotesTextSoundLoadInfo::Instance);
|
||||
DB2Storage<ExpectedStatEntry> sExpectedStatStore("ExpectedStat.db2", &ExpectedStatLoadInfo::Instance);
|
||||
DB2Storage<ExpectedStatModEntry> sExpectedStatModStore("ExpectedStatMod.db2", &ExpectedStatModLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentEntry> sExteriorComponentStore("ExteriorComponent.db2", &ExteriorComponentLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentExitPointEntry> sExteriorComponentExitPointStore("ExteriorComponentExitPoint.db2", &ExteriorComponentExitPointLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentGroupEntry> sExteriorComponentGroupStore("ExteriorComponentGroup.db2", &ExteriorComponentGroupLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentGroupXHookEntry> sExteriorComponentGroupXHookStore("ExteriorComponentGroupXHook.db2", &ExteriorComponentGroupXHookLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentHookEntry> sExteriorComponentHookStore("ExteriorComponentHook.db2", &ExteriorComponentHookLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentTypeEntry> sExteriorComponentTypeStore("ExteriorComponentType.db2", &ExteriorComponentTypeLoadInfo::Instance);
|
||||
DB2Storage<ExteriorComponentXGroupEntry> sExteriorComponentXGroupStore("ExteriorComponentXGroup.db2", &ExteriorComponentXGroupLoadInfo::Instance);
|
||||
DB2Storage<FactionEntry> sFactionStore("Faction.db2", &FactionLoadInfo::Instance);
|
||||
DB2Storage<FactionTemplateEntry> sFactionTemplateStore("FactionTemplate.db2", &FactionTemplateLoadInfo::Instance);
|
||||
DB2Storage<FlightCapabilityEntry> sFlightCapabilityStore("FlightCapability.db2", &FlightCapabilityLoadInfo::Instance);
|
||||
@@ -247,25 +274,10 @@ DB2Storage<GuildPerkSpellsEntry> sGuildPerkSpellsStore("GuildPerk
|
||||
DB2Storage<HeirloomEntry> sHeirloomStore("Heirloom.db2", &HeirloomLoadInfo::Instance);
|
||||
DB2Storage<HolidaysEntry> sHolidaysStore("Holidays.db2", &HolidaysLoadInfo::Instance);
|
||||
DB2Storage<HouseEntry> sHouseStore("House.db2", &HouseLoadInfo::Instance);
|
||||
DB2Storage<HouseDecorEntry> sHouseDecorStore("HouseDecor.db2", &HouseDecorLoadInfo::Instance);
|
||||
DB2Storage<HouseDecorMaterialEntry> sHouseDecorMaterialStore("HouseDecorMaterial.db2", &HouseDecorMaterialLoadInfo::Instance);
|
||||
DB2Storage<HouseDecorThemeSetEntry> sHouseDecorThemeSetStore("HouseDecorThemeSet.db2", &HouseDecorThemeSetLoadInfo::Instance);
|
||||
DB2Storage<HouseExteriorWmoDataEntry> sHouseExteriorWmoDataStore("HouseExteriorWmoData.db2", &HouseExteriorWmoDataLoadInfo::Instance);
|
||||
DB2Storage<HouseLevelDataEntry> sHouseLevelDataStore("HouseLevelData.db2", &HouseLevelDataLoadInfo::Instance);
|
||||
DB2Storage<HouseLevelRewardInfoEntry> sHouseLevelRewardInfoStore("HouseLevelRewardInfo.db2", &HouseLevelRewardInfoLoadInfo::Instance);
|
||||
DB2Storage<HouseRoomEntry> sHouseRoomStore("HouseRoom.db2", &HouseRoomLoadInfo::Instance);
|
||||
DB2Storage<HouseThemeEntry> sHouseThemeStore("HouseTheme.db2", &HouseThemeLoadInfo::Instance);
|
||||
DB2Storage<ImportPriceArmorEntry> sImportPriceArmorStore("ImportPriceArmor.db2", &ImportPriceArmorLoadInfo::Instance);
|
||||
DB2Storage<ImportPriceQualityEntry> sImportPriceQualityStore("ImportPriceQuality.db2", &ImportPriceQualityLoadInfo::Instance);
|
||||
DB2Storage<ImportPriceShieldEntry> sImportPriceShieldStore("ImportPriceShield.db2", &ImportPriceShieldLoadInfo::Instance);
|
||||
DB2Storage<ImportPriceWeaponEntry> sImportPriceWeaponStore("ImportPriceWeapon.db2", &ImportPriceWeaponLoadInfo::Instance);
|
||||
DB2Storage<InitiativeCycleEntry> sInitiativeCycleStore("InitiativeCycle.db2", &InitiativeCycleLoadInfo::Instance);
|
||||
DB2Storage<InitiativeCyclePriorityEntry> sInitiativeCyclePriorityStore("InitiativeCyclePriority.db2", &InitiativeCyclePriorityLoadInfo::Instance);
|
||||
DB2Storage<InitiativeMilestoneEntry> sInitiativeMilestoneStore("InitiativeMilestone.db2", &InitiativeMilestoneLoadInfo::Instance);
|
||||
DB2Storage<InitiativeRewardEntry> sInitiativeRewardStore("InitiativeReward.db2", &InitiativeRewardLoadInfo::Instance);
|
||||
DB2Storage<InitiativeRewardXMilestoneEntry> sInitiativeRewardXMilestoneStore("InitiativeRewardXMilestone.db2", &InitiativeRewardXMilestoneLoadInfo::Instance);
|
||||
DB2Storage<InitiativeTaskEntry> sInitiativeTaskStore("InitiativeTask.db2", &InitiativeTaskLoadInfo::Instance);
|
||||
DB2Storage<InitiativeXTaskEntry> sInitiativeXTaskStore("InitiativeXTask.db2", &InitiativeXTaskLoadInfo::Instance);
|
||||
DB2Storage<ItemAppearanceEntry> sItemAppearanceStore("ItemAppearance.db2", &ItemAppearanceLoadInfo::Instance);
|
||||
DB2Storage<ItemArmorQualityEntry> sItemArmorQualityStore("ItemArmorQuality.db2", &ItemArmorQualityLoadInfo::Instance);
|
||||
DB2Storage<ItemArmorShieldEntry> sItemArmorShieldStore("ItemArmorShield.db2", &ItemArmorShieldLoadInfo::Instance);
|
||||
@@ -343,10 +355,6 @@ DB2Storage<NameGenEntry> sNameGenStore("NameGen.db2", &Na
|
||||
DB2Storage<NamesProfanityEntry> sNamesProfanityStore("NamesProfanity.db2", &NamesProfanityLoadInfo::Instance);
|
||||
DB2Storage<NamesReservedEntry> sNamesReservedStore("NamesReserved.db2", &NamesReservedLoadInfo::Instance);
|
||||
DB2Storage<NamesReservedLocaleEntry> sNamesReservedLocaleStore("NamesReservedLocale.db2", &NamesReservedLocaleLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodInitiativeEntry> sNeighborhoodInitiativeStore("NeighborhoodInitiative.db2", &NeighborhoodInitiativeLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodMapEntry> sNeighborhoodMapStore("NeighborhoodMap.db2", &NeighborhoodMapLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodNameGenEntry> sNeighborhoodNameGenStore("NeighborhoodNameGen.db2", &NeighborhoodNameGenLoadInfo::Instance);
|
||||
DB2Storage<NeighborhoodPlotEntry> sNeighborhoodPlotStore("NeighborhoodPlot.db2", &NeighborhoodPlotLoadInfo::Instance);
|
||||
DB2Storage<NumTalentsAtLevelEntry> sNumTalentsAtLevelStore("NumTalentsAtLevel.db2", &NumTalentsAtLevelLoadInfo::Instance);
|
||||
DB2Storage<OverrideSpellDataEntry> sOverrideSpellDataStore("OverrideSpellData.db2", &OverrideSpellDataLoadInfo::Instance);
|
||||
DB2Storage<ParagonReputationEntry> sParagonReputationStore("ParagonReputation.db2", &ParagonReputationLoadInfo::Instance);
|
||||
@@ -385,11 +393,6 @@ DB2Storage<RandPropPointsEntry> sRandPropPointsStore("RandPropPo
|
||||
DB2Storage<RewardPackEntry> sRewardPackStore("RewardPack.db2", &RewardPackLoadInfo::Instance);
|
||||
DB2Storage<RewardPackXCurrencyTypeEntry> sRewardPackXCurrencyTypeStore("RewardPackXCurrencyType.db2", &RewardPackXCurrencyTypeLoadInfo::Instance);
|
||||
DB2Storage<RewardPackXItemEntry> sRewardPackXItemStore("RewardPackXItem.db2", &RewardPackXItemLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentEntry> sRoomComponentStore("RoomComponent.db2", &RoomComponentLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentOptionEntry> sRoomComponentOptionStore("RoomComponentOption.db2", &RoomComponentOptionLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentOptionTextureEntry> sRoomComponentOptionTextureStore("RoomComponentOptionTexture.db2", &RoomComponentOptionTextureLoadInfo::Instance);
|
||||
DB2Storage<RoomComponentTextureEntry> sRoomComponentTextureStore("RoomComponentTexture.db2", &RoomComponentTextureLoadInfo::Instance);
|
||||
DB2Storage<RoomWmoDataEntry> sRoomWmoDataStore("RoomWmoData.db2", &RoomWmoDataLoadInfo::Instance);
|
||||
DB2Storage<ScenarioEntry> sScenarioStore("Scenario.db2", &ScenarioLoadInfo::Instance);
|
||||
DB2Storage<ScenarioStepEntry> sScenarioStepStore("ScenarioStep.db2", &ScenarioStepLoadInfo::Instance);
|
||||
DB2Storage<SceneScriptEntry> sSceneScriptStore("SceneScript.db2", &SceneScriptLoadInfo::Instance);
|
||||
@@ -1277,6 +1280,8 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
|
||||
LOAD_DB2(sDataTagXHouseDecorRecordStore);
|
||||
LOAD_DB2(sDecorCategoryStore);
|
||||
LOAD_DB2(sDecorDyeSlotStore);
|
||||
LOAD_DB2(sDyeColorStore);
|
||||
LOAD_DB2(sDyeColorCategoryStore);
|
||||
LOAD_DB2(sDecorSubcategoryStore);
|
||||
LOAD_DB2(sDecorXDecorSubcategoryStore);
|
||||
LOAD_DB2(sDestructibleModelDataStore);
|
||||
@@ -1374,13 +1379,13 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
|
||||
LOAD_DB2(sHeirloomStore);
|
||||
LOAD_DB2(sHolidaysStore);
|
||||
LOAD_DB2(sHouseStore);
|
||||
// LOAD_DB2(sHouseDecorStore);todo update
|
||||
LOAD_DB2(sHouseDecorStore);
|
||||
LOAD_DB2(sHouseDecorMaterialStore);
|
||||
LOAD_DB2(sHouseDecorThemeSetStore);
|
||||
LOAD_DB2(sHouseExteriorWmoDataStore);
|
||||
// LOAD_DB2(sHouseLevelDataStore); todo update 12.0.7
|
||||
LOAD_DB2(sHouseLevelDataStore);
|
||||
LOAD_DB2(sHouseLevelRewardInfoStore);
|
||||
// LOAD_DB2(sHouseRoomStore);todo update
|
||||
LOAD_DB2(sHouseRoomStore);
|
||||
LOAD_DB2(sHouseThemeStore);
|
||||
LOAD_DB2(sImportPriceArmorStore);
|
||||
LOAD_DB2(sImportPriceQualityStore);
|
||||
@@ -1516,6 +1521,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
|
||||
LOAD_DB2(sRoomComponentOptionStore);
|
||||
LOAD_DB2(sRoomComponentOptionTextureStore);
|
||||
LOAD_DB2(sRoomComponentTextureStore);
|
||||
LOAD_DB2(sRoomGridLineStore);
|
||||
LOAD_DB2(sRoomWmoDataStore);
|
||||
LOAD_DB2(sScenarioStore);
|
||||
LOAD_DB2(sScenarioStepStore);
|
||||
@@ -4392,3 +4398,7 @@ std::vector<BountyEntry const*> const* DB2Manager::GetBountiesForBountySet(int32
|
||||
{
|
||||
return Trinity::Containers::MapGetValuePtr(_bountiesByBountySet, bountySetId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,45 @@
|
||||
|
||||
class DB2HotfixGeneratorBase;
|
||||
|
||||
TC_GAME_API extern DB2Storage<DataTagXHouseDecorRecordEntry> sDataTagXHouseDecorRecordStore;
|
||||
TC_GAME_API extern DB2Storage<DecorCategoryEntry> sDecorCategoryStore;
|
||||
TC_GAME_API extern DB2Storage<DecorDyeSlotEntry> sDecorDyeSlotStore;
|
||||
TC_GAME_API extern DB2Storage<DyeColorEntry> sDyeColorStore;
|
||||
TC_GAME_API extern DB2Storage<DyeColorCategoryEntry> sDyeColorCategoryStore;
|
||||
TC_GAME_API extern DB2Storage<DecorSubcategoryEntry> sDecorSubcategoryStore;
|
||||
TC_GAME_API extern DB2Storage<DecorXDecorSubcategoryEntry> sDecorXDecorSubcategoryStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentEntry> sExteriorComponentStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentExitPointEntry> sExteriorComponentExitPointStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentGroupEntry> sExteriorComponentGroupStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentGroupXHookEntry> sExteriorComponentGroupXHookStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentHookEntry> sExteriorComponentHookStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentTypeEntry> sExteriorComponentTypeStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentXGroupEntry> sExteriorComponentXGroupStore;
|
||||
TC_GAME_API extern DB2Storage<HouseDecorEntry> sHouseDecorStore;
|
||||
TC_GAME_API extern DB2Storage<HouseDecorMaterialEntry> sHouseDecorMaterialStore;
|
||||
TC_GAME_API extern DB2Storage<HouseDecorThemeSetEntry> sHouseDecorThemeSetStore;
|
||||
TC_GAME_API extern DB2Storage<HouseExteriorWmoDataEntry> sHouseExteriorWmoDataStore;
|
||||
TC_GAME_API extern DB2Storage<HouseLevelDataEntry> sHouseLevelDataStore;
|
||||
TC_GAME_API extern DB2Storage<HouseLevelRewardInfoEntry> sHouseLevelRewardInfoStore;
|
||||
TC_GAME_API extern DB2Storage<HouseRoomEntry> sHouseRoomStore;
|
||||
TC_GAME_API extern DB2Storage<HouseThemeEntry> sHouseThemeStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeCycleEntry> sInitiativeCycleStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeCyclePriorityEntry> sInitiativeCyclePriorityStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeMilestoneEntry> sInitiativeMilestoneStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeRewardEntry> sInitiativeRewardStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeRewardXMilestoneEntry> sInitiativeRewardXMilestoneStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeTaskEntry> sInitiativeTaskStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeXTaskEntry> sInitiativeXTaskStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodInitiativeEntry> sNeighborhoodInitiativeStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodMapEntry> sNeighborhoodMapStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodNameGenEntry> sNeighborhoodNameGenStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodPlotEntry> sNeighborhoodPlotStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentEntry> sRoomComponentStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentOptionEntry> sRoomComponentOptionStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentOptionTextureEntry> sRoomComponentOptionTextureStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentTextureEntry> sRoomComponentTextureStore;
|
||||
TC_GAME_API extern DB2Storage<RoomGridLineEntry> sRoomGridLineStore;
|
||||
TC_GAME_API extern DB2Storage<RoomWmoDataEntry> sRoomWmoDataStore;
|
||||
TC_GAME_API extern DB2Storage<AchievementEntry> sAchievementStore;
|
||||
TC_GAME_API extern DB2Storage<Achievement_CategoryEntry> sAchievementCategoryStore;
|
||||
TC_GAME_API extern DB2Storage<AdventureJournalEntry> sAdventureJournalStore;
|
||||
@@ -108,11 +147,6 @@ TC_GAME_API extern DB2Storage<CriteriaEntry> sCriteriaSto
|
||||
TC_GAME_API extern DB2Storage<CriteriaTreeEntry> sCriteriaTreeStore;
|
||||
TC_GAME_API extern DB2Storage<CurrencyTypesEntry> sCurrencyTypesStore;
|
||||
TC_GAME_API extern DB2Storage<CurveEntry> sCurveStore;
|
||||
TC_GAME_API extern DB2Storage<DataTagXHouseDecorRecordEntry> sDataTagXHouseDecorRecordStore;
|
||||
TC_GAME_API extern DB2Storage<DecorCategoryEntry> sDecorCategoryStore;
|
||||
TC_GAME_API extern DB2Storage<DecorDyeSlotEntry> sDecorDyeSlotStore;
|
||||
TC_GAME_API extern DB2Storage<DecorSubcategoryEntry> sDecorSubcategoryStore;
|
||||
TC_GAME_API extern DB2Storage<DecorXDecorSubcategoryEntry> sDecorXDecorSubcategoryStore;
|
||||
TC_GAME_API extern DB2Storage<DestructibleModelDataEntry> sDestructibleModelDataStore;
|
||||
TC_GAME_API extern DB2Storage<DifficultyEntry> sDifficultyStore;
|
||||
TC_GAME_API extern DB2Storage<DungeonEncounterEntry> sDungeonEncounterStore;
|
||||
@@ -120,13 +154,6 @@ TC_GAME_API extern DB2Storage<DurabilityCostsEntry> sDurabilityC
|
||||
TC_GAME_API extern DB2Storage<DurabilityQualityEntry> sDurabilityQualityStore;
|
||||
TC_GAME_API extern DB2Storage<EmotesEntry> sEmotesStore;
|
||||
TC_GAME_API extern DB2Storage<EmotesTextEntry> sEmotesTextStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentEntry> sExteriorComponentStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentExitPointEntry> sExteriorComponentExitPointStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentGroupEntry> sExteriorComponentGroupStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentGroupXHookEntry> sExteriorComponentGroupXHookStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentHookEntry> sExteriorComponentHookStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentTypeEntry> sExteriorComponentTypeStore;
|
||||
TC_GAME_API extern DB2Storage<ExteriorComponentXGroupEntry> sExteriorComponentXGroupStore;
|
||||
TC_GAME_API extern DB2Storage<FactionEntry> sFactionStore;
|
||||
TC_GAME_API extern DB2Storage<FactionTemplateEntry> sFactionTemplateStore;
|
||||
TC_GAME_API extern DB2Storage<FlightCapabilityEntry> sFlightCapabilityStore;
|
||||
@@ -200,25 +227,10 @@ TC_GAME_API extern DB2Storage<GuildColorEmblemEntry> sGuildColorE
|
||||
TC_GAME_API extern DB2Storage<GuildPerkSpellsEntry> sGuildPerkSpellsStore;
|
||||
TC_GAME_API extern DB2Storage<HolidaysEntry> sHolidaysStore;
|
||||
TC_GAME_API extern DB2Storage<HouseEntry> sHouseStore;
|
||||
TC_GAME_API extern DB2Storage<HouseDecorEntry> sHouseDecorStore;
|
||||
TC_GAME_API extern DB2Storage<HouseDecorMaterialEntry> sHouseDecorMaterialStore;
|
||||
TC_GAME_API extern DB2Storage<HouseDecorThemeSetEntry> sHouseDecorThemeSetStore;
|
||||
TC_GAME_API extern DB2Storage<HouseExteriorWmoDataEntry> sHouseExteriorWmoDataStore;
|
||||
TC_GAME_API extern DB2Storage<HouseLevelDataEntry> sHouseLevelDataStore;
|
||||
TC_GAME_API extern DB2Storage<HouseLevelRewardInfoEntry> sHouseLevelRewardInfoStore;
|
||||
TC_GAME_API extern DB2Storage<HouseRoomEntry> sHouseRoomStore;
|
||||
TC_GAME_API extern DB2Storage<HouseThemeEntry> sHouseThemeStore;
|
||||
TC_GAME_API extern DB2Storage<ImportPriceArmorEntry> sImportPriceArmorStore;
|
||||
TC_GAME_API extern DB2Storage<ImportPriceQualityEntry> sImportPriceQualityStore;
|
||||
TC_GAME_API extern DB2Storage<ImportPriceShieldEntry> sImportPriceShieldStore;
|
||||
TC_GAME_API extern DB2Storage<ImportPriceWeaponEntry> sImportPriceWeaponStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeCycleEntry> sInitiativeCycleStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeCyclePriorityEntry> sInitiativeCyclePriorityStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeMilestoneEntry> sInitiativeMilestoneStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeRewardEntry> sInitiativeRewardStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeRewardXMilestoneEntry> sInitiativeRewardXMilestoneStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeTaskEntry> sInitiativeTaskStore;
|
||||
TC_GAME_API extern DB2Storage<InitiativeXTaskEntry> sInitiativeXTaskStore;
|
||||
TC_GAME_API extern DB2Storage<ItemAppearanceEntry> sItemAppearanceStore;
|
||||
TC_GAME_API extern DB2Storage<ItemArmorQualityEntry> sItemArmorQualityStore;
|
||||
TC_GAME_API extern DB2Storage<ItemArmorShieldEntry> sItemArmorShieldStore;
|
||||
@@ -283,10 +295,6 @@ TC_GAME_API extern DB2Storage<MountEntry> sMountStore;
|
||||
TC_GAME_API extern DB2Storage<MountEquipmentEntry> sMountEquipmentStore;
|
||||
TC_GAME_API extern DB2Storage<MovieEntry> sMovieStore;
|
||||
TC_GAME_API extern DB2Storage<MythicPlusSeasonEntry> sMythicPlusSeasonStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodInitiativeEntry> sNeighborhoodInitiativeStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodMapEntry> sNeighborhoodMapStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodNameGenEntry> sNeighborhoodNameGenStore;
|
||||
TC_GAME_API extern DB2Storage<NeighborhoodPlotEntry> sNeighborhoodPlotStore;
|
||||
TC_GAME_API extern DB2Storage<OverrideSpellDataEntry> sOverrideSpellDataStore;
|
||||
TC_GAME_API extern DB2Storage<ParagonReputationEntry> sParagonReputationStore;
|
||||
TC_GAME_API extern DB2Storage<PerksActivityEntry> sPerksActivityStore;
|
||||
@@ -313,11 +321,6 @@ TC_GAME_API extern DB2Storage<QuestSortEntry> sQuestSortSt
|
||||
TC_GAME_API extern DB2Storage<QuestXPEntry> sQuestXPStore;
|
||||
TC_GAME_API extern DB2Storage<RandPropPointsEntry> sRandPropPointsStore;
|
||||
TC_GAME_API extern DB2Storage<RewardPackEntry> sRewardPackStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentEntry> sRoomComponentStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentOptionEntry> sRoomComponentOptionStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentOptionTextureEntry> sRoomComponentOptionTextureStore;
|
||||
TC_GAME_API extern DB2Storage<RoomComponentTextureEntry> sRoomComponentTextureStore;
|
||||
TC_GAME_API extern DB2Storage<RoomWmoDataEntry> sRoomWmoDataStore;
|
||||
TC_GAME_API extern DB2Storage<ScenarioEntry> sScenarioStore;
|
||||
TC_GAME_API extern DB2Storage<ScenarioStepEntry> sScenarioStepStore;
|
||||
TC_GAME_API extern DB2Storage<SkillLineEntry> sSkillLineStore;
|
||||
|
||||
@@ -1577,33 +1577,33 @@ struct DecorCategoryEntry
|
||||
{
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
int32 UiTextureAtlasElementID; // Meta[2] INT: WoWDBDefs FK->UiTextureAtlasElement
|
||||
int32 OrderIndex; // Meta[3] INT: WoWDBDefs "OrderIndex"
|
||||
int32 UiTextureAtlasElementID;
|
||||
int32 OrderIndex;
|
||||
};
|
||||
|
||||
struct DecorDyeSlotEntry
|
||||
{
|
||||
uint32 ID;
|
||||
int32 DyeColorCategoryID; // Meta[1] INT: WoWDBDefs FK->DyeColorCategory
|
||||
int32 HouseDecorID; // Meta[2] INT: WoWDBDefs FK->HouseDecor (nullable)
|
||||
int32 OrderIndex; // Meta[3] INT: WoWDBDefs "OrderIndex"
|
||||
int32 Channel; // Meta[4] INT: WoWDBDefs "Channel"
|
||||
int32 DyeColorCategoryID;
|
||||
int32 HouseDecorID; // nullable
|
||||
int32 OrderIndex;
|
||||
int32 Channel;
|
||||
};
|
||||
|
||||
struct DecorSubcategoryEntry
|
||||
{
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
int32 UiTextureAtlasElementID; // Meta[2] INT: WoWDBDefs FK->UiTextureAtlasElement
|
||||
int32 DecorCategoryID; // Meta[3] INT: WoWDBDefs FK->DecorCategory
|
||||
int32 OrderIndex; // Meta[4] INT: WoWDBDefs "OrderIndex"
|
||||
int32 UiTextureAtlasElementID;
|
||||
int32 DecorCategoryID;
|
||||
int32 OrderIndex;
|
||||
};
|
||||
|
||||
struct DecorXDecorSubcategoryEntry
|
||||
{
|
||||
uint32 ID;
|
||||
uint32 HouseDecorID; // WoWDBDefs field 1: $relation$ to HouseDecor
|
||||
int32 DecorSubcategoryID; // WoWDBDefs field 2: references DecorSubcategory
|
||||
uint32 HouseDecorID;
|
||||
int32 DecorSubcategoryID;
|
||||
};
|
||||
|
||||
struct DestructibleModelDataEntry
|
||||
@@ -1744,20 +1744,21 @@ struct ExpectedStatModEntry
|
||||
|
||||
struct ExteriorComponentEntry
|
||||
{
|
||||
// HouseExteriorWmoDataID is the ParentIndexField and sits mid-record, right after Size
|
||||
LocalizedString Name;
|
||||
std::array<float, 3> Position;
|
||||
uint32 ID;
|
||||
uint8 Size;
|
||||
int32 HouseExteriorWmoDataID;
|
||||
int32 ParentComponentID;
|
||||
int32 ModelFileDataID;
|
||||
int32 Flags;
|
||||
uint8 Field_12_0_0_63534_007;
|
||||
uint8 Type;
|
||||
int32 Field_12_0_0_63534_009;
|
||||
int32 GameObjectID;
|
||||
int32 Field_11_2_7_64044_011;
|
||||
int32 ItemID; // ParentIndexField - must be unsigned
|
||||
uint8 Size;
|
||||
uint32 HouseExteriorWmoDataID; // ParentIndexField - must be unsigned
|
||||
int32 ParentComponentID; // references another ExteriorComponent (0 for defaults)
|
||||
int32 ModelFileDataID;
|
||||
int32 Flags; // 0x1 IsDefaultFixture, 0x2 UnlockedByDefault
|
||||
uint8 Field_7; // unknown (always 1)
|
||||
uint8 Type; // references ExteriorComponentType
|
||||
int32 Field_9;
|
||||
int32 GameObjectID;
|
||||
int32 Field_11; // WoWDBDefs: Field_11_2_7_64044_011
|
||||
int32 ItemID; // references Item.ID - component can be sold/earned as an item, like HouseDecor.ItemID
|
||||
};
|
||||
|
||||
struct ExteriorComponentExitPointEntry
|
||||
@@ -1765,45 +1766,44 @@ struct ExteriorComponentExitPointEntry
|
||||
std::array<float, 3> Position;
|
||||
std::array<float, 3> Rotation;
|
||||
uint32 ID;
|
||||
uint32 ExteriorComponentID; // ParentIndexField - must be unsigned
|
||||
uint32 ExteriorComponentID; // ParentIndexField
|
||||
};
|
||||
|
||||
struct ExteriorComponentGroupEntry
|
||||
{
|
||||
std::array<float, 3> Position;
|
||||
uint32 ID;
|
||||
uint32 HouseExteriorWmoDataID; // ParentIndexField - references HouseExteriorWmoData
|
||||
uint32 HouseExteriorWmoDataID; // ParentIndexField
|
||||
};
|
||||
|
||||
struct ExteriorComponentGroupXHookEntry
|
||||
{
|
||||
uint32 ID;
|
||||
uint32 ExteriorComponentGroupID; // ParentIndexField - must be unsigned
|
||||
uint32 ExteriorComponentGroupID; // ParentIndexField
|
||||
int32 ExteriorComponentHookID;
|
||||
};
|
||||
|
||||
struct ExteriorComponentHookEntry
|
||||
{
|
||||
// Field order must match LoadInfo: Position, Rotation, ID, TypeID, CompID
|
||||
std::array<float, 3> Position;
|
||||
std::array<float, 3> Rotation;
|
||||
uint32 ID;
|
||||
int32 ExteriorComponentTypeID;
|
||||
uint32 ExteriorComponentID; // ParentIndexField - must be unsigned
|
||||
uint32 ExteriorComponentID; // ParentIndexField
|
||||
};
|
||||
|
||||
struct ExteriorComponentTypeEntry
|
||||
{
|
||||
uint32 ID;
|
||||
LocalizedString Name;
|
||||
int32 ParentComponentType; // WoWDBDefs: self-reference to ExteriorComponentType
|
||||
int32 ParentComponentType;
|
||||
};
|
||||
|
||||
struct ExteriorComponentXGroupEntry
|
||||
{
|
||||
uint32 ID;
|
||||
int32 ExteriorComponentGroupID; // WoWDBDefs field 1: references ExteriorComponentGroup
|
||||
int32 ExteriorComponentID; // WoWDBDefs field 2: references ExteriorComponent
|
||||
int32 ExteriorComponentGroupID;
|
||||
int32 ExteriorComponentID;
|
||||
};
|
||||
|
||||
struct FactionEntry
|
||||
@@ -2782,10 +2782,9 @@ struct HouseEntry
|
||||
|
||||
struct HouseDecorEntry
|
||||
{
|
||||
LocalizedString Name; // Meta field 0: FT_STRING
|
||||
DBCPosition3D InitialRotation; // Meta field 1: FT_FLOAT[3]
|
||||
uint32 ID; // Meta field 2: IndexField
|
||||
int32 Field_003;
|
||||
LocalizedString Name;
|
||||
DBCPosition3D InitialRotation;
|
||||
uint32 ID;
|
||||
int32 GameObjectID;
|
||||
int32 Flags;
|
||||
uint8 Type;
|
||||
@@ -2795,9 +2794,9 @@ struct HouseDecorEntry
|
||||
int32 WeightCost;
|
||||
int32 ItemID;
|
||||
float InitialScale;
|
||||
int32 FirstAcquisitionBonus; // House XP gained on first acquisition (from Lua API)
|
||||
int32 FirstAcquisitionBonus; // house XP gained on first acquisition (per Lua API)
|
||||
int32 OrderIndex;
|
||||
int8 Size; // HousingCatalogEntrySize (inferred from Lua API)
|
||||
int8 Size; // HousingCatalogEntrySize
|
||||
int32 StartingQuantity;
|
||||
int32 UiModelSceneID;
|
||||
};
|
||||
@@ -2805,11 +2804,11 @@ struct HouseDecorEntry
|
||||
struct HouseDecorMaterialEntry
|
||||
{
|
||||
uint32 ID;
|
||||
uint64 WMOMaterialReference; // Meta[1] LONG: WoWDBDefs "WMOMaterialReference"
|
||||
int32 MaterialTextureIndex; // Meta[2] INT: WoWDBDefs "MaterialTextureIndex"
|
||||
int32 HouseThemeID; // Meta[3] INT: WoWDBDefs "HouseThemeID" FK->HouseTheme
|
||||
int32 TextureAFileDataID; // Meta[4] INT: WoWDBDefs "TextureAFileDataID" FK->FileData
|
||||
int32 TextureBFileDataID; // Meta[5] INT: WoWDBDefs "TextureBFileDataID" FK->FileData
|
||||
uint64 WMOMaterialReference;
|
||||
int32 MaterialTextureIndex;
|
||||
int32 HouseThemeID;
|
||||
int32 TextureAFileDataID;
|
||||
int32 TextureBFileDataID;
|
||||
};
|
||||
|
||||
struct HouseDecorThemeSetEntry
|
||||
@@ -2822,11 +2821,11 @@ struct HouseDecorThemeSetEntry
|
||||
|
||||
struct HouseExteriorWmoDataEntry
|
||||
{
|
||||
LocalizedString Name; // Meta field 0: FT_STRING
|
||||
uint32 ID; // Meta field 1: IndexField
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
int32 Flags;
|
||||
int32 Field_003; // Meta field 3: NEW in 12.0.5.66330 (per WoWDBDefs 0x95E7A088)
|
||||
int32 Field_004; // Meta field 4: NEW in 12.0.5.66330
|
||||
int32 Field_003;
|
||||
int32 Field_004;
|
||||
};
|
||||
|
||||
struct HouseLevelDataEntry
|
||||
@@ -2834,45 +2833,47 @@ struct HouseLevelDataEntry
|
||||
uint32 ID;
|
||||
int32 Level;
|
||||
int32 QuestID;
|
||||
int32 Field_12_0_7_67808_003; // 12.0.7 (build 67808): new field, unnamed in all static sources (kept as-is per NO WRONG DATA)
|
||||
int32 Field_12_0_7_67808_003; // added in 12.0.7, unnamed in all sources
|
||||
};
|
||||
|
||||
struct HouseLevelRewardInfoEntry
|
||||
{
|
||||
LocalizedString Name; // Meta field 0: FT_STRING
|
||||
LocalizedString Description; // Meta field 1: FT_STRING
|
||||
uint32 ID; // Meta field 2: IndexField
|
||||
int32 HouseLevelDataID; // Meta[3] INT: WoWDBDefs "HouseLevelDataID" FK->HouseLevelData
|
||||
int32 Field_4; // Meta[4] INT: WoWDBDefs "Field_12_0_0_63967_004"
|
||||
int32 IconFileDataID; // Meta[5] INT: WoWDBDefs "IconFileDataID" FK->FileData
|
||||
LocalizedString Name;
|
||||
LocalizedString Description;
|
||||
uint32 ID;
|
||||
int32 HouseLevelDataID;
|
||||
int32 Field_4; // WoWDBDefs: Field_12_0_0_63967_004
|
||||
int32 IconFileDataID;
|
||||
};
|
||||
|
||||
struct HouseRoomEntry
|
||||
{
|
||||
uint32 ID;
|
||||
// 12.1.0 client meta places Name before ID and appends a trailing SortPriority field
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
int8 Size;
|
||||
int32 Flags;
|
||||
int32 Field_002;
|
||||
int32 RoomWmoDataID;
|
||||
int32 UiTextureAtlasElementID;
|
||||
int32 WeightCost;
|
||||
int32 Field_007; // NEW in 12.0.5.66330 (per WoWDBDefs layout 0xFC6C2118)
|
||||
int32 ItemID;
|
||||
int32 SortPriority;
|
||||
};
|
||||
|
||||
struct HouseThemeEntry
|
||||
{
|
||||
LocalizedString Name; // Meta field 0: FT_STRING
|
||||
uint32 ID; // Meta field 1: IndexField
|
||||
int32 Flags; // Meta[2] INT: WoWDBDefs "Flags"
|
||||
int32 ParentThemeID; // Meta[3] INT: WoWDBDefs "ParentThemeID" FK->HouseTheme
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
int32 Flags;
|
||||
int32 ParentThemeID;
|
||||
};
|
||||
|
||||
struct RoomComponentEntry
|
||||
{
|
||||
DBCPosition3D OffsetPos; // Meta field 0: FT_FLOAT[3]
|
||||
DBCPosition3D OffsetRot; // Meta field 1: FT_FLOAT[3]
|
||||
uint32 ID; // Meta field 2: IndexField
|
||||
DBCPosition3D OffsetPos;
|
||||
DBCPosition3D OffsetRot;
|
||||
uint32 ID;
|
||||
int32 RoomWmoDataID;
|
||||
int32 ModelFileDataID;
|
||||
uint8 Type;
|
||||
@@ -2957,8 +2958,8 @@ struct InitiativeCycleEntry
|
||||
int32 RewardGroupID; // Unknown FK (values 600-2607), not NeighborhoodInitiative
|
||||
int32 CycleIndex;
|
||||
int32 StartDay;
|
||||
int32 HouseXPCap; // Meta[4] INT: WoWDBDefs "HouseXPCap"
|
||||
int32 InitiativeID; // Meta[5] INT: FK -> NeighborhoodInitiative.ID
|
||||
int32 HouseXPCap;
|
||||
int32 InitiativeID; // FK -> NeighborhoodInitiative.ID
|
||||
};
|
||||
|
||||
struct InitiativeCyclePriorityEntry
|
||||
@@ -2973,23 +2974,23 @@ struct InitiativeCyclePriorityEntry
|
||||
struct InitiativeMilestoneEntry
|
||||
{
|
||||
uint32 ID;
|
||||
int32 MilestoneOrderIndex; // Meta[1] INT: WoWDBDefs "MilestoneOrderIndex"
|
||||
float RequiredContributionAmount; // Meta[2] FLOAT: WoWDBDefs "RequiredContributionAmount"
|
||||
int32 Field_3; // Meta[3] INT: WoWDBDefs "Field_12_0_0_63534_003"
|
||||
uint32 NeighborhoodInitiativeID; // ParentIndexField: WoWDBDefs "NeighborhoodInitiativeID"
|
||||
int32 MilestoneOrderIndex;
|
||||
float RequiredContributionAmount;
|
||||
int32 Field_3; // WoWDBDefs: Field_12_0_0_63534_003
|
||||
uint32 NeighborhoodInitiativeID; // ParentIndexField
|
||||
};
|
||||
|
||||
struct InitiativeRewardEntry
|
||||
{
|
||||
int64 Money; // Meta[0] LONG: WoWDBDefs "Money"
|
||||
LocalizedString Title; // Meta[1] STRING: WoWDBDefs "Title"
|
||||
int64 Money;
|
||||
LocalizedString Title;
|
||||
LocalizedString Description;
|
||||
uint32 ID;
|
||||
int32 DecorID; // Meta[4] INT: WoWDBDefs "DecorID" FK->HouseDecor
|
||||
int32 DecorQuantity; // Meta[5] INT: WoWDBDefs "DecorQuantity"
|
||||
int32 Field_6; // Meta[6] INT: WoWDBDefs "Field_12_0_0_63534_006"
|
||||
int32 Favor; // Meta[7] INT: WoWDBDefs "Favor"
|
||||
int32 RewardQuestID; // Meta[8] INT: WoWDBDefs "RewardQuestID" FK->QuestV2
|
||||
int32 DecorID;
|
||||
int32 DecorQuantity;
|
||||
int32 Field_6; // WoWDBDefs: Field_12_0_0_63534_006
|
||||
int32 Favor;
|
||||
int32 RewardQuestID;
|
||||
};
|
||||
|
||||
struct InitiativeRewardXMilestoneEntry
|
||||
@@ -3001,15 +3002,15 @@ struct InitiativeRewardXMilestoneEntry
|
||||
|
||||
struct InitiativeTaskEntry
|
||||
{
|
||||
LocalizedString TaskName; // Meta[0] STRING: WoWDBDefs "TaskName"
|
||||
LocalizedString TaskName;
|
||||
LocalizedString Description;
|
||||
uint32 ID;
|
||||
int32 CriteriaTreeID; // Meta[3] INT: WoWDBDefs "CriteriaTreeID" FK->CriteriaTree
|
||||
int32 QuestID; // Meta[4] INT: WoWDBDefs "QuestID" FK->QuestV2
|
||||
int32 ProgressContributionAmount; // Meta[5] INT: WoWDBDefs "ProgressContributionAmount"
|
||||
int32 RepetitionContributionDampeningCurve; // Meta[6] INT: WoWDBDefs FK->Curve
|
||||
int32 Supersedes; // Meta[7] INT: WoWDBDefs "Supersedes" FK->InitiativeTask
|
||||
int32 Field_8; // Meta[8] INT: WoWDBDefs "Field_12_0_0_63534_008"
|
||||
int32 CriteriaTreeID;
|
||||
int32 QuestID;
|
||||
int32 ProgressContributionAmount;
|
||||
int32 RepetitionContributionDampeningCurve;
|
||||
int32 Supersedes;
|
||||
int32 Field_8; // WoWDBDefs: Field_12_0_0_63534_008
|
||||
};
|
||||
|
||||
struct InitiativeXTaskEntry
|
||||
@@ -3992,9 +3993,9 @@ struct NamesReservedLocaleEntry
|
||||
|
||||
struct NeighborhoodInitiativeEntry
|
||||
{
|
||||
LocalizedString Name; // Meta field 0: FT_STRING
|
||||
LocalizedString Description; // Meta field 1: FT_STRING
|
||||
uint32 ID; // Meta field 2: IndexField
|
||||
LocalizedString Name;
|
||||
LocalizedString Description;
|
||||
uint32 ID;
|
||||
int32 InitiativeType;
|
||||
int32 Duration;
|
||||
int32 RequiredParticipants;
|
||||
@@ -4003,40 +4004,40 @@ struct NeighborhoodInitiativeEntry
|
||||
|
||||
struct NeighborhoodMapEntry
|
||||
{
|
||||
DBCPosition3D Position; // Meta field 0: FT_FLOAT[3]
|
||||
uint32 ID; // Meta field 1: IndexField
|
||||
DBCPosition3D Position;
|
||||
uint32 ID;
|
||||
int32 MapID;
|
||||
float EntryRotation; // Meta[3] FLOAT: WoWDBDefs "EntryRotation"
|
||||
uint32 UiTextureKitID; // Meta[4] UINT: WoWDBDefs "UiTextureKitID"
|
||||
int32 Flags; // Meta[5] INT: WoWDBDefs "Flags"
|
||||
float EntryRotation;
|
||||
uint32 UiTextureKitID;
|
||||
int32 Flags;
|
||||
};
|
||||
|
||||
struct NeighborhoodNameGenEntry
|
||||
{
|
||||
uint32 ID;
|
||||
LocalizedString Prefix;
|
||||
LocalizedString Middle; // Meta[2] STRING: WoWDBDefs "Middle"
|
||||
LocalizedString Suffix; // Meta[3] STRING: WoWDBDefs "Suffix"
|
||||
LocalizedString Middle;
|
||||
LocalizedString Suffix;
|
||||
uint32 NeighborhoodMapID;
|
||||
};
|
||||
|
||||
struct NeighborhoodPlotEntry
|
||||
{
|
||||
uint64 Cost; // Meta field 0: FT_LONG
|
||||
char const* Name; // Meta field 1: FT_STRING_NOT_LOCALIZED
|
||||
DBCPosition3D HousePosition; // Meta field 2: FT_FLOAT[3]
|
||||
DBCPosition3D HouseRotation; // Meta field 3: FT_FLOAT[3]
|
||||
DBCPosition3D CornerstonePosition; // Meta field 4: FT_FLOAT[3]
|
||||
DBCPosition3D CornerstoneRotation; // Meta field 5: FT_FLOAT[3]
|
||||
DBCPosition3D TeleportPosition; // Meta field 6: FT_FLOAT[3]
|
||||
uint32 ID; // Meta field 7: IndexField
|
||||
uint64 Cost;
|
||||
char const* Name;
|
||||
DBCPosition3D HousePosition;
|
||||
DBCPosition3D HouseRotation;
|
||||
DBCPosition3D CornerstonePosition;
|
||||
DBCPosition3D CornerstoneRotation;
|
||||
DBCPosition3D TeleportPosition;
|
||||
uint32 ID;
|
||||
uint32 NeighborhoodMapID;
|
||||
int32 Field_010;
|
||||
int32 CornerstoneGameObjectID;
|
||||
int32 PlotIndex;
|
||||
int32 WorldState;
|
||||
int32 PlotGameObjectID;
|
||||
float TeleportFacing; // Facing angle at TeleportPosition (mirrors NeighborhoodMap.EntryRotation pattern)
|
||||
float TeleportFacing; // facing angle at TeleportPosition
|
||||
int32 Field_016;
|
||||
};
|
||||
|
||||
@@ -6791,4 +6792,32 @@ struct QuestV2CliTaskEntry
|
||||
};
|
||||
//WowCommunity
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
struct RoomGridLineEntry
|
||||
{
|
||||
DBCPosition3D Offset;
|
||||
uint32 ID;
|
||||
float SizeX;
|
||||
float SizeY;
|
||||
uint32 RoomWmoDataID; // ParentIndexField
|
||||
};
|
||||
|
||||
struct DyeColorEntry
|
||||
{
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
uint32 DyeColorCategoryID; // ParentIndexField - must be unsigned
|
||||
int32 GradientTextureIndex;
|
||||
int32 ItemID;
|
||||
int32 SwatchColorStart;
|
||||
int32 SwatchColorEnd;
|
||||
int32 SortOrder;
|
||||
};
|
||||
|
||||
struct DyeColorCategoryEntry
|
||||
{
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -800,9 +800,9 @@ enum class CriteriaType : int16
|
||||
ReachRenownLevel = 261,
|
||||
LearnTaxiNode = 262,
|
||||
|
||||
PlaceDecor = 270, /*NYI*/ // Place any decor
|
||||
RemoveDecor = 271, /*NYI*/ // Remove any decor
|
||||
CollectUniqueDecor = 272, /*NYI*/ // Collect Unique Decor
|
||||
PlaceDecor = 270, // Place any decor
|
||||
RemoveDecor = 271, // Remove any decor
|
||||
CollectUniqueDecor = 272, // Collect Unique Decor
|
||||
|
||||
Count = 284
|
||||
};
|
||||
@@ -2296,7 +2296,7 @@ enum class ModifierTreeType : int32
|
||||
|
||||
PlayerMoneyIsRelOp = 417, // Player money is {RelOp} {#Amount}
|
||||
|
||||
PlayerHousesCountEqualOrGreaterThan = 419, /*NYI*/ // Player has at least {#Amount} houses
|
||||
PlayerHousesCountEqualOrGreaterThan = 419, // Player has at least {#Amount} houses
|
||||
};
|
||||
|
||||
enum class ModifierTreeOperator : int8
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "HousingDefines.h"
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
@@ -24,14 +25,17 @@
|
||||
namespace Battlenet
|
||||
{
|
||||
Account::Account(WorldSession* session, ObjectGuid guid, std::string&& name) : m_session(session), m_name(std::move(name))
|
||||
{
|
||||
_Create(guid);
|
||||
{
|
||||
_Create(guid);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingStorage_C, false, WowCS::GetRawFragmentData(m_housingStorageData));
|
||||
// Only FHousingStorage_C belongs on the BNetAccount entity.
|
||||
// FHousingPlayerHouse_C ? Housing/3 entity (HousingPlayerHouseEntity)
|
||||
// FNeighborhoodMirrorData_C ? Housing/4 entity (HousingNeighborhoodMirrorEntity)
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingStorage_C, false, WowCS::GetRawFragmentData(m_housingStorageData));
|
||||
|
||||
// Default value
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Account::m_housingStorageData).ModifyValue(&UF::HousingStorageData::DecorMaxOwnedCount), 5000);
|
||||
}
|
||||
// Default value
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Account::m_housingStorageData).ModifyValue(&UF::HousingStorageData::DecorMaxOwnedCount), 5000);
|
||||
}
|
||||
|
||||
std::string Account::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
{
|
||||
@@ -78,3 +82,60 @@ namespace Battlenet
|
||||
owner->GetMap()->RemoveUpdateObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Battlenet::Account::ClearUpdateMask(bool remove)
|
||||
{
|
||||
m_values.ClearChangesMask(&Account::m_housingStorageData);
|
||||
BaseEntity::ClearUpdateMask(remove);
|
||||
}
|
||||
|
||||
void Battlenet::Account::SendUpdateToPlayer(Player* player)
|
||||
{
|
||||
// BaseEntity::SendUpdateToPlayer is const and skips BuildUpdateChangesMask(),
|
||||
// so ContentsChangedMask is 0 and the VALUES_UPDATE contains no fragment data.
|
||||
// We must compute the mask before serializing so that pending fragment changes
|
||||
// (e.g., FHousingStorage_C populated by PopulateCatalogStorageEntries) are included.
|
||||
BuildUpdateChangesMask();
|
||||
BaseEntity::SendUpdateToPlayer(player);
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
void Battlenet::Account::SetHousingDecorStorageEntry(ObjectGuid decorGuid, ObjectGuid houseGuid, uint8 sourceType, std::string sourceValue, std::optional<uint8> placementStatus)
|
||||
{
|
||||
auto setter = m_values.ModifyValue(&Account::m_housingStorageData).ModifyValue(&UF::HousingStorageData::Decor);
|
||||
// Remove before re-inserting so identical values still mark the entry changed: the client
|
||||
// ingests storage entries only through updates after its housing state initializes (the
|
||||
// login CREATE is processed too early), so the editor-entry storage refresh must reach it
|
||||
// even when nothing actually changed.
|
||||
RemoveMapUpdateFieldValue(setter, decorGuid);
|
||||
|
||||
// Retail 12.1.0.69933: 1 = placed inside the house, 2 = placed on the plot. The client sums
|
||||
// the interior placement budget from status 1 records and the exterior budget from status 2.
|
||||
uint8 const status = placementStatus.value_or(houseGuid.IsEmpty()
|
||||
? uint8(HOUSING_DECOR_IN_STORAGE)
|
||||
: uint8(HOUSING_DECOR_PLACED_HOUSE));
|
||||
|
||||
auto ref = m_values.ModifyValue(&Account::m_housingStorageData).ModifyValue(&UF::HousingStorageData::Decor, decorGuid);
|
||||
SetUpdateFieldValue(ref.ModifyValue(&UF::DecorStoragePersistedData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(ref.ModifyValue(&UF::DecorStoragePersistedData::PlacementStatus), status);
|
||||
SetUpdateFieldValue(ref.ModifyValue(&UF::DecorStoragePersistedData::SourceType), sourceType);
|
||||
SetUpdateFieldValue(ref.ModifyValue(&UF::DecorStoragePersistedData::SourceValue), std::move(sourceValue));
|
||||
}
|
||||
|
||||
void Battlenet::Account::SetHousingDecorDyeSlots(ObjectGuid decorGuid, std::array<uint32, 3> const& dyeSlots)
|
||||
{
|
||||
auto ref = m_values.ModifyValue(&Account::m_housingStorageData).ModifyValue(&UF::HousingStorageData::Decor, decorGuid);
|
||||
if (std::ranges::any_of(dyeSlots, [](uint32 dye) { return dye != 0; }))
|
||||
SetUpdateFieldValue(ref.ModifyValue(&UF::DecorStoragePersistedData::DyeSlots, 0)
|
||||
.ModifyValue(&UF::DecorDyeSlots::DyeColorID), { int32(dyeSlots[0]), int32(dyeSlots[1]), int32(dyeSlots[2]) });
|
||||
else
|
||||
RemoveOptionalUpdateFieldValue(ref.ModifyValue(&UF::DecorStoragePersistedData::DyeSlots));
|
||||
}
|
||||
|
||||
void Battlenet::Account::RemoveHousingDecorStorageEntry(ObjectGuid decorGuid)
|
||||
{
|
||||
auto setter = m_values.ModifyValue(&Account::m_housingStorageData).ModifyValue(&UF::HousingStorageData::Decor);
|
||||
RemoveMapUpdateFieldValue(setter, decorGuid);
|
||||
}
|
||||
|
||||
@@ -20,33 +20,55 @@
|
||||
|
||||
#include "BaseEntity.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
class WorldSession;
|
||||
|
||||
namespace Battlenet
|
||||
{
|
||||
class Account final : public BaseEntity
|
||||
{
|
||||
public:
|
||||
explicit Account(WorldSession* session, ObjectGuid guid, std::string&& name);
|
||||
class Account final : public BaseEntity
|
||||
{
|
||||
public:
|
||||
explicit Account(WorldSession* session, ObjectGuid guid, std::string&& name);
|
||||
|
||||
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
void ClearUpdateMask(bool remove) override;
|
||||
|
||||
void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
|
||||
std::string GetDebugInfo() const override;
|
||||
void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
|
||||
UF::UpdateField<UF::HousingStorageData, int32(WowCS::EntityFragment::FHousingStorage_C), 0> m_housingStorageData;
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
protected:
|
||||
UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
// Direct send that builds ContentsChangedMask before serializing.
|
||||
// BaseEntity::SendUpdateToPlayer is const and doesn't call BuildUpdateChangesMask(),
|
||||
// so the VALUES_UPDATE packet is empty when called directly from handlers.
|
||||
// This override ensures fragment changes are detected before the send.
|
||||
void SendUpdateToPlayer(Player* player);
|
||||
|
||||
bool AddToObjectUpdate() override;
|
||||
void RemoveFromObjectUpdate() override;
|
||||
// Housing storage data (decor catalog) ? only FHousingStorage_C belongs on the BNetAccount entity.
|
||||
// FHousingPlayerHouse_C is on the Housing/3 entity (HousingPlayerHouseEntity).
|
||||
// FNeighborhoodMirrorData_C is on the Housing/4 entity (HousingNeighborhoodMirrorEntity).
|
||||
// placementStatus: HousingDecorPlacementStatus. Empty houseGuid defaults to in-storage (0),
|
||||
// non-empty to placed-in-house (1); pass HOUSING_DECOR_PLACED_PLOT explicitly for plot decor.
|
||||
void SetHousingDecorStorageEntry(ObjectGuid decorGuid, ObjectGuid houseGuid, uint8 sourceType,
|
||||
std::string sourceValue = {}, std::optional<uint8> placementStatus = {});
|
||||
void RemoveHousingDecorStorageEntry(ObjectGuid decorGuid);
|
||||
// DyeColor.db2 IDs per DecorDyeSlot (0 = undyed); all zero drops the optional DyeSlots.
|
||||
void SetHousingDecorDyeSlots(ObjectGuid decorGuid, std::array<uint32, 3> const& dyeSlots);
|
||||
|
||||
private:
|
||||
WorldSession* m_session;
|
||||
std::string m_name;
|
||||
};
|
||||
UF::UpdateField<UF::HousingStorageData, int32(WowCS::EntityFragment::FHousingStorage_C), 0> m_housingStorageData;
|
||||
|
||||
protected:
|
||||
UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
|
||||
bool AddToObjectUpdate() override;
|
||||
void RemoveFromObjectUpdate() override;
|
||||
|
||||
private:
|
||||
WorldSession* m_session;
|
||||
std::string m_name;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_ACCOUNT_H
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ void AreaTrigger::PlaySpellVisual(uint32 spellVisualId) const
|
||||
SendMessageToSet(packet.Write(), false);
|
||||
}
|
||||
|
||||
bool AreaTrigger::Create(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration, AreaTriggerSpawn const* spawnData /*= nullptr*/, Unit* caster /*= nullptr*/, Unit* target /*= nullptr*/, SpellCastVisual spellVisual /*= { 0, 0 }*/, SpellInfo const* spellInfo /*= nullptr*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/)
|
||||
bool AreaTrigger::Create(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration, AreaTriggerSpawn const* spawnData /*= nullptr*/, Unit* caster /*= nullptr*/, Unit* target /*= nullptr*/, SpellCastVisual spellVisual /*= { 0, 0 }*/, SpellInfo const* spellInfo /*= nullptr*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/, bool addToMap /*= true*/)
|
||||
{
|
||||
_targetGuid = target ? target->GetGUID() : ObjectGuid::Empty;
|
||||
_aurEff = aurEff;
|
||||
@@ -324,7 +324,7 @@ bool AreaTrigger::Create(AreaTriggerCreatePropertiesId areaTriggerCreateProperti
|
||||
|
||||
UpdateShape();
|
||||
|
||||
if (!IsStaticSpawn())
|
||||
if (!IsStaticSpawn() && addToMap)
|
||||
{
|
||||
if (!GetMap()->AddToMap(this))
|
||||
{
|
||||
@@ -1856,10 +1856,10 @@ void AreaTrigger::ClearValuesChangesMask()
|
||||
}
|
||||
|
||||
//WowCommunity
|
||||
AreaTrigger* AreaTrigger::CreateStaticAreaTrigger(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration /*= -1*/)
|
||||
AreaTrigger* AreaTrigger::CreateStaticAreaTrigger(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration /*= -1*/, bool addToMap /*= true*/)
|
||||
{
|
||||
AreaTrigger* at = new AreaTrigger();
|
||||
if (!at->Create(areaTriggerCreatePropertiesId, map, pos, duration, nullptr, nullptr, nullptr, { 0, 0 }, nullptr, nullptr, nullptr))
|
||||
if (!at->Create(areaTriggerCreatePropertiesId, map, pos, duration, nullptr, nullptr, nullptr, { 0, 0 }, nullptr, nullptr, nullptr, addToMap))
|
||||
{
|
||||
delete at;
|
||||
return nullptr;
|
||||
@@ -1867,3 +1867,34 @@ AreaTrigger* AreaTrigger::CreateStaticAreaTrigger(AreaTriggerCreatePropertiesId
|
||||
|
||||
return at;
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int32 HousingPlotSpellForVisuals = 1282351;
|
||||
constexpr int32 HousingPlotSpellXSpellVisualId = 510142;
|
||||
constexpr uint32 HousingPlotExtraScaleParameterCurve = 0x3F800001;
|
||||
}
|
||||
|
||||
void AreaTrigger::InitHousingPlotVisuals()
|
||||
{
|
||||
auto areaTriggerData = m_values.ModifyValue(&AreaTrigger::m_areaTriggerData);
|
||||
|
||||
// the spell doesn't exist in sSpellMgr, so force these values
|
||||
if (*m_areaTriggerData->SpellForVisuals == 0)
|
||||
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::SpellForVisuals), HousingPlotSpellForVisuals);
|
||||
if (m_areaTriggerData->SpellVisual->SpellXSpellVisualID == 0)
|
||||
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::SpellVisual).ModifyValue(&UF::SpellCastVisual::SpellXSpellVisualID), HousingPlotSpellXSpellVisualId);
|
||||
|
||||
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::PeriodModifier)
|
||||
.ModifyValue(&UF::AreaTriggerActionSetPeriodModifier::Field_0), int32(0));
|
||||
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::PeriodModifier)
|
||||
.ModifyValue(&UF::AreaTriggerActionSetPeriodModifier::Field_4), 1.0f);
|
||||
|
||||
// flatten terrain / remove grass within the plot boundary
|
||||
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ExtraScaleCurve)
|
||||
.ModifyValue(&UF::OverrideCurve::ParameterCurve), HousingPlotExtraScaleParameterCurve);
|
||||
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ExtraScaleCurve)
|
||||
.ModifyValue(&UF::OverrideCurve::OverrideActive), true);
|
||||
}
|
||||
|
||||
@@ -119,14 +119,17 @@ class TC_GAME_API AreaTrigger final : public WorldObject, public GridObject<Area
|
||||
void PlaySpellVisual(uint32 spellVisualId) const;
|
||||
|
||||
private:
|
||||
bool Create(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration, AreaTriggerSpawn const* spawnData = nullptr, Unit* caster = nullptr, Unit* target = nullptr, SpellCastVisual spellVisual = { 0, 0 }, SpellInfo const* spellInfo = nullptr, Spell* spell = nullptr, AuraEffect const* aurEff = nullptr);
|
||||
bool Create(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration, AreaTriggerSpawn const* spawnData = nullptr, Unit* caster = nullptr, Unit* target = nullptr, SpellCastVisual spellVisual = { 0, 0 }, SpellInfo const* spellInfo = nullptr, Spell* spell = nullptr, AuraEffect const* aurEff = nullptr, bool addToMap = true);
|
||||
|
||||
public:
|
||||
static AreaTrigger* CreateAreaTrigger(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Position const& pos, int32 duration, Unit* caster, Unit* target, SpellCastVisual spellVisual = { 0, 0 }, SpellInfo const* spellInfo = nullptr, Spell* spell = nullptr, AuraEffect const* aurEff = nullptr);
|
||||
static AreaTrigger* CreateStaticAreaTrigger(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration = -1);
|
||||
static AreaTrigger* CreateStaticAreaTrigger(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration = -1, bool addToMap = true);
|
||||
static ObjectGuid CreateNewMovementForceId(Map* map, uint32 areaTriggerId);
|
||||
bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate);
|
||||
|
||||
// Housing plot AT visual setup (SpellForVisuals, PeriodModifier, ExtraScaleCurve).
|
||||
void InitHousingPlotVisuals();
|
||||
|
||||
void Update(uint32 diff) override;
|
||||
void Remove();
|
||||
bool IsRemoved() const { return _isRemoved; }
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
#include "Garrison.h"
|
||||
#include "NPCPackets.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "HousingTutorialSession.h"
|
||||
//WowCommunity
|
||||
|
||||
void GameObjectTemplate::InitializeQueryData()
|
||||
@@ -961,6 +960,9 @@ void GameObject::AddToWorld()
|
||||
|
||||
EnableCollision(toggledState);
|
||||
WorldObject::AddToWorld();
|
||||
// Objects visible beyond the visited grids need an explicit visibility pass.
|
||||
if (GetVisibilityRange() > SIZE_OF_GRIDS)
|
||||
GetMap()->AddInfiniteGameObject(GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -981,6 +983,7 @@ void GameObject::RemoveFromWorld()
|
||||
if (GameObject* linkedTrap = GetLinkedTrap())
|
||||
linkedTrap->DespawnOrUnsummon();
|
||||
|
||||
GetMap()->RemoveInfiniteGameObject(GetGUID());
|
||||
WorldObject::RemoveFromWorld();
|
||||
|
||||
if (m_spawnId)
|
||||
@@ -3486,52 +3489,68 @@ void GameObject::Use(Unit* user, bool ignoreCastInProgress /*= false*/)
|
||||
return;
|
||||
}
|
||||
case GAMEOBJECT_TYPE_UI_LINK:
|
||||
{
|
||||
Player* player = user->ToPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
WorldPackets::GameObject::GameObjectInteraction gameObjectUILink;
|
||||
gameObjectUILink.ObjectGUID = GetGUID();
|
||||
// Data7 supersedes the deprecated Data0 on current client templates.
|
||||
uint32 const uiLinkType = GetGOInfo()->UILink.PlayerInteractionType == 70
|
||||
? 4 : GetGOInfo()->UILink.UILinkType;
|
||||
switch (uiLinkType)
|
||||
{
|
||||
case 0:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::AdventureJournal;
|
||||
break;
|
||||
case 1:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::ObliterumForge;
|
||||
break;
|
||||
case 2:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::ScrappingMachine;
|
||||
break;
|
||||
case 3:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::ItemInteraction;
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
if (!sHousingMgr.IsPlayerOnHousingExterior(player) ||
|
||||
GetMap()->GetHousingPlotIdByCornerstoneGuid(GetGUID()) < 0 ||
|
||||
!player->IsWithinDistInMap(this, 10.0f))
|
||||
Player* player = user->ToPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
player->PlayerTalkClass->GetInteractionData().StartInteraction(
|
||||
GetGUID(), static_cast<PlayerInteractionType>(70));
|
||||
WorldPackets::NPC::NPCInteractionOpenResult open;
|
||||
open.Npc = GetGUID();
|
||||
open.InteractionType = static_cast<PlayerInteractionType>(70);
|
||||
open.Success = true;
|
||||
player->SendDirectMessage(open.Write());
|
||||
Housing::CreditTutorialObjective(player, Housing::TutorialExplorePlotsCredit, GetGUID());
|
||||
|
||||
// per-plot cornerstone GOs have spell=0 in their template; fall back to the conversation trigger spell
|
||||
constexpr uint32 CornerstoneInteractionType = 70;
|
||||
constexpr uint32 CornerstoneConversationSpellId = 1266097; // [DNT] Trigger Convo for Unowned Plot
|
||||
|
||||
GameObjectTemplate const* goInfo = GetGOInfo();
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::Use(GAMEOBJECT_TYPE_UI_LINK): entry={} guid={} "
|
||||
"UILinkType={} PlayerInteractionType={} spell={} player={}",
|
||||
GetEntry(), GetGUID().ToString(),
|
||||
goInfo->UILink.UILinkType,
|
||||
goInfo->UILink.PlayerInteractionType,
|
||||
goInfo->UILink.spell,
|
||||
player->GetGUID().ToString());
|
||||
|
||||
if (goInfo->UILink.PlayerInteractionType)
|
||||
{
|
||||
WorldPackets::NPC::NPCInteractionOpenResult npcInteraction;
|
||||
npcInteraction.Npc = GetGUID();
|
||||
npcInteraction.InteractionType = static_cast<PlayerInteractionType>(goInfo->UILink.PlayerInteractionType);
|
||||
npcInteraction.Success = true;
|
||||
player->SendDirectMessage(npcInteraction.Write());
|
||||
|
||||
uint32 spellId = goInfo->UILink.spell;
|
||||
if (!spellId && goInfo->UILink.PlayerInteractionType == CornerstoneInteractionType)
|
||||
spellId = CornerstoneConversationSpellId;
|
||||
|
||||
if (spellId)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "GameObject::Use(GAMEOBJECT_TYPE_UI_LINK): casting spell {} on player", spellId);
|
||||
player->CastSpell(player, spellId, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldPackets::GameObject::GameObjectInteraction gameObjectUILink;
|
||||
gameObjectUILink.ObjectGUID = GetGUID();
|
||||
switch (goInfo->UILink.UILinkType)
|
||||
{
|
||||
case 0:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::AdventureJournal;
|
||||
break;
|
||||
case 1:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::ObliterumForge;
|
||||
break;
|
||||
case 2:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::ScrappingMachine;
|
||||
break;
|
||||
case 3:
|
||||
gameObjectUILink.InteractionType = PlayerInteractionType::ItemInteraction;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
player->SendDirectMessage(gameObjectUILink.Write());
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
player->SendDirectMessage(gameObjectUILink.Write());
|
||||
return;
|
||||
}
|
||||
case GAMEOBJECT_TYPE_GARRISON_MONUMENT: //44
|
||||
{
|
||||
// The WoD garrison trophy monuments ("Monument Base"): 232379/232380/233177 in Lunarfall and
|
||||
@@ -4419,6 +4438,8 @@ void GameObject::ValuesUpdateForPlayerWithMaskSender::operator()(Player const* p
|
||||
void GameObject::ClearValuesChangesMask()
|
||||
{
|
||||
m_values.ClearChangesMask(&GameObject::m_gameObjectData);
|
||||
m_values.ClearChangesMask(&GameObject::m_housingCornerstoneData);
|
||||
m_values.ClearChangesMask(&GameObject::m_mirroredPositionData);
|
||||
WorldObject::ClearValuesChangesMask();
|
||||
}
|
||||
|
||||
@@ -4967,3 +4988,122 @@ SpellInfo const* GameObject::GetSpellForLock(Player const* player) const
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GameObject::InitHousingCornerstoneData(uint64 cost, int32 plotIndex)
|
||||
{
|
||||
if (m_housingCornerstoneData.has_value())
|
||||
return;
|
||||
|
||||
auto cornerstoneData = m_values.ModifyValue(&GameObject::m_housingCornerstoneData, 0);
|
||||
SetUpdateFieldValue(cornerstoneData.ModifyValue(&UF::HousingCornerstoneData::Cost), cost);
|
||||
SetUpdateFieldValue(cornerstoneData.ModifyValue(&UF::HousingCornerstoneData::PlotIndex), plotIndex);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FJamHousingCornerstone_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingCornerstoneData));
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingCornerstoneData: entry={} guid={} cost={} plotIndex={} "
|
||||
"isInWorld={} fragmentCount={} updateableCount={}",
|
||||
GetEntry(), GetGUID().ToString(), cost, plotIndex,
|
||||
IsInWorld(), m_entityFragments.Count, m_entityFragments.UpdateableCount);
|
||||
}
|
||||
|
||||
void GameObject::InitHousingDecorData(ObjectGuid decorGuid, ObjectGuid houseGuid,
|
||||
uint8 flags, ObjectGuid attachParent /*= ObjectGuid::Empty*/,
|
||||
uint8 sourceType /*= 0*/, std::string sourceValue /*= {}*/)
|
||||
{
|
||||
if (m_housingDecorData.has_value())
|
||||
return;
|
||||
|
||||
auto decorData = m_values.ModifyValue(&Object::m_housingDecorData, 0);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::DecorGUID), decorGuid);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::Flags), flags);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::TargetGameObjectGUID), GetGUID());
|
||||
|
||||
auto persistedRef = decorData.ModifyValue(&UF::HousingDecorData::PersistedData, 0);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceType), sourceType);
|
||||
if (!sourceValue.empty())
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceValue), std::move(sourceValue));
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingDecor_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingDecorData));
|
||||
|
||||
// marks the GO as a housing-decor proxy (chair/chest/mailbox/etc. placed as decor)
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HousingDecorProxyGameObject, IsInWorld());
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingDecorData: entry={} goGuid={} decorGuid={} houseGuid={} flags={} "
|
||||
"isInWorld={} fragmentCount={}",
|
||||
GetEntry(), GetGUID().ToString(), decorGuid.ToString(), houseGuid.ToString(), flags,
|
||||
IsInWorld(), m_entityFragments.Count);
|
||||
}
|
||||
|
||||
void GameObject::SetHousingDecorAttachParent(ObjectGuid attachParent)
|
||||
{
|
||||
auto posData = m_values.ModifyValue(&GameObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
}
|
||||
|
||||
void GameObject::InitHousingDecorMirroredPosition(Position const& localPos, QuaternionData const& localRot,
|
||||
float localScale, ObjectGuid attachParent, uint8 attachFlags /*= 3*/)
|
||||
{
|
||||
auto posData = m_values.ModifyValue(&GameObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(localPos.GetPositionX(), localPos.GetPositionY(), localPos.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), localRot);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), localScale);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachFlags);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingDecorMirroredPosition: entry={} goGuid={} "
|
||||
"localPos=({:.2f},{:.2f},{:.2f}) attachParent={} attachFlags={}",
|
||||
GetEntry(), GetGUID().ToString(),
|
||||
localPos.GetPositionX(), localPos.GetPositionY(), localPos.GetPositionZ(),
|
||||
attachParent.ToString(), attachFlags);
|
||||
}
|
||||
|
||||
void GameObject::UpdateHousingDecorMirroredTransform(Position const& localPos, QuaternionData const& localRot, float localScale)
|
||||
{
|
||||
auto posData = m_values.ModifyValue(&GameObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(localPos.GetPositionX(), localPos.GetPositionY(), localPos.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), localRot);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), localScale);
|
||||
}
|
||||
|
||||
void GameObject::InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
uint8 exteriorComponentType /*= 9*/, uint8 houseSize /*= 2*/, int32 exteriorComponentHookID /*= -1*/)
|
||||
{
|
||||
if (m_housingFixtureData.has_value())
|
||||
return;
|
||||
|
||||
auto fixtureData = m_values.ModifyValue(&Object::m_housingFixtureData, 0);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::ExteriorComponentID), exteriorComponentID);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::HouseExteriorWmoDataID), houseExteriorWmoDataID);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::ExteriorComponentHookID), exteriorComponentHookID);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::HouseGUID), houseGuid);
|
||||
// must be a Housing-type GUID - the client crashes on other non-null GUID types here
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::Guid), houseGuid);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::ExteriorComponentType), exteriorComponentType);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::Field_59), uint8(1)); // always 1
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::Size), houseSize);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingFixture_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingFixtureData));
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingFixtureData: entry={} goGuid={} houseGuid={} "
|
||||
"exteriorComponentID={} wmoDataID={} hookID={} componentType={} size={} field59=1 "
|
||||
"isInWorld={} fragmentCount={}",
|
||||
GetEntry(), GetGUID().ToString(), houseGuid.ToString(),
|
||||
exteriorComponentID, houseExteriorWmoDataID, exteriorComponentHookID,
|
||||
exteriorComponentType, houseSize,
|
||||
IsInWorld(), m_entityFragments.Count);
|
||||
}
|
||||
|
||||
@@ -462,31 +462,31 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
|
||||
|
||||
UF::UpdateField<UF::GameObjectData, int32(WowCS::EntityFragment::CGObject), TYPEID_GAMEOBJECT> m_gameObjectData;
|
||||
|
||||
// Housing entity fragment (optional - only set on cornerstone GameObjects)
|
||||
// Housing cornerstone fragment (only set on cornerstone GameObjects)
|
||||
UF::OptionalUpdateField<UF::HousingCornerstoneData, int32(WowCS::EntityFragment::FJamHousingCornerstone_C), 0> m_housingCornerstoneData;
|
||||
void InitHousingCornerstoneData(uint64 cost, int32 plotIndex);
|
||||
// -1 when this GO is not a cornerstone (all plots share one Cornerstone GO entry)
|
||||
int32 GetHousingCornerstonePlotIndex() const
|
||||
{
|
||||
return m_housingCornerstoneData.has_value() ? m_housingCornerstoneData->PlotIndex : -1;
|
||||
}
|
||||
|
||||
// Housing decor entity fragment (optional - only set on decor item GameObjects)
|
||||
// Housing decor fragment (only set on decor item GameObjects)
|
||||
void InitHousingDecorData(ObjectGuid decorGuid, ObjectGuid houseGuid,
|
||||
uint8 flags, ObjectGuid attachParent = ObjectGuid::Empty,
|
||||
uint8 sourceType = 0, std::string sourceValue = {});
|
||||
|
||||
// Housing decor mirrored position (FMirroredPositionData_C fragment)
|
||||
// Set on functional-decor GameObjects (chairs, chests, mailboxes, etc.) so the client
|
||||
// associates them with a room entity in the housing layout. Retail sniff confirmed:
|
||||
// frags=[CGObject, FHousingDecor_C, FMirroredPositionData_C, Tag_GameObject].
|
||||
// pos: local-space offset from attachParent (room entity)
|
||||
// attachFlags: sniff-verified value = 3
|
||||
// decor mirrored position (FMirroredPositionData_C); localPos is the offset from attachParent
|
||||
void InitHousingDecorMirroredPosition(Position const& localPos, QuaternionData const& localRot,
|
||||
float localScale, ObjectGuid attachParent, uint8 attachFlags = 3);
|
||||
// Moves decor already carrying FMirroredPositionData_C (the local transform is what the client renders).
|
||||
void UpdateHousingDecorMirroredTransform(Position const& localPos, QuaternionData const& localRot, float localScale);
|
||||
ObjectGuid GetHousingDecorAttachParent() const { return m_mirroredPositionData->PositionData->AttachParentGUID; }
|
||||
void SetHousingDecorAttachParent(ObjectGuid attachParent);
|
||||
|
||||
UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
|
||||
// Housing fixture entity fragment (optional - only set on house structure GameObjects)
|
||||
// Sniff-verified: retail sends MeshObjects (type 14) for fixtures, but we attach the
|
||||
// entity fragment to a GO as a workaround until MeshObject spawning is implemented.
|
||||
// ExteriorComponentID: primary rendering key from ExteriorComponent.db2 (e.g. 141 = Stucco Base)
|
||||
// HouseExteriorWmoDataID: housing theme (e.g. 9 = Human/Generic)
|
||||
// Housing fixture fragment (only set on house structure GameObjects).
|
||||
// ExteriorComponentType: 9=Base, 10=Roof, 11=Door, 12=Window, 13=RoofDetail, 14=RoofWindow, 15=Tower, 16=Chimney
|
||||
void InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
uint8 exteriorComponentType = 9, uint8 houseSize = 2, int32 exteriorComponentHookID = -1);
|
||||
|
||||
@@ -15,186 +15,183 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include "HousingDecorEntity.h"
|
||||
//#include "Log.h"
|
||||
//#include "Map.h"
|
||||
//#include "PhasingHandler.h"
|
||||
//#include "Player.h"
|
||||
//#include "StringFormat.h"
|
||||
//#include "UpdateData.h"
|
||||
//
|
||||
//HousingDecorEntity::HousingDecorEntity()
|
||||
// : WorldObject(false)
|
||||
//{
|
||||
// m_objectTypeId = TYPEID_HOUSING_ENTITY; // 18 ? same as HousingRoomEntity
|
||||
//
|
||||
// m_updateFlag.HasEntityPosition = true;
|
||||
//
|
||||
// // Retail decor entities do NOT have CGObject ? sniff-verified fragment list is
|
||||
// // [FHousingDecor_C, FMirroredPositionData_C] only. Remove CGObject added by Object ctor.
|
||||
// m_entityFragments.Remove(WowCS::EntityFragment::CGObject);
|
||||
//
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::FHousingDecor_C, false, WowCS::GetRawFragmentData(m_housingDecorData));
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
//}
|
||||
//
|
||||
//bool HousingDecorEntity::Create(ObjectGuid guid, Map* map, Position const& pos)
|
||||
//{
|
||||
// _Create(guid);
|
||||
// SetMap(map);
|
||||
// Relocate(pos);
|
||||
// SetObjectScale(1.0f);
|
||||
//
|
||||
// return true;
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::AddToWorld()
|
||||
//{
|
||||
// if (!IsInWorld())
|
||||
// {
|
||||
// GetMap()->GetObjectsStore().Insert<HousingDecorEntity>(this);
|
||||
// WorldObject::AddToWorld();
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::RemoveFromWorld()
|
||||
//{
|
||||
// if (IsInWorld())
|
||||
// {
|
||||
// WorldObject::RemoveFromWorld();
|
||||
// GetMap()->GetObjectsStore().Remove<HousingDecorEntity>(this);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
//{
|
||||
// if (!target)
|
||||
// return;
|
||||
//
|
||||
// uint8 updateType = UPDATETYPE_CREATE_OBJECT;
|
||||
// uint8 objectType = m_objectTypeId;
|
||||
// CreateObjectBits flags = m_updateFlag;
|
||||
//
|
||||
// ByteBuffer& buf = data->GetBuffer();
|
||||
// buf << uint8(updateType);
|
||||
// buf << GetGUID();
|
||||
// buf << uint8(objectType);
|
||||
//
|
||||
// BuildMovementUpdate(buf, flags, target);
|
||||
//
|
||||
// UF::UpdateFieldFlag fieldFlags = GetUpdateFieldFlagsFor(target);
|
||||
// std::size_t sizePos = buf.wpos();
|
||||
// buf << uint32(0);
|
||||
// buf << uint8(fieldFlags);
|
||||
// BuildEntityFragments(buf, m_entityFragments.GetIds());
|
||||
//
|
||||
// for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
|
||||
// {
|
||||
// WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
|
||||
// if (WowCS::IsIndirectFragment(fragmentId))
|
||||
// buf << uint8(1);
|
||||
//
|
||||
// WowCS::EntityFragmentInfo->SerializeCreate[static_cast<std::size_t>(m_entityFragments.Updateable.Ids[i])](
|
||||
// m_entityFragments.Updateable.Data[i], fieldFlags, buf, target, this);
|
||||
// }
|
||||
//
|
||||
// buf.put<uint32>(sizePos, buf.wpos() - sizePos - 4);
|
||||
// data->AddUpdateBlock();
|
||||
//
|
||||
// TC_LOG_DEBUG("housing", "HousingDecorEntity::BuildCreate guid={} objectType={} "
|
||||
// "fieldBlockSize={} pos=({:.1f},{:.1f},{:.1f})",
|
||||
// GetGUID().ToString(), uint32(m_objectTypeId),
|
||||
// buf.wpos() - sizePos - 4,
|
||||
// GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::BuildValuesCreate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
//{
|
||||
// // Not used ? BuildCreateUpdateBlockForPlayer handles everything via entity fragments.
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::BuildValuesUpdate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
//{
|
||||
//}
|
||||
//
|
||||
//std::string HousingDecorEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
//{
|
||||
// return "HousingDecorEntity";
|
||||
//}
|
||||
//
|
||||
//std::string HousingDecorEntity::GetDebugInfo() const
|
||||
//{
|
||||
// return Trinity::StringFormat("{}\nType: HousingDecorEntity pos: ({:.1f}, {:.1f}, {:.1f})",
|
||||
// Object::GetDebugInfo(),
|
||||
// GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
//}
|
||||
//
|
||||
//UF::UpdateFieldFlag HousingDecorEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
//{
|
||||
// return UF::UpdateFieldFlag::None;
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::ClearValuesChangesMask()
|
||||
//{
|
||||
// m_values.ClearChangesMask(&HousingDecorEntity::m_housingDecorData);
|
||||
// m_values.ClearChangesMask(&HousingDecorEntity::m_mirroredPositionData);
|
||||
// Object::ClearValuesChangesMask();
|
||||
//}
|
||||
//
|
||||
//bool HousingDecorEntity::AddToObjectUpdate()
|
||||
//{
|
||||
// GetMap()->AddUpdateObject(this);
|
||||
// return true;
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::RemoveFromObjectUpdate()
|
||||
//{
|
||||
// GetMap()->RemoveUpdateObject(this);
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::SetDecorGUID(ObjectGuid decorGuid)
|
||||
//{
|
||||
// _decorGuid = decorGuid;
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
// .ModifyValue(&UF::HousingDecorData::DecorGUID), decorGuid);
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::SetAttachParentGUID(ObjectGuid roomEntityGuid)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
// .ModifyValue(&UF::HousingDecorData::AttachParentGUID), roomEntityGuid);
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::SetTargetGameObjectGUID(ObjectGuid goGuid)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
// .ModifyValue(&UF::HousingDecorData::TargetGameObjectGUID), goGuid);
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::SetFlags(uint8 flags)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
// .ModifyValue(&UF::HousingDecorData::Flags), flags);
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::SetPersistedData(ObjectGuid houseGuid, uint8 sourceType, std::string sourceValue)
|
||||
//{
|
||||
// auto persistedRef = m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
// .ModifyValue(&UF::HousingDecorData::PersistedData, 0);
|
||||
// SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::HouseGUID), houseGuid);
|
||||
// SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceType), sourceType);
|
||||
// SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceValue), std::move(sourceValue));
|
||||
//}
|
||||
//
|
||||
//void HousingDecorEntity::SetMirroredPosition(Position const& pos, QuaternionData const& rot,
|
||||
// float scale, ObjectGuid attachParent, uint8 attachFlags)
|
||||
//{
|
||||
// auto posData = m_values.ModifyValue(&HousingDecorEntity::m_mirroredPositionData)
|
||||
// .ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
// TaggedPosition<Position::XYZ>(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()));
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rot);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachFlags);
|
||||
//}
|
||||
#include "HousingDecorEntity.h"
|
||||
#include "Log.h"
|
||||
#include "Map.h"
|
||||
#include "Player.h"
|
||||
#include "StringFormat.h"
|
||||
#include "UpdateData.h"
|
||||
|
||||
HousingDecorEntity::HousingDecorEntity()
|
||||
: WorldObject(false)
|
||||
{
|
||||
m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
|
||||
m_updateFlag.HasEntityPosition = true;
|
||||
|
||||
// remove the CGObject fragment added by the Object constructor
|
||||
m_entityFragments.Remove(WowCS::EntityFragment::CGObject);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingDecor_C, false, WowCS::GetRawFragmentData(m_housingDecorData));
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
}
|
||||
|
||||
bool HousingDecorEntity::Create(ObjectGuid guid, Map* map, Position const& pos)
|
||||
{
|
||||
_Create(guid);
|
||||
SetMap(map);
|
||||
Relocate(pos);
|
||||
SetObjectScale(1.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HousingDecorEntity::AddToWorld()
|
||||
{
|
||||
if (!IsInWorld())
|
||||
{
|
||||
GetMap()->GetObjectsStore().Insert<HousingDecorEntity>(this);
|
||||
WorldObject::AddToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
void HousingDecorEntity::RemoveFromWorld()
|
||||
{
|
||||
if (IsInWorld())
|
||||
{
|
||||
WorldObject::RemoveFromWorld();
|
||||
GetMap()->GetObjectsStore().Remove<HousingDecorEntity>(this);
|
||||
}
|
||||
}
|
||||
|
||||
void HousingDecorEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
CreateObjectBits flags = m_updateFlag;
|
||||
|
||||
ByteBuffer& buf = data->GetBuffer();
|
||||
buf << uint8(UPDATETYPE_CREATE_OBJECT);
|
||||
buf << GetGUID();
|
||||
buf << uint8(m_objectTypeId);
|
||||
|
||||
BuildMovementUpdate(buf, flags, target);
|
||||
|
||||
UF::UpdateFieldFlag fieldFlags = GetUpdateFieldFlagsFor(target);
|
||||
std::size_t sizePos = buf.wpos();
|
||||
buf << uint32(0);
|
||||
buf << uint8(fieldFlags);
|
||||
BuildEntityFragments(buf, m_entityFragments.GetIds());
|
||||
|
||||
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
|
||||
{
|
||||
WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
|
||||
if (WowCS::IsIndirectFragment(fragmentId))
|
||||
buf << uint8(1);
|
||||
|
||||
WowCS::EntityFragmentInfo->SerializeCreate[static_cast<std::size_t>(fragmentId)](
|
||||
m_entityFragments.Updateable.Data[i], fieldFlags, buf, target, this);
|
||||
}
|
||||
|
||||
buf.put<uint32>(sizePos, buf.wpos() - sizePos - 4);
|
||||
data->AddUpdateBlock();
|
||||
|
||||
TC_LOG_DEBUG("housing", "HousingDecorEntity::BuildCreate guid={} objectType={} "
|
||||
"fieldBlockSize={} pos=({:.1f},{:.1f},{:.1f})",
|
||||
GetGUID().ToString(), uint32(m_objectTypeId),
|
||||
buf.wpos() - sizePos - 4,
|
||||
GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
}
|
||||
|
||||
void HousingDecorEntity::BuildValuesCreate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
{
|
||||
// values are serialized via entity fragments
|
||||
}
|
||||
|
||||
void HousingDecorEntity::BuildValuesUpdate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
{
|
||||
}
|
||||
|
||||
std::string HousingDecorEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
{
|
||||
return "HousingDecorEntity";
|
||||
}
|
||||
|
||||
std::string HousingDecorEntity::GetDebugInfo() const
|
||||
{
|
||||
return Trinity::StringFormat("{}\nType: HousingDecorEntity pos: ({:.1f}, {:.1f}, {:.1f})",
|
||||
Object::GetDebugInfo(),
|
||||
GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
}
|
||||
|
||||
UF::UpdateFieldFlag HousingDecorEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
{
|
||||
return UF::UpdateFieldFlag::None;
|
||||
}
|
||||
|
||||
void HousingDecorEntity::ClearValuesChangesMask()
|
||||
{
|
||||
m_values.ClearChangesMask(&HousingDecorEntity::m_housingDecorData);
|
||||
m_values.ClearChangesMask(&HousingDecorEntity::m_mirroredPositionData);
|
||||
Object::ClearValuesChangesMask();
|
||||
}
|
||||
|
||||
bool HousingDecorEntity::AddToObjectUpdate()
|
||||
{
|
||||
GetMap()->AddUpdateObject(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HousingDecorEntity::RemoveFromObjectUpdate()
|
||||
{
|
||||
GetMap()->RemoveUpdateObject(this);
|
||||
}
|
||||
|
||||
void HousingDecorEntity::SetDecorGUID(ObjectGuid decorGuid)
|
||||
{
|
||||
_decorGuid = decorGuid;
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
.ModifyValue(&UF::HousingDecorData::DecorGUID), decorGuid);
|
||||
}
|
||||
|
||||
void HousingDecorEntity::SetAttachParentGUID(ObjectGuid roomEntityGuid)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
.ModifyValue(&UF::HousingDecorData::AttachParentGUID), roomEntityGuid);
|
||||
}
|
||||
|
||||
void HousingDecorEntity::SetTargetGameObjectGUID(ObjectGuid goGuid)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
.ModifyValue(&UF::HousingDecorData::TargetGameObjectGUID), goGuid);
|
||||
}
|
||||
|
||||
void HousingDecorEntity::SetFlags(uint8 flags)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
.ModifyValue(&UF::HousingDecorData::Flags), flags);
|
||||
}
|
||||
|
||||
void HousingDecorEntity::SetPersistedData(ObjectGuid houseGuid, uint8 sourceType, std::string sourceValue)
|
||||
{
|
||||
auto persistedRef = m_values.ModifyValue(&HousingDecorEntity::m_housingDecorData)
|
||||
.ModifyValue(&UF::HousingDecorData::PersistedData, 0);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceType), sourceType);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceValue), std::move(sourceValue));
|
||||
}
|
||||
|
||||
void HousingDecorEntity::SetMirroredPosition(Position const& pos, QuaternionData const& rot,
|
||||
float scale, ObjectGuid attachParent, uint8 attachFlags)
|
||||
{
|
||||
auto posData = m_values.ModifyValue(&HousingDecorEntity::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rot);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachFlags);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,58 +22,51 @@
|
||||
#include "GridObject.h"
|
||||
#include "MapObject.h"
|
||||
|
||||
//// Lightweight decor entity matching retail Object Type 18 entities with FHousingDecor_C.
|
||||
//// Used for interior door decor: a non-visual data entity that the clickable GO attaches to
|
||||
//// via TransportGUID. Retail sniff fragment list: [FHousingDecor_C, FMirroredPositionData_C].
|
||||
//// NO CGObject, NO FMeshObjectData_C, NO Tag_MeshObject ? purely a data carrier.
|
||||
////
|
||||
//// The GO (entry 575017/587318) attaches to this entity with PositionLocalSpace=(0,0,0)
|
||||
//// and AttachmentFlags=7, making the GO appear at the decor entity's world position.
|
||||
//class TC_GAME_API HousingDecorEntity final : public WorldObject, public GridObject<HousingDecorEntity>, public MapObject
|
||||
//{
|
||||
//public:
|
||||
// explicit HousingDecorEntity();
|
||||
//
|
||||
// void AddToWorld() override;
|
||||
// void RemoveFromWorld() override;
|
||||
//
|
||||
// bool Create(ObjectGuid guid, Map* map, Position const& pos);
|
||||
//
|
||||
// // Pure virtual overrides from WorldObject
|
||||
// ObjectGuid GetCreatorGUID() const override { return ObjectGuid::Empty; }
|
||||
// ObjectGuid GetOwnerGUID() const override { return ObjectGuid::Empty; }
|
||||
// uint32 GetFaction() const override { return 0; }
|
||||
//
|
||||
// void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
// void BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
// void BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
// std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
// std::string GetDebugInfo() const override;
|
||||
//
|
||||
// // Decor data setters ? call before AddToMap
|
||||
// void SetDecorGUID(ObjectGuid decorGuid);
|
||||
// void SetAttachParentGUID(ObjectGuid roomEntityGuid);
|
||||
// void SetTargetGameObjectGUID(ObjectGuid goGuid);
|
||||
// void SetFlags(uint8 flags);
|
||||
// void SetPersistedData(ObjectGuid houseGuid, uint8 sourceType = 0, std::string sourceValue = {});
|
||||
//
|
||||
// // Mirrored position data setters
|
||||
// void SetMirroredPosition(Position const& pos, QuaternionData const& rot, float scale,
|
||||
// ObjectGuid attachParent = ObjectGuid::Empty, uint8 attachFlags = 3);
|
||||
//
|
||||
// ObjectGuid GetDecorGUID() const { return _decorGuid; }
|
||||
//
|
||||
// UF::UpdateField<UF::HousingDecorData, int32(WowCS::EntityFragment::FHousingDecor_C), 0> m_housingDecorData;
|
||||
// UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
//
|
||||
//protected:
|
||||
// UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
// void ClearValuesChangesMask() override;
|
||||
// bool AddToObjectUpdate() override;
|
||||
// void RemoveFromObjectUpdate() override;
|
||||
//
|
||||
//private:
|
||||
// ObjectGuid _decorGuid;
|
||||
//};
|
||||
// Non-visual data carrier for interior door decor; the clickable GO attaches to it via TransportGUID.
|
||||
class TC_GAME_API HousingDecorEntity final : public WorldObject, public GridObject<HousingDecorEntity>, public MapObject
|
||||
{
|
||||
public:
|
||||
explicit HousingDecorEntity();
|
||||
|
||||
void AddToWorld() override;
|
||||
void RemoveFromWorld() override;
|
||||
|
||||
bool Create(ObjectGuid guid, Map* map, Position const& pos);
|
||||
|
||||
ObjectGuid GetCreatorGUID() const override { return ObjectGuid::Empty; }
|
||||
ObjectGuid GetOwnerGUID() const override { return ObjectGuid::Empty; }
|
||||
uint32 GetFaction() const override { return 0; }
|
||||
|
||||
void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
void BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
void BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
// must be called before AddToMap
|
||||
void SetDecorGUID(ObjectGuid decorGuid);
|
||||
void SetAttachParentGUID(ObjectGuid roomEntityGuid);
|
||||
void SetTargetGameObjectGUID(ObjectGuid goGuid);
|
||||
void SetFlags(uint8 flags);
|
||||
void SetPersistedData(ObjectGuid houseGuid, uint8 sourceType = 0, std::string sourceValue = {});
|
||||
|
||||
void SetMirroredPosition(Position const& pos, QuaternionData const& rot, float scale,
|
||||
ObjectGuid attachParent = ObjectGuid::Empty, uint8 attachFlags = 3);
|
||||
|
||||
ObjectGuid GetDecorGUID() const { return _decorGuid; }
|
||||
|
||||
UF::UpdateField<UF::HousingDecorData, int32(WowCS::EntityFragment::FHousingDecor_C), 0> m_housingDecorData;
|
||||
UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
|
||||
protected:
|
||||
UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
void ClearValuesChangesMask() override;
|
||||
bool AddToObjectUpdate() override;
|
||||
void RemoveFromObjectUpdate() override;
|
||||
|
||||
private:
|
||||
ObjectGuid _decorGuid;
|
||||
};
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_DECOR_ENTITY_H
|
||||
|
||||
|
||||
@@ -15,89 +15,82 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include "HousingMirrorEntity.h"
|
||||
//#include "Map.h"
|
||||
//#include "StringFormat.h"
|
||||
//#include "UpdateData.h"
|
||||
//
|
||||
//HousingMirrorEntity::HousingMirrorEntity(Map* map, ObjectGuid guid) : _map(map)
|
||||
//{
|
||||
// _Create(guid);
|
||||
//
|
||||
// // Retail emits these with objectType = 18 (TYPEID_HOUSING_ENTITY).
|
||||
// m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
//
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false,
|
||||
// WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
//}
|
||||
//
|
||||
//HousingMirrorEntity::~HousingMirrorEntity() = default;
|
||||
//
|
||||
//void HousingMirrorEntity::InitPositionData(ObjectGuid attachParent,
|
||||
// Position const& position, QuaternionData const& rotation,
|
||||
// float scale, uint8 attachmentFlags, Tagging tagging)
|
||||
//{
|
||||
// auto posData = m_values.ModifyValue(&HousingMirrorEntity::m_mirroredPositionData)
|
||||
// .ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
// TaggedPosition<Position::XYZ>(position.GetPositionX(), position.GetPositionY(), position.GetPositionZ()));
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rotation);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachmentFlags);
|
||||
//
|
||||
// // Retail (idx 9984 in dump_12.0.1.66838_2026-04-15_09-35-59) emits 4
|
||||
// // Group A mirrors per plot. The Type-9 (Base) root mirror carries both
|
||||
// // Tag_HouseExteriorPiece and Tag_HouseExteriorRoot; the other three
|
||||
// // (Roof / Door / Window) carry Tag_HouseExteriorPiece only. Group B
|
||||
// // per-piece mesh mirrors carry neither tag.
|
||||
// switch (tagging)
|
||||
// {
|
||||
// case Tagging::PieceAndRoot:
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, false);
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorRoot, false);
|
||||
// break;
|
||||
// case Tagging::Piece:
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, false);
|
||||
// break;
|
||||
// case Tagging::None:
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void HousingMirrorEntity::ClearUpdateMask(bool remove)
|
||||
//{
|
||||
// m_values.ClearChangesMask(&HousingMirrorEntity::m_mirroredPositionData);
|
||||
// BaseEntity::ClearUpdateMask(remove);
|
||||
//}
|
||||
//
|
||||
//std::string HousingMirrorEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
//{
|
||||
// return "HousingMirror";
|
||||
//}
|
||||
//
|
||||
//void HousingMirrorEntity::BuildUpdate(UpdateDataMapType& /*data_map*/)
|
||||
//{
|
||||
// // Mirror position is set once at spawn and does not change. No-op update.
|
||||
// ClearUpdateMask(false);
|
||||
//}
|
||||
//
|
||||
//std::string HousingMirrorEntity::GetDebugInfo() const
|
||||
//{
|
||||
// return Trinity::StringFormat("{}\nType: HousingMirrorEntity", BaseEntity::GetDebugInfo());
|
||||
//}
|
||||
//
|
||||
//UF::UpdateFieldFlag HousingMirrorEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
//{
|
||||
// return UF::UpdateFieldFlag::None;
|
||||
//}
|
||||
//
|
||||
//bool HousingMirrorEntity::AddToObjectUpdate()
|
||||
//{
|
||||
// // Mirror fields are static once set; it never asks the map to queue it.
|
||||
// return false;
|
||||
//}
|
||||
//
|
||||
//void HousingMirrorEntity::RemoveFromObjectUpdate()
|
||||
//{
|
||||
//}
|
||||
#include "HousingMirrorEntity.h"
|
||||
#include "StringFormat.h"
|
||||
|
||||
HousingMirrorEntity::HousingMirrorEntity(Map* /*map*/, ObjectGuid guid)
|
||||
{
|
||||
_Create(guid);
|
||||
|
||||
m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false,
|
||||
WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
}
|
||||
|
||||
HousingMirrorEntity::~HousingMirrorEntity() = default;
|
||||
|
||||
void HousingMirrorEntity::InitPositionData(ObjectGuid attachParent,
|
||||
Position const& position, QuaternionData const& rotation,
|
||||
float scale, uint8 attachmentFlags, Tagging tagging)
|
||||
{
|
||||
auto posData = m_values.ModifyValue(&HousingMirrorEntity::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(position.GetPositionX(), position.GetPositionY(), position.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rotation);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachmentFlags);
|
||||
|
||||
switch (tagging)
|
||||
{
|
||||
case Tagging::PieceAndRoot:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, false);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorRoot, false);
|
||||
break;
|
||||
case Tagging::Piece:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, false);
|
||||
break;
|
||||
case Tagging::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HousingMirrorEntity::ClearUpdateMask(bool remove)
|
||||
{
|
||||
m_values.ClearChangesMask(&HousingMirrorEntity::m_mirroredPositionData);
|
||||
BaseEntity::ClearUpdateMask(remove);
|
||||
}
|
||||
|
||||
std::string HousingMirrorEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
{
|
||||
return "HousingMirror";
|
||||
}
|
||||
|
||||
void HousingMirrorEntity::BuildUpdate(UpdateDataMapType& /*data_map*/)
|
||||
{
|
||||
// mirror position is set once at spawn and never changes
|
||||
ClearUpdateMask(false);
|
||||
}
|
||||
|
||||
std::string HousingMirrorEntity::GetDebugInfo() const
|
||||
{
|
||||
return Trinity::StringFormat("{}\nType: HousingMirrorEntity", BaseEntity::GetDebugInfo());
|
||||
}
|
||||
|
||||
UF::UpdateFieldFlag HousingMirrorEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
{
|
||||
return UF::UpdateFieldFlag::None;
|
||||
}
|
||||
|
||||
bool HousingMirrorEntity::AddToObjectUpdate()
|
||||
{
|
||||
// fields are static once set; the mirror is never queued for updates
|
||||
return false;
|
||||
}
|
||||
|
||||
void HousingMirrorEntity::RemoveFromObjectUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,83 +1,61 @@
|
||||
///*
|
||||
//* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
//*
|
||||
//* This program is free software; you can redistribute it and/or modify it
|
||||
//* under the terms of the GNU General Public License as published by the
|
||||
//* Free Software Foundation; either version 2 of the License, or (at your
|
||||
//* option) any later version.
|
||||
//*
|
||||
//* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
//* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
//* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
//* more details.
|
||||
//*
|
||||
//* You should have received a copy of the GNU General Public License along
|
||||
//* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//*/
|
||||
//
|
||||
//#ifndef TRINITYCORE_HOUSING_MIRROR_ENTITY_H
|
||||
//#define TRINITYCORE_HOUSING_MIRROR_ENTITY_H
|
||||
//
|
||||
//#include "BaseEntity.h"
|
||||
//#include "Position.h"
|
||||
//#include "QuaternionData.h"
|
||||
//
|
||||
//class Map;
|
||||
//
|
||||
//// Lightweight position-carrier entity paired with a housing MeshObject or
|
||||
//// HousingPlayerHouse. Retail emits these as HighGuid::Entity (57) with
|
||||
//// objectType=18 and a single FMirroredPositionData_C fragment (plus tags).
|
||||
//// Their role in the client is to place the plot/house in map-space spatial
|
||||
//// indices used by the world-map icon picker and other rendering paths.
|
||||
////
|
||||
//// Sniff-verified at dump_12.0.1.66838_2026-04-15_09-35-59 idx 9984:
|
||||
//// - Fragments: FMirroredPositionData_C [+ Tag_HouseExteriorPiece / Root]
|
||||
//// - movement block: 3 CreateObjectBits flag bytes + uint32 PauseTimes=0
|
||||
//// - valuesSize: 44 when AttachParent is HighGuid::Housing (short packed GUID)
|
||||
//// - No CGObject, no FMeshObjectData_C ? pure mirror
|
||||
//class TC_GAME_API HousingMirrorEntity final : public BaseEntity
|
||||
//{
|
||||
//public:
|
||||
// HousingMirrorEntity(Map* map, ObjectGuid guid);
|
||||
// ~HousingMirrorEntity();
|
||||
//
|
||||
// // Tag set this mirror carries ? controls which entity-fragment Tag_*
|
||||
// // entries are added when InitPositionData is called.
|
||||
// // None ? Group B per-piece mesh anchor (no tags)
|
||||
// // Piece ? Group A non-root piece (Tag_HouseExteriorPiece only)
|
||||
// // PieceAndRoot ? Group A root piece (Tag_HouseExteriorPiece +
|
||||
// // Tag_HouseExteriorRoot)
|
||||
// enum class Tagging : uint8
|
||||
// {
|
||||
// None,
|
||||
// Piece,
|
||||
// PieceAndRoot,
|
||||
// };
|
||||
//
|
||||
// // Populate the mirror's position fragment.
|
||||
// // attachParent: the housing entity this mirror is attached to
|
||||
// // (room identity for Group A, MeshObject for Group B)
|
||||
// // position/rotation/scale: local-space to attachParent
|
||||
// // attachmentFlags: usually 3 (sniff-typical)
|
||||
// // tagging: which Tag_HouseExterior* fragments to attach (see enum above)
|
||||
// void InitPositionData(ObjectGuid attachParent,
|
||||
// Position const& position, QuaternionData const& rotation,
|
||||
// float scale, uint8 attachmentFlags, Tagging tagging);
|
||||
//
|
||||
// void ClearUpdateMask(bool remove) override;
|
||||
// std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
// void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
// std::string GetDebugInfo() const override;
|
||||
//
|
||||
// UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
//
|
||||
//protected:
|
||||
// UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
// bool AddToObjectUpdate() override;
|
||||
// void RemoveFromObjectUpdate() override;
|
||||
//
|
||||
//private:
|
||||
// Map* _map;
|
||||
//};
|
||||
//
|
||||
//#endif // TRINITYCORE_HOUSING_MIRROR_ENTITY_H
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_MIRROR_ENTITY_H
|
||||
#define TRINITYCORE_HOUSING_MIRROR_ENTITY_H
|
||||
|
||||
#include "BaseEntity.h"
|
||||
#include "Position.h"
|
||||
#include "QuaternionData.h"
|
||||
|
||||
class Map;
|
||||
|
||||
// Position-carrier entity paired with housing MeshObjects / player houses.
|
||||
class TC_GAME_API HousingMirrorEntity final : public BaseEntity
|
||||
{
|
||||
public:
|
||||
HousingMirrorEntity(Map* map, ObjectGuid guid);
|
||||
~HousingMirrorEntity();
|
||||
|
||||
// which Tag_HouseExterior* fragments InitPositionData attaches
|
||||
enum class Tagging : uint8
|
||||
{
|
||||
None,
|
||||
Piece,
|
||||
PieceAndRoot,
|
||||
};
|
||||
|
||||
// position/rotation/scale are local-space to attachParent
|
||||
void InitPositionData(ObjectGuid attachParent,
|
||||
Position const& position, QuaternionData const& rotation,
|
||||
float scale, uint8 attachmentFlags, Tagging tagging);
|
||||
|
||||
void ClearUpdateMask(bool remove) override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
|
||||
protected:
|
||||
UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
bool AddToObjectUpdate() override;
|
||||
void RemoveFromObjectUpdate() override;
|
||||
};
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_MIRROR_ENTITY_H
|
||||
|
||||
|
||||
@@ -1,138 +1,134 @@
|
||||
///*
|
||||
//* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
//*
|
||||
//* This program is free software; you can redistribute it and/or modify it
|
||||
//* under the terms of the GNU General Public License as published by the
|
||||
//* Free Software Foundation; either version 2 of the License, or (at your
|
||||
//* option) any later version.
|
||||
//*
|
||||
//* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
//* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
//* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
//* more details.
|
||||
//*
|
||||
//* You should have received a copy of the GNU General Public License along
|
||||
//* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//*/
|
||||
//
|
||||
//#include "HousingNeighborhoodMirrorEntity.h"
|
||||
//#include "Map.h"
|
||||
//#include "Player.h"
|
||||
//#include "StringFormat.h"
|
||||
//#include "UpdateData.h"
|
||||
//#include "WorldSession.h"
|
||||
//
|
||||
//HousingNeighborhoodMirrorEntity::HousingNeighborhoodMirrorEntity(WorldSession* session, ObjectGuid guid) : _session(session)
|
||||
//{
|
||||
// _Create(guid);
|
||||
//
|
||||
// // Retail serialises Housing/4 (NeighborhoodMirror) CREATE blocks with
|
||||
// // objectType byte = 18 (TYPEID_HOUSING_ENTITY). Sniff-verified at
|
||||
// // dump_12.0.1.66838_2026-04-15_09-35-59 packet idx 9984. Without this,
|
||||
// // BaseEntity's default leaves m_objectTypeId = NUM_CLIENT_OBJECT_TYPES
|
||||
// // (invalid sentinel), which the client's CREATE dispatcher may silently
|
||||
// // drop from its entity registry.
|
||||
// m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
//
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::FNeighborhoodMirrorData_C, false, WowCS::GetRawFragmentData(m_neighborhoodMirrorData));
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::ClearUpdateMask(bool remove)
|
||||
//{
|
||||
// m_values.ClearChangesMask(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData);
|
||||
// BaseEntity::ClearUpdateMask(remove);
|
||||
//}
|
||||
//
|
||||
//std::string HousingNeighborhoodMirrorEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
//{
|
||||
// return "HousingNeighborhoodMirror";
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::BuildUpdate(UpdateDataMapType& data_map)
|
||||
//{
|
||||
// BuildUpdateChangesMask();
|
||||
//
|
||||
// if (Player* owner = _session->GetPlayer())
|
||||
// BuildFieldsUpdate(owner, data_map);
|
||||
//
|
||||
// ClearUpdateMask(false);
|
||||
//}
|
||||
//
|
||||
//std::string HousingNeighborhoodMirrorEntity::GetDebugInfo() const
|
||||
//{
|
||||
// return Trinity::StringFormat("{}\nType: HousingNeighborhoodMirrorEntity", BaseEntity::GetDebugInfo());
|
||||
//}
|
||||
//
|
||||
//UF::UpdateFieldFlag HousingNeighborhoodMirrorEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
//{
|
||||
// return UF::UpdateFieldFlag::None;
|
||||
//}
|
||||
//
|
||||
//bool HousingNeighborhoodMirrorEntity::AddToObjectUpdate()
|
||||
//{
|
||||
// if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
// {
|
||||
// owner->GetMap()->AddUpdateObject(this);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::RemoveFromObjectUpdate()
|
||||
//{
|
||||
// if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
// owner->GetMap()->RemoveUpdateObject(this);
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::SendUpdateToPlayer(Player* player)
|
||||
//{
|
||||
// BuildUpdateChangesMask();
|
||||
// BaseEntity::SendUpdateToPlayer(player);
|
||||
// ClearUpdateMask(true);
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::SendCreateToPlayer(Player* player)
|
||||
//{
|
||||
// BuildUpdateChangesMask();
|
||||
// UpdateData upd(player->GetMapId());
|
||||
// WorldPacket packet;
|
||||
// BuildCreateUpdateBlockForPlayer(&upd, player);
|
||||
// upd.BuildPacket(&packet);
|
||||
// player->SendDirectMessage(&packet);
|
||||
// ClearUpdateMask(true);
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::SetName(std::string const& name)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Name), name);
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::SetOwnerGUID(ObjectGuid ownerGuid)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::OwnerGUID), ownerGuid);
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::AddHouse(ObjectGuid houseGuid, ObjectGuid ownerGuid)
|
||||
//{
|
||||
// UF::PlayerHouseInfo& houseInfo = AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Houses));
|
||||
// houseInfo.HouseGUID = houseGuid;
|
||||
// houseInfo.OwnerGUID = ownerGuid;
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::ClearHouses()
|
||||
//{
|
||||
// ClearDynamicUpdateFieldValues(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Houses));
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::AddManager(ObjectGuid bnetAccountGuid, ObjectGuid playerGuid)
|
||||
//{
|
||||
// UF::HousingOwner& manager = AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Managers));
|
||||
// manager.BnetAccountGUID = bnetAccountGuid;
|
||||
// manager.PlayerGUID = playerGuid;
|
||||
//}
|
||||
//
|
||||
//void HousingNeighborhoodMirrorEntity::ClearManagers()
|
||||
//{
|
||||
// ClearDynamicUpdateFieldValues(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Managers));
|
||||
//}
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HousingNeighborhoodMirrorEntity.h"
|
||||
#include "Map.h"
|
||||
#include "Player.h"
|
||||
#include "StringFormat.h"
|
||||
#include "UpdateData.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
HousingNeighborhoodMirrorEntity::HousingNeighborhoodMirrorEntity(WorldSession* session, ObjectGuid guid) : _session(session)
|
||||
{
|
||||
_Create(guid);
|
||||
|
||||
// BaseEntity's default m_objectTypeId is an invalid sentinel the client drops silently
|
||||
m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FNeighborhoodMirrorData_C, false, WowCS::GetRawFragmentData(m_neighborhoodMirrorData));
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::ClearUpdateMask(bool remove)
|
||||
{
|
||||
m_values.ClearChangesMask(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData);
|
||||
BaseEntity::ClearUpdateMask(remove);
|
||||
}
|
||||
|
||||
std::string HousingNeighborhoodMirrorEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
{
|
||||
return "HousingNeighborhoodMirror";
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::BuildUpdate(UpdateDataMapType& data_map)
|
||||
{
|
||||
BuildUpdateChangesMask();
|
||||
|
||||
if (Player* owner = _session->GetPlayer())
|
||||
BuildFieldsUpdate(owner, data_map);
|
||||
|
||||
ClearUpdateMask(false);
|
||||
}
|
||||
|
||||
std::string HousingNeighborhoodMirrorEntity::GetDebugInfo() const
|
||||
{
|
||||
return Trinity::StringFormat("{}\nType: HousingNeighborhoodMirrorEntity", BaseEntity::GetDebugInfo());
|
||||
}
|
||||
|
||||
UF::UpdateFieldFlag HousingNeighborhoodMirrorEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
{
|
||||
return UF::UpdateFieldFlag::None;
|
||||
}
|
||||
|
||||
bool HousingNeighborhoodMirrorEntity::AddToObjectUpdate()
|
||||
{
|
||||
if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
{
|
||||
owner->GetMap()->AddUpdateObject(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::RemoveFromObjectUpdate()
|
||||
{
|
||||
if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
owner->GetMap()->RemoveUpdateObject(this);
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::SendUpdateToPlayer(Player* player)
|
||||
{
|
||||
BuildUpdateChangesMask();
|
||||
BaseEntity::SendUpdateToPlayer(player);
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::SendCreateToPlayer(Player* player)
|
||||
{
|
||||
BuildUpdateChangesMask();
|
||||
UpdateData upd(player->GetMapId());
|
||||
WorldPacket packet;
|
||||
BuildCreateUpdateBlockForPlayer(&upd, player);
|
||||
upd.BuildPacket(&packet);
|
||||
player->SendDirectMessage(&packet);
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::SetName(std::string const& name)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Name), name);
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::SetOwnerGUID(ObjectGuid ownerGuid)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::OwnerGUID), ownerGuid);
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::AddHouse(ObjectGuid houseGuid, ObjectGuid ownerGuid)
|
||||
{
|
||||
UF::PlayerHouseInfo& houseInfo = AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Houses));
|
||||
houseInfo.HouseGUID = houseGuid;
|
||||
houseInfo.OwnerGUID = ownerGuid;
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::ClearHouses()
|
||||
{
|
||||
ClearDynamicUpdateFieldValues(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Houses));
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::AddManager(ObjectGuid bnetAccountGuid, ObjectGuid playerGuid)
|
||||
{
|
||||
UF::HousingOwner& manager = AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Managers));
|
||||
manager.BnetAccountGUID = bnetAccountGuid;
|
||||
manager.PlayerGUID = playerGuid;
|
||||
}
|
||||
|
||||
void HousingNeighborhoodMirrorEntity::ClearManagers()
|
||||
{
|
||||
ClearDynamicUpdateFieldValues(m_values.ModifyValue(&HousingNeighborhoodMirrorEntity::m_neighborhoodMirrorData).ModifyValue(&UF::NeighborhoodMirrorData::Managers));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +1,62 @@
|
||||
///*
|
||||
//* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
//*
|
||||
//* This program is free software; you can redistribute it and/or modify it
|
||||
//* under the terms of the GNU General Public License as published by the
|
||||
//* Free Software Foundation; either version 2 of the License, or (at your
|
||||
//* option) any later version.
|
||||
//*
|
||||
//* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
//* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
//* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
//* more details.
|
||||
//*
|
||||
//* You should have received a copy of the GNU General Public License along
|
||||
//* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//*/
|
||||
//
|
||||
//#ifndef TRINITYCORE_HOUSING_NEIGHBORHOOD_MIRROR_ENTITY_H
|
||||
//#define TRINITYCORE_HOUSING_NEIGHBORHOOD_MIRROR_ENTITY_H
|
||||
//
|
||||
//#include "BaseEntity.h"
|
||||
//
|
||||
//class WorldSession;
|
||||
//
|
||||
//// Separate entity carrying FNeighborhoodMirrorData_C fragment.
|
||||
//// Retail sends this on a Housing/4 GUID (subType=4), NOT on the BNetAccount entity.
|
||||
//class HousingNeighborhoodMirrorEntity final : public BaseEntity
|
||||
//{
|
||||
//public:
|
||||
// explicit HousingNeighborhoodMirrorEntity(WorldSession* session, ObjectGuid guid);
|
||||
//
|
||||
// void ClearUpdateMask(bool remove) override;
|
||||
// std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
// void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
// std::string GetDebugInfo() const override;
|
||||
//
|
||||
// void SendUpdateToPlayer(Player* player);
|
||||
// // Force-emit a CREATE_OBJECT block (bypasses HaveAtClient). Use this for
|
||||
// // wholesale re-pushes (ClearHouses + re-populate) ? retail-sniff-verified
|
||||
// // to be >=80% of Housing/4 updates. The client's map-icon refresh path
|
||||
// // only fires on CREATE; VALUES_UPDATE applies the field changes silently.
|
||||
// // See 63_values_update_changemask.py for the rule.
|
||||
// void SendCreateToPlayer(Player* player);
|
||||
//
|
||||
// // Reset the entity GUID (must be called before AddToWorld)
|
||||
// void ResetGuid(ObjectGuid newGuid) { _Create(newGuid); }
|
||||
//
|
||||
// // Neighborhood mirror setters
|
||||
// void SetName(std::string const& name);
|
||||
// void SetOwnerGUID(ObjectGuid ownerGuid);
|
||||
// void AddHouse(ObjectGuid houseGuid, ObjectGuid ownerGuid);
|
||||
// void ClearHouses();
|
||||
// void AddManager(ObjectGuid bnetAccountGuid, ObjectGuid playerGuid);
|
||||
// void ClearManagers();
|
||||
//
|
||||
// UF::UpdateField<UF::NeighborhoodMirrorData, int32(WowCS::EntityFragment::FNeighborhoodMirrorData_C), 0> m_neighborhoodMirrorData;
|
||||
//
|
||||
//protected:
|
||||
// UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
// bool AddToObjectUpdate() override;
|
||||
// void RemoveFromObjectUpdate() override;
|
||||
//
|
||||
//private:
|
||||
// WorldSession* _session;
|
||||
//};
|
||||
//
|
||||
//#endif // TRINITYCORE_HOUSING_NEIGHBORHOOD_MIRROR_ENTITY_H
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_NEIGHBORHOOD_MIRROR_ENTITY_H
|
||||
#define TRINITYCORE_HOUSING_NEIGHBORHOOD_MIRROR_ENTITY_H
|
||||
|
||||
#include "BaseEntity.h"
|
||||
|
||||
class WorldSession;
|
||||
|
||||
// FNeighborhoodMirrorData_C carrier entity on a Housing/4 GUID (subType 4).
|
||||
class HousingNeighborhoodMirrorEntity final : public BaseEntity
|
||||
{
|
||||
public:
|
||||
explicit HousingNeighborhoodMirrorEntity(WorldSession* session, ObjectGuid guid);
|
||||
|
||||
void ClearUpdateMask(bool remove) override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
void SendUpdateToPlayer(Player* player);
|
||||
// force-emit CREATE_OBJECT (bypasses HaveAtClient); map-icon refresh only fires on CREATE
|
||||
void SendCreateToPlayer(Player* player);
|
||||
|
||||
// must be called before AddToWorld
|
||||
void ResetGuid(ObjectGuid newGuid) { _Create(newGuid); }
|
||||
|
||||
void SetName(std::string const& name);
|
||||
void SetOwnerGUID(ObjectGuid ownerGuid);
|
||||
void AddHouse(ObjectGuid houseGuid, ObjectGuid ownerGuid);
|
||||
void ClearHouses();
|
||||
void AddManager(ObjectGuid bnetAccountGuid, ObjectGuid playerGuid);
|
||||
void ClearManagers();
|
||||
|
||||
UF::UpdateField<UF::NeighborhoodMirrorData, int32(WowCS::EntityFragment::FNeighborhoodMirrorData_C), 0> m_neighborhoodMirrorData;
|
||||
|
||||
protected:
|
||||
UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
bool AddToObjectUpdate() override;
|
||||
void RemoveFromObjectUpdate() override;
|
||||
|
||||
private:
|
||||
WorldSession* _session;
|
||||
};
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_NEIGHBORHOOD_MIRROR_ENTITY_H
|
||||
|
||||
|
||||
@@ -1,156 +1,138 @@
|
||||
///*
|
||||
//* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
//*
|
||||
//* This program is free software; you can redistribute it and/or modify it
|
||||
//* under the terms of the GNU General Public License as published by the
|
||||
//* Free Software Foundation; either version 2 of the License, or (at your
|
||||
//* option) any later version.
|
||||
//*
|
||||
//* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
//* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
//* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
//* more details.
|
||||
//*
|
||||
//* You should have received a copy of the GNU General Public License along
|
||||
//* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//*/
|
||||
//
|
||||
//#include "HousingPlayerHouseEntity.h"
|
||||
//#include "Map.h"
|
||||
//#include "Player.h"
|
||||
//#include "StringFormat.h"
|
||||
//#include "UpdateData.h"
|
||||
//#include "WorldSession.h"
|
||||
//
|
||||
//HousingPlayerHouseEntity::HousingPlayerHouseEntity(WorldSession* session, ObjectGuid guid) : _session(session)
|
||||
//{
|
||||
// _Create(guid);
|
||||
//
|
||||
// // Retail serialises Housing/3 (HousingPlayerHouse) CREATE blocks with
|
||||
// // objectType byte = 18 (TYPEID_HOUSING_ENTITY). Sniff-verified at
|
||||
// // dump_12.0.1.66838_2026-04-15_09-35-59 packet idx 9984. Without this,
|
||||
// // BaseEntity's default leaves m_objectTypeId = NUM_CLIENT_OBJECT_TYPES
|
||||
// // (invalid sentinel), which the client's CREATE dispatcher may silently
|
||||
// // drop from its entity registry ? breaking the icon picker's
|
||||
// // HouseGUID ? entity ? BnetAccount lookup for the own plot.
|
||||
// m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
//
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::FHousingPlayerHouse_C, false, WowCS::GetRawFragmentData(m_housingPlayerHouseData));
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::ClearUpdateMask(bool remove)
|
||||
//{
|
||||
// m_values.ClearChangesMask(&HousingPlayerHouseEntity::m_housingPlayerHouseData);
|
||||
// BaseEntity::ClearUpdateMask(remove);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
//{
|
||||
// // Diagnostic: dump the field values at CREATE time so post-deploy sniff
|
||||
// // verification can be cross-checked without decoding hex by hand. Only
|
||||
// // logs when this specific entity (Housing/3) emits a CREATE.
|
||||
// UF::HousingPlayerHouseData const& d = *m_housingPlayerHouseData;
|
||||
// TC_LOG_INFO("housing",
|
||||
// "HousingPlayerHouseEntity::BuildCreate guid={} -> target={} "
|
||||
// "Bnet={} Plot={} Level={} Favor={} Budgets[I={} E={} R={} F={}] EntityGUID={}",
|
||||
// GetGUID().ToString(), target ? target->GetGUID().ToString() : "<null>",
|
||||
// d.BnetAccount->ToString(), int32(d.PlotIndex), uint32(d.Level), uint64(d.Favor),
|
||||
// uint32(d.InteriorDecorPlacementBudget), uint32(d.ExteriorDecorPlacementBudget),
|
||||
// uint32(d.RoomPlacementBudget), uint32(d.ExteriorFixtureBudget),
|
||||
// d.EntityGUID->ToString());
|
||||
//
|
||||
// BaseEntity::BuildCreateUpdateBlockForPlayer(data, target);
|
||||
//}
|
||||
//
|
||||
//std::string HousingPlayerHouseEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
//{
|
||||
// return "HousingPlayerHouse";
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::BuildUpdate(UpdateDataMapType& data_map)
|
||||
//{
|
||||
// BuildUpdateChangesMask();
|
||||
//
|
||||
// if (Player* owner = _session->GetPlayer())
|
||||
// BuildFieldsUpdate(owner, data_map);
|
||||
//
|
||||
// ClearUpdateMask(false);
|
||||
//}
|
||||
//
|
||||
//std::string HousingPlayerHouseEntity::GetDebugInfo() const
|
||||
//{
|
||||
// return Trinity::StringFormat("{}\nType: HousingPlayerHouseEntity", BaseEntity::GetDebugInfo());
|
||||
//}
|
||||
//
|
||||
//UF::UpdateFieldFlag HousingPlayerHouseEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
//{
|
||||
// return UF::UpdateFieldFlag::None;
|
||||
//}
|
||||
//
|
||||
//bool HousingPlayerHouseEntity::AddToObjectUpdate()
|
||||
//{
|
||||
// if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
// {
|
||||
// owner->GetMap()->AddUpdateObject(this);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::RemoveFromObjectUpdate()
|
||||
//{
|
||||
// if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
// owner->GetMap()->RemoveUpdateObject(this);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SendUpdateToPlayer(Player* player)
|
||||
//{
|
||||
// BuildUpdateChangesMask();
|
||||
// BaseEntity::SendUpdateToPlayer(player);
|
||||
// ClearUpdateMask(true);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SendCreateToPlayer(Player* player)
|
||||
//{
|
||||
// BuildUpdateChangesMask();
|
||||
// UpdateData upd(player->GetMapId());
|
||||
// WorldPacket packet;
|
||||
// BuildCreateUpdateBlockForPlayer(&upd, player);
|
||||
// upd.BuildPacket(&packet);
|
||||
// player->SendDirectMessage(&packet);
|
||||
// ClearUpdateMask(true);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SetPlotIndex(int32 plotIndex)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::PlotIndex), plotIndex);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SetLevel(uint32 level)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::Level), level);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SetFavor(uint64 favor)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::Favor), favor);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SetBudgets(uint32 interiorDecor, uint32 exteriorDecor, uint32 room, uint32 fixture)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::InteriorDecorPlacementBudget), interiorDecor);
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::ExteriorDecorPlacementBudget), exteriorDecor);
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::RoomPlacementBudget), room);
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::ExteriorFixtureBudget), fixture);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SetBnetAccount(ObjectGuid bnetAccountGuid)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::BnetAccount), bnetAccountGuid);
|
||||
//}
|
||||
//
|
||||
//void HousingPlayerHouseEntity::SetEntityGUID(ObjectGuid entityGuid)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::EntityGUID), entityGuid);
|
||||
//}
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HousingPlayerHouseEntity.h"
|
||||
#include "Map.h"
|
||||
#include "Player.h"
|
||||
#include "StringFormat.h"
|
||||
#include "UpdateData.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
HousingPlayerHouseEntity::HousingPlayerHouseEntity(WorldSession* session, ObjectGuid guid) : _session(session)
|
||||
{
|
||||
_Create(guid);
|
||||
|
||||
// BaseEntity's default m_objectTypeId is an invalid sentinel the client drops silently
|
||||
m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingPlayerHouse_C, false, WowCS::GetRawFragmentData(m_housingPlayerHouseData));
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::ClearUpdateMask(bool remove)
|
||||
{
|
||||
m_values.ClearChangesMask(&HousingPlayerHouseEntity::m_housingPlayerHouseData);
|
||||
BaseEntity::ClearUpdateMask(remove);
|
||||
}
|
||||
|
||||
std::string HousingPlayerHouseEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
{
|
||||
return "HousingPlayerHouse";
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::BuildUpdate(UpdateDataMapType& data_map)
|
||||
{
|
||||
BuildUpdateChangesMask();
|
||||
|
||||
if (Player* owner = _session->GetPlayer())
|
||||
BuildFieldsUpdate(owner, data_map);
|
||||
|
||||
ClearUpdateMask(false);
|
||||
}
|
||||
|
||||
std::string HousingPlayerHouseEntity::GetDebugInfo() const
|
||||
{
|
||||
return Trinity::StringFormat("{}\nType: HousingPlayerHouseEntity", BaseEntity::GetDebugInfo());
|
||||
}
|
||||
|
||||
UF::UpdateFieldFlag HousingPlayerHouseEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
{
|
||||
return UF::UpdateFieldFlag::None;
|
||||
}
|
||||
|
||||
bool HousingPlayerHouseEntity::AddToObjectUpdate()
|
||||
{
|
||||
if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
{
|
||||
owner->GetMap()->AddUpdateObject(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::RemoveFromObjectUpdate()
|
||||
{
|
||||
if (Player* owner = _session->GetPlayer(); owner && owner->IsInWorld())
|
||||
owner->GetMap()->RemoveUpdateObject(this);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SendUpdateToPlayer(Player* player)
|
||||
{
|
||||
BuildUpdateChangesMask();
|
||||
BaseEntity::SendUpdateToPlayer(player);
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SendCreateToPlayer(Player* player)
|
||||
{
|
||||
BuildUpdateChangesMask();
|
||||
UpdateData upd(player->GetMapId());
|
||||
WorldPacket packet;
|
||||
BuildCreateUpdateBlockForPlayer(&upd, player);
|
||||
upd.BuildPacket(&packet);
|
||||
player->SendDirectMessage(&packet);
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SetPlotIndex(int32 plotIndex)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::PlotIndex), plotIndex);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SetLevel(uint32 level)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::Level), level);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SetFavor(uint64 favor)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::Favor), favor);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SetBudgets(uint32 interiorDecor, uint32 exteriorDecor, uint32 room, uint32 fixture)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::InteriorDecorPlacementBudget), interiorDecor);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::ExteriorDecorPlacementBudget), exteriorDecor);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::RoomPlacementBudget), room);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::ExteriorFixtureBudget), fixture);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SetBnetAccount(ObjectGuid bnetAccountGuid)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::BnetAccount), bnetAccountGuid);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SetCosmeticOwner(ObjectGuid ownerGuid)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::CosmeticOwner), ownerGuid);
|
||||
}
|
||||
|
||||
void HousingPlayerHouseEntity::SetEntityGUID(ObjectGuid entityGuid)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingPlayerHouseEntity::m_housingPlayerHouseData).ModifyValue(&UF::HousingPlayerHouseData::EntityGUID), entityGuid);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +1,64 @@
|
||||
///*
|
||||
//* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
//*
|
||||
//* This program is free software; you can redistribute it and/or modify it
|
||||
//* under the terms of the GNU General Public License as published by the
|
||||
//* Free Software Foundation; either version 2 of the License, or (at your
|
||||
//* option) any later version.
|
||||
//*
|
||||
//* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
//* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
//* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
//* more details.
|
||||
//*
|
||||
//* You should have received a copy of the GNU General Public License along
|
||||
//* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//*/
|
||||
//
|
||||
//#ifndef TRINITYCORE_HOUSING_PLAYER_HOUSE_ENTITY_H
|
||||
//#define TRINITYCORE_HOUSING_PLAYER_HOUSE_ENTITY_H
|
||||
//
|
||||
//#include "BaseEntity.h"
|
||||
//
|
||||
//class WorldSession;
|
||||
//
|
||||
//// Separate entity carrying FHousingPlayerHouse_C fragment.
|
||||
//// Retail sends this on a Housing/3 GUID (subType=3, arg2=7), NOT on the BNetAccount entity.
|
||||
//class HousingPlayerHouseEntity final : public BaseEntity
|
||||
//{
|
||||
//public:
|
||||
// explicit HousingPlayerHouseEntity(WorldSession* session, ObjectGuid guid);
|
||||
//
|
||||
// void ClearUpdateMask(bool remove) override;
|
||||
// std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
// void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
// std::string GetDebugInfo() const override;
|
||||
//
|
||||
// void SendUpdateToPlayer(Player* player);
|
||||
// // Force-emit CREATE_OBJECT (bypasses HaveAtClient). See Housing/4 twin
|
||||
// // for rationale ? the world-map / plot icon refresh path needs CREATE
|
||||
// // on wholesale re-pushes (sniff-verified against retail dumps).
|
||||
// void SendCreateToPlayer(Player* player);
|
||||
//
|
||||
// // Diagnostic: log every emitted CREATE's field contents so we can verify
|
||||
// // fixes from sniff comparisons without needing to decode hex by hand.
|
||||
// void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
//
|
||||
// // Housing UpdateField setters (IDA-verified: HouseType/HouseSize not in this fragment)
|
||||
// void SetPlotIndex(int32 plotIndex);
|
||||
// void SetLevel(uint32 level);
|
||||
// void SetFavor(uint64 favor);
|
||||
// void SetBudgets(uint32 interiorDecor, uint32 exteriorDecor, uint32 room, uint32 fixture);
|
||||
// void SetBnetAccount(ObjectGuid bnetAccountGuid);
|
||||
// void SetEntityGUID(ObjectGuid entityGuid);
|
||||
// void SetObjectType(TypeID typeId) { m_objectTypeId = typeId; }
|
||||
//
|
||||
// UF::UpdateField<UF::HousingPlayerHouseData, int32(WowCS::EntityFragment::FHousingPlayerHouse_C), 0> m_housingPlayerHouseData;
|
||||
//
|
||||
//protected:
|
||||
// UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
// bool AddToObjectUpdate() override;
|
||||
// void RemoveFromObjectUpdate() override;
|
||||
//
|
||||
//private:
|
||||
// WorldSession* _session;
|
||||
//};
|
||||
//
|
||||
//#endif // TRINITYCORE_HOUSING_PLAYER_HOUSE_ENTITY_H
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_PLAYER_HOUSE_ENTITY_H
|
||||
#define TRINITYCORE_HOUSING_PLAYER_HOUSE_ENTITY_H
|
||||
|
||||
#include "BaseEntity.h"
|
||||
|
||||
class WorldSession;
|
||||
|
||||
// FHousingPlayerHouse_C carrier entity on a Housing/3 GUID (subType 3, arg2 7).
|
||||
class HousingPlayerHouseEntity final : public BaseEntity
|
||||
{
|
||||
public:
|
||||
explicit HousingPlayerHouseEntity(WorldSession* session, ObjectGuid guid);
|
||||
|
||||
void ClearUpdateMask(bool remove) override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
void BuildUpdate(UpdateDataMapType& data_map) override;
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
void SendUpdateToPlayer(Player* player);
|
||||
// The entity stands for the logged-in character's house (Housing::SyncUpdateFields); its GUID follows that house.
|
||||
void SetGuid(ObjectGuid const& guid) { _Create(guid); }
|
||||
// force-emit CREATE_OBJECT (bypasses HaveAtClient); map-icon refresh only fires on CREATE
|
||||
void SendCreateToPlayer(Player* player);
|
||||
|
||||
// HouseType/HouseSize are not part of this fragment
|
||||
void SetPlotIndex(int32 plotIndex);
|
||||
void SetLevel(uint32 level);
|
||||
void SetFavor(uint64 favor);
|
||||
void SetBudgets(uint32 interiorDecor, uint32 exteriorDecor, uint32 room, uint32 fixture);
|
||||
void SetBnetAccount(ObjectGuid bnetAccountGuid);
|
||||
void SetCosmeticOwner(ObjectGuid ownerGuid);
|
||||
void SetEntityGUID(ObjectGuid entityGuid);
|
||||
void SetObjectType(TypeID typeId) { m_objectTypeId = typeId; }
|
||||
|
||||
UF::UpdateField<UF::HousingPlayerHouseData, int32(WowCS::EntityFragment::FHousingPlayerHouse_C), 0> m_housingPlayerHouseData;
|
||||
|
||||
protected:
|
||||
UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
bool AddToObjectUpdate() override;
|
||||
void RemoveFromObjectUpdate() override;
|
||||
|
||||
private:
|
||||
WorldSession* _session;
|
||||
};
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_PLAYER_HOUSE_ENTITY_H
|
||||
|
||||
|
||||
@@ -1,266 +1,243 @@
|
||||
///*
|
||||
//* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
//*
|
||||
//* This program is free software; you can redistribute it and/or modify it
|
||||
//* under the terms of the GNU General Public License as published by the
|
||||
//* Free Software Foundation; either version 2 of the License, or (at your
|
||||
//* option) any later version.
|
||||
//*
|
||||
//* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
//* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
//* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
//* more details.
|
||||
//*
|
||||
//* You should have received a copy of the GNU General Public License along
|
||||
//* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//*/
|
||||
//
|
||||
//#include "HousingRoomEntity.h"
|
||||
//#include "Log.h"
|
||||
//#include "Map.h"
|
||||
//#include "PhasingHandler.h"
|
||||
//#include "Player.h"
|
||||
//#include "StringFormat.h"
|
||||
//#include "UpdateData.h"
|
||||
//
|
||||
//HousingRoomEntity::HousingRoomEntity()
|
||||
// : WorldObject(false)
|
||||
//{
|
||||
// m_objectTypeId = TYPEID_HOUSING_ENTITY; // 18 ? retail objectType for housing entities
|
||||
//
|
||||
// m_updateFlag.HasEntityPosition = true;
|
||||
// m_updateFlag.Stationary = true;
|
||||
//
|
||||
// // Object constructor adds CGObject (fragment 2) automatically. Retail room entities
|
||||
// // do NOT have CGObject ? sniff-verified fragment list is [21, 31, 220] only.
|
||||
// // Remove it before adding our housing fragments.
|
||||
// m_entityFragments.Remove(WowCS::EntityFragment::CGObject);
|
||||
//
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::FHousingRoom_C, false, WowCS::GetRawFragmentData(m_housingRoomData));
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
// m_entityFragments.Add(WowCS::EntityFragment::Tag_HousingRoom, false);
|
||||
//}
|
||||
//
|
||||
//bool HousingRoomEntity::Create(ObjectGuid guid, Map* map, Position const& pos)
|
||||
//{
|
||||
// _Create(guid);
|
||||
// SetMap(map);
|
||||
// Relocate(pos);
|
||||
// SetObjectScale(1.0f);
|
||||
//
|
||||
// if (!GetMap()->AddToMap(this))
|
||||
// return false;
|
||||
//
|
||||
// // The Housing/2 identity carries the per-plot Geobox via its attached
|
||||
// // component MeshObject. The client's OutsidePlotBounds and IsInsidePlot
|
||||
// // checks both walk the room registry ? if the identity is not in the
|
||||
// // client's entity table, every decor placement attempt fails. Mark it
|
||||
// // active + far-visible so it streams to every player on the map even
|
||||
// // after we drop HousingMap::m_VisibleDistance below MAX.
|
||||
// setActive(true);
|
||||
// SetFarVisible(true);
|
||||
//
|
||||
// return true;
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::AddToWorld()
|
||||
//{
|
||||
// if (!IsInWorld())
|
||||
// {
|
||||
// GetMap()->GetObjectsStore().Insert<HousingRoomEntity>(this);
|
||||
// WorldObject::AddToWorld();
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::RemoveFromWorld()
|
||||
//{
|
||||
// if (IsInWorld())
|
||||
// {
|
||||
// WorldObject::RemoveFromWorld();
|
||||
// GetMap()->GetObjectsStore().Remove<HousingRoomEntity>(this);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
//{
|
||||
// if (!target)
|
||||
// return;
|
||||
//
|
||||
// // HousingRoomEntity uses entity fragments (like BaseEntity) but is a WorldObject
|
||||
// // for grid/visibility. Object::BuildCreateUpdateBlockForPlayer uses BuildValuesCreate
|
||||
// // which expects CGObject fields. We override to use the entity fragment path instead.
|
||||
//
|
||||
// uint8 updateType = UPDATETYPE_CREATE_OBJECT;
|
||||
// uint8 objectType = m_objectTypeId;
|
||||
// CreateObjectBits flags = m_updateFlag;
|
||||
//
|
||||
// ByteBuffer& buf = data->GetBuffer();
|
||||
// buf << uint8(updateType);
|
||||
// buf << GetGUID();
|
||||
// buf << uint8(objectType);
|
||||
//
|
||||
// BuildMovementUpdate(buf, flags, target);
|
||||
//
|
||||
// UF::UpdateFieldFlag fieldFlags = GetUpdateFieldFlagsFor(target);
|
||||
// std::size_t sizePos = buf.wpos();
|
||||
// buf << uint32(0);
|
||||
// buf << uint8(fieldFlags);
|
||||
// BuildEntityFragments(buf, m_entityFragments.GetIds());
|
||||
//
|
||||
// for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
|
||||
// {
|
||||
// WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
|
||||
// if (WowCS::IsIndirectFragment(fragmentId))
|
||||
// buf << uint8(1);
|
||||
//
|
||||
// WowCS::EntityFragmentInfo->SerializeCreate[static_cast<std::size_t>(m_entityFragments.Updateable.Ids[i])](
|
||||
// m_entityFragments.Updateable.Data[i], fieldFlags, buf, target, this);
|
||||
// }
|
||||
//
|
||||
// buf.put<uint32>(sizePos, buf.wpos() - sizePos - 4);
|
||||
// data->AddUpdateBlock();
|
||||
//
|
||||
// // Hex dump of the complete CREATE block for byte-level debugging
|
||||
// {
|
||||
// // Dump from start of this entity (approximate: back up from sizePos)
|
||||
// std::size_t blockEnd = buf.wpos();
|
||||
// std::size_t dumpStart = (sizePos > 40) ? sizePos - 40 : 0;
|
||||
// std::string hex;
|
||||
// for (std::size_t i = dumpStart; i < std::min(blockEnd, dumpStart + 80); ++i)
|
||||
// hex += Trinity::StringFormat("{:02x} ", buf[i]);
|
||||
// TC_LOG_ERROR("housing", "HousingRoomEntity::BuildCreate guid={} objectType={} "
|
||||
// "fieldBlockSize={} pos=({:.1f},{:.1f},{:.1f}) HEX: {}",
|
||||
// GetGUID().ToString(), uint32(m_objectTypeId),
|
||||
// buf.wpos() - sizePos - 4,
|
||||
// GetPositionX(), GetPositionY(), GetPositionZ(), hex);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::BuildValuesCreate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
//{
|
||||
// // Not used ? BuildCreateUpdateBlockForPlayer handles everything via entity fragments.
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::BuildValuesUpdate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
//{
|
||||
// // VALUES updates use the standard fragment change mask system
|
||||
//}
|
||||
//
|
||||
//std::string HousingRoomEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
//{
|
||||
// return "HousingRoom";
|
||||
//}
|
||||
//
|
||||
//std::string HousingRoomEntity::GetDebugInfo() const
|
||||
//{
|
||||
// return Trinity::StringFormat("{}\nType: HousingRoomEntity pos: ({:.1f}, {:.1f}, {:.1f})",
|
||||
// Object::GetDebugInfo(),
|
||||
// GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
//}
|
||||
//
|
||||
//UF::UpdateFieldFlag HousingRoomEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
//{
|
||||
// return UF::UpdateFieldFlag::None;
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::ClearValuesChangesMask()
|
||||
//{
|
||||
// m_values.ClearChangesMask(&HousingRoomEntity::m_housingRoomData);
|
||||
// m_values.ClearChangesMask(&HousingRoomEntity::m_mirroredPositionData);
|
||||
// Object::ClearValuesChangesMask();
|
||||
//}
|
||||
//
|
||||
//bool HousingRoomEntity::AddToObjectUpdate()
|
||||
//{
|
||||
// GetMap()->AddUpdateObject(this);
|
||||
// return true;
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::RemoveFromObjectUpdate()
|
||||
//{
|
||||
// GetMap()->RemoveUpdateObject(this);
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::SetHouseGUID(ObjectGuid houseGuid)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData).ModifyValue(&UF::HousingRoomData::HouseGUID), houseGuid);
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::SetHouseRoomID(int32 roomId)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData).ModifyValue(&UF::HousingRoomData::HouseRoomID), roomId);
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::SetFlags(int32 flags)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData).ModifyValue(&UF::HousingRoomData::Flags), flags);
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::SetFloorIndex(int32 floorIndex)
|
||||
//{
|
||||
// SetUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData).ModifyValue(&UF::HousingRoomData::FloorIndex), floorIndex);
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::AddMeshObject(ObjectGuid meshObjectGuid)
|
||||
//{
|
||||
// AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
// .ModifyValue(&UF::HousingRoomData::MeshObjects)) = meshObjectGuid;
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::ReplaceMeshObjects(std::vector<ObjectGuid> const& newGuids)
|
||||
//{
|
||||
// // Clear existing MeshObjects dynamic array and repopulate with new GUIDs.
|
||||
// // Called after theme respawn to update the room entity's mesh list so the
|
||||
// // client doesn't reference stale/destroyed mesh GUIDs (causes null deref crash).
|
||||
// ClearDynamicUpdateFieldValues(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
// .ModifyValue(&UF::HousingRoomData::MeshObjects));
|
||||
//
|
||||
// for (ObjectGuid const& guid : newGuids)
|
||||
// AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
// .ModifyValue(&UF::HousingRoomData::MeshObjects)) = guid;
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::AddDoor(int32 roomComponentID, Position const& offset, uint8 connectionType, ObjectGuid attachedRoomGuid)
|
||||
//{
|
||||
// auto doorRef = AddDynamicUpdateFieldValue(
|
||||
// m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
// .ModifyValue(&UF::HousingRoomData::Doors));
|
||||
// doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentID).SetValue(roomComponentID);
|
||||
// doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentOffset).SetValue(
|
||||
// TaggedPosition<Position::XYZ>(offset.GetPositionX(), offset.GetPositionY(), offset.GetPositionZ()));
|
||||
// doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentType).SetValue(connectionType);
|
||||
// doorRef.ModifyValue(&UF::HousingDoorData::AttachedRoomGUID).SetValue(attachedRoomGuid);
|
||||
//}
|
||||
//
|
||||
//bool HousingRoomEntity::UpdateDoorConnection(int32 roomComponentID, ObjectGuid attachedRoomGuid)
|
||||
//{
|
||||
// // Find the door with the matching component ID and update its AttachedRoomGUID.
|
||||
// // Read from the const view, then modify the matching entry.
|
||||
// UF::HousingRoomData const& roomData = *m_housingRoomData;
|
||||
// for (uint32 i = 0; i < roomData.Doors.size(); ++i)
|
||||
// {
|
||||
// if (roomData.Doors[i].RoomComponentID == roomComponentID)
|
||||
// {
|
||||
// SetUpdateFieldValue(
|
||||
// m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
// .ModifyValue(&UF::HousingRoomData::Doors, i)
|
||||
// .ModifyValue(&UF::HousingDoorData::AttachedRoomGUID),
|
||||
// attachedRoomGuid);
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
//
|
||||
//void HousingRoomEntity::SetMirroredPosition(Position const& pos, QuaternionData const& rot,
|
||||
// float scale, ObjectGuid attachParent, uint8 attachFlags)
|
||||
//{
|
||||
// auto posData = m_values.ModifyValue(&HousingRoomEntity::m_mirroredPositionData)
|
||||
// .ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
// TaggedPosition<Position::XYZ>(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()));
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rot);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
// SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachFlags);
|
||||
//}
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HousingRoomEntity.h"
|
||||
#include "Map.h"
|
||||
#include "Player.h"
|
||||
#include "StringFormat.h"
|
||||
#include "UpdateData.h"
|
||||
|
||||
HousingRoomEntity::HousingRoomEntity(bool exteriorRoot /*= false*/)
|
||||
: WorldObject(false)
|
||||
{
|
||||
m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
|
||||
m_updateFlag.HasEntityPosition = true;
|
||||
m_updateFlag.Stationary = !exteriorRoot;
|
||||
|
||||
// remove the CGObject fragment added by the Object constructor
|
||||
m_entityFragments.Remove(WowCS::EntityFragment::CGObject);
|
||||
|
||||
if (exteriorRoot)
|
||||
{
|
||||
// base and roof meshes hang off this root at local 0
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, false);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorRoot, false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingRoom_C, false, WowCS::GetRawFragmentData(m_housingRoomData));
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HousingRoom, false);
|
||||
}
|
||||
|
||||
bool HousingRoomEntity::Create(ObjectGuid guid, Map* map, Position const& pos)
|
||||
{
|
||||
_Create(guid);
|
||||
SetMap(map);
|
||||
Relocate(pos);
|
||||
SetObjectScale(1.0f);
|
||||
|
||||
if (!GetMap()->AddToMap(this))
|
||||
return false;
|
||||
|
||||
// client plot-bounds checks walk the room registry; keep this streamed to every player
|
||||
setActive(true);
|
||||
SetFarVisible(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HousingRoomEntity::AddToWorld()
|
||||
{
|
||||
if (!IsInWorld())
|
||||
{
|
||||
GetMap()->GetObjectsStore().Insert<HousingRoomEntity>(this);
|
||||
WorldObject::AddToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
void HousingRoomEntity::RemoveFromWorld()
|
||||
{
|
||||
if (IsInWorld())
|
||||
{
|
||||
WorldObject::RemoveFromWorld();
|
||||
GetMap()->GetObjectsStore().Remove<HousingRoomEntity>(this);
|
||||
}
|
||||
}
|
||||
|
||||
void HousingRoomEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
CreateObjectBits flags = m_updateFlag;
|
||||
|
||||
ByteBuffer& buf = data->GetBuffer();
|
||||
buf << uint8(UPDATETYPE_CREATE_OBJECT);
|
||||
buf << GetGUID();
|
||||
buf << uint8(m_objectTypeId);
|
||||
|
||||
BuildMovementUpdate(buf, flags, target);
|
||||
|
||||
UF::UpdateFieldFlag fieldFlags = GetUpdateFieldFlagsFor(target);
|
||||
std::size_t sizePos = buf.wpos();
|
||||
buf << uint32(0);
|
||||
buf << uint8(fieldFlags);
|
||||
BuildEntityFragments(buf, m_entityFragments.GetIds());
|
||||
|
||||
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
|
||||
{
|
||||
WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
|
||||
if (WowCS::IsIndirectFragment(fragmentId))
|
||||
buf << uint8(1);
|
||||
|
||||
WowCS::EntityFragmentInfo->SerializeCreate[static_cast<std::size_t>(fragmentId)](
|
||||
m_entityFragments.Updateable.Data[i], fieldFlags, buf, target, this);
|
||||
}
|
||||
|
||||
buf.put<uint32>(sizePos, buf.wpos() - sizePos - 4);
|
||||
data->AddUpdateBlock();
|
||||
}
|
||||
|
||||
void HousingRoomEntity::BuildValuesCreate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
{
|
||||
// values are serialized via entity fragments
|
||||
}
|
||||
|
||||
void HousingRoomEntity::BuildValuesUpdate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const
|
||||
{
|
||||
// handled by the entity fragment change mask system
|
||||
}
|
||||
|
||||
std::string HousingRoomEntity::GetNameForLocaleIdx(LocaleConstant /*locale*/) const
|
||||
{
|
||||
return "HousingRoom";
|
||||
}
|
||||
|
||||
std::string HousingRoomEntity::GetDebugInfo() const
|
||||
{
|
||||
return Trinity::StringFormat("{}\nType: HousingRoomEntity pos: ({:.1f}, {:.1f}, {:.1f})",
|
||||
Object::GetDebugInfo(),
|
||||
GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
}
|
||||
|
||||
UF::UpdateFieldFlag HousingRoomEntity::GetUpdateFieldFlagsFor(Player const* /*target*/) const
|
||||
{
|
||||
return UF::UpdateFieldFlag::None;
|
||||
}
|
||||
|
||||
void HousingRoomEntity::ClearValuesChangesMask()
|
||||
{
|
||||
m_values.ClearChangesMask(&HousingRoomEntity::m_housingRoomData);
|
||||
m_values.ClearChangesMask(&HousingRoomEntity::m_mirroredPositionData);
|
||||
Object::ClearValuesChangesMask();
|
||||
}
|
||||
|
||||
bool HousingRoomEntity::AddToObjectUpdate()
|
||||
{
|
||||
GetMap()->AddUpdateObject(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HousingRoomEntity::RemoveFromObjectUpdate()
|
||||
{
|
||||
GetMap()->RemoveUpdateObject(this);
|
||||
}
|
||||
|
||||
void HousingRoomEntity::SetHouseGUID(ObjectGuid houseGuid)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData).ModifyValue(&UF::HousingRoomData::HouseGUID), houseGuid);
|
||||
}
|
||||
|
||||
void HousingRoomEntity::SetHouseRoomID(int32 roomId)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData).ModifyValue(&UF::HousingRoomData::HouseRoomID), roomId);
|
||||
}
|
||||
|
||||
void HousingRoomEntity::SetFlags(int32 flags)
|
||||
{
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData).ModifyValue(&UF::HousingRoomData::Flags), flags);
|
||||
}
|
||||
|
||||
void HousingRoomEntity::SetFloorIndex(int32 floorIndex)
|
||||
{
|
||||
// server-side only; not a client wire field anymore
|
||||
_floorIndex = floorIndex;
|
||||
}
|
||||
|
||||
void HousingRoomEntity::AddMeshObject(ObjectGuid meshObjectGuid)
|
||||
{
|
||||
AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
.ModifyValue(&UF::HousingRoomData::MeshObjects)) = meshObjectGuid;
|
||||
}
|
||||
|
||||
void HousingRoomEntity::ReplaceMeshObjects(std::vector<ObjectGuid> const& newGuids)
|
||||
{
|
||||
// refresh the mesh list after theme respawn (stale GUIDs crash the client)
|
||||
ClearDynamicUpdateFieldValues(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
.ModifyValue(&UF::HousingRoomData::MeshObjects));
|
||||
|
||||
for (ObjectGuid const& guid : newGuids)
|
||||
AddDynamicUpdateFieldValue(m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
.ModifyValue(&UF::HousingRoomData::MeshObjects)) = guid;
|
||||
}
|
||||
|
||||
void HousingRoomEntity::AddDoor(int32 roomComponentID, Position const& offset, uint8 connectionType, ObjectGuid attachedRoomGuid)
|
||||
{
|
||||
auto doorRef = AddDynamicUpdateFieldValue(
|
||||
m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
.ModifyValue(&UF::HousingRoomData::Doors));
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentID).SetValue(roomComponentID);
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentOffset).SetValue(
|
||||
TaggedPosition<Position::XYZ>(offset.GetPositionX(), offset.GetPositionY(), offset.GetPositionZ()));
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentType).SetValue(connectionType);
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::AttachedRoomGUID).SetValue(attachedRoomGuid);
|
||||
}
|
||||
|
||||
bool HousingRoomEntity::UpdateDoorConnection(int32 roomComponentID, ObjectGuid attachedRoomGuid)
|
||||
{
|
||||
UF::HousingRoomData const& roomData = *m_housingRoomData;
|
||||
for (uint32 i = 0; i < roomData.Doors.size(); ++i)
|
||||
{
|
||||
if (roomData.Doors[i].RoomComponentID == roomComponentID)
|
||||
{
|
||||
SetUpdateFieldValue(
|
||||
m_values.ModifyValue(&HousingRoomEntity::m_housingRoomData)
|
||||
.ModifyValue(&UF::HousingRoomData::Doors, i)
|
||||
.ModifyValue(&UF::HousingDoorData::AttachedRoomGUID),
|
||||
attachedRoomGuid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HousingRoomEntity::SetMirroredPosition(Position const& pos, QuaternionData const& rot,
|
||||
float scale, ObjectGuid attachParent, uint8 attachFlags)
|
||||
{
|
||||
auto posData = m_values.ModifyValue(&HousingRoomEntity::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rot);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachFlags);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
///*
|
||||
//* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
//*
|
||||
//* This program is free software; you can redistribute it and/or modify it
|
||||
//* under the terms of the GNU General Public License as published by the
|
||||
//* Free Software Foundation; either version 2 of the License, or (at your
|
||||
//* option) any later version.
|
||||
//*
|
||||
//* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
//* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
//* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
//* more details.
|
||||
//*
|
||||
//* You should have received a copy of the GNU General Public License along
|
||||
//* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//*/
|
||||
//
|
||||
//#ifndef TRINITYCORE_HOUSING_ROOM_ENTITY_H
|
||||
//#define TRINITYCORE_HOUSING_ROOM_ENTITY_H
|
||||
//
|
||||
//#include "Object.h"
|
||||
//#include "GridObject.h"
|
||||
//#include "MapObject.h"
|
||||
//
|
||||
//// Room entity matching retail Housing/2 GUID (subType=2, arg2=HouseRoomID).
|
||||
//// objectType=18. Fragments: FHousingRoom_C + FMirroredPositionData_C + Tag_HousingRoom.
|
||||
//// Inherits from WorldObject so it can be AddToMap'd and sent via the visibility
|
||||
//// system in the SAME UPDATE_OBJECT as room MeshObjects ? matching retail behavior.
|
||||
//// Has Stationary position in the movement block (sniff-verified: COB 0x81 0x00 0x00).
|
||||
//class TC_GAME_API HousingRoomEntity final : public WorldObject, public GridObject<HousingRoomEntity>, public MapObject
|
||||
//{
|
||||
//public:
|
||||
// explicit HousingRoomEntity();
|
||||
//
|
||||
// void AddToWorld() override;
|
||||
// void RemoveFromWorld() override;
|
||||
//
|
||||
// bool Create(ObjectGuid guid, Map* map, Position const& pos);
|
||||
//
|
||||
// // Pure virtual overrides from WorldObject
|
||||
// ObjectGuid GetCreatorGUID() const override { return ObjectGuid::Empty; }
|
||||
// ObjectGuid GetOwnerGUID() const override { return ObjectGuid::Empty; }
|
||||
// uint32 GetFaction() const override { return 0; }
|
||||
//
|
||||
// // Override to use entity fragment serialization (like BaseEntity) instead of
|
||||
// // Object's BuildValuesCreate path which expects CGObject fields.
|
||||
// void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
// void BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
// void BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
// std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
// std::string GetDebugInfo() const override;
|
||||
//
|
||||
// // Room data setters
|
||||
// void SetHouseGUID(ObjectGuid houseGuid);
|
||||
// void SetHouseRoomID(int32 roomId);
|
||||
// void SetFlags(int32 flags);
|
||||
// void SetFloorIndex(int32 floorIndex);
|
||||
// void AddMeshObject(ObjectGuid meshObjectGuid);
|
||||
// void ReplaceMeshObjects(std::vector<ObjectGuid> const& newGuids);
|
||||
// void AddDoor(int32 roomComponentID, Position const& offset, uint8 connectionType, ObjectGuid attachedRoomGuid = ObjectGuid::Empty);
|
||||
// bool UpdateDoorConnection(int32 roomComponentID, ObjectGuid attachedRoomGuid);
|
||||
//
|
||||
// // Mirrored position data setters
|
||||
// void SetMirroredPosition(Position const& pos, QuaternionData const& rot, float scale,
|
||||
// ObjectGuid attachParent = ObjectGuid::Empty, uint8 attachFlags = 3);
|
||||
//
|
||||
// UF::UpdateField<UF::HousingRoomData, int32(WowCS::EntityFragment::FHousingRoom_C), 0> m_housingRoomData;
|
||||
// UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
//
|
||||
//protected:
|
||||
// UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
// void ClearValuesChangesMask() override;
|
||||
// bool AddToObjectUpdate() override;
|
||||
// void RemoveFromObjectUpdate() override;
|
||||
//};
|
||||
//
|
||||
//#endif // TRINITYCORE_HOUSING_ROOM_ENTITY_H
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_ROOM_ENTITY_H
|
||||
#define TRINITYCORE_HOUSING_ROOM_ENTITY_H
|
||||
|
||||
#include "Object.h"
|
||||
#include "GridObject.h"
|
||||
#include "MapObject.h"
|
||||
|
||||
// Housing/2 room entity; streams through the grid visibility system with its MeshObjects.
|
||||
class TC_GAME_API HousingRoomEntity final : public WorldObject, public GridObject<HousingRoomEntity>, public MapObject
|
||||
{
|
||||
public:
|
||||
// exteriorRoot: create the house-exterior root entity instead of a room (see constructor)
|
||||
explicit HousingRoomEntity(bool exteriorRoot = false);
|
||||
|
||||
void AddToWorld() override;
|
||||
void RemoveFromWorld() override;
|
||||
|
||||
bool Create(ObjectGuid guid, Map* map, Position const& pos);
|
||||
|
||||
ObjectGuid GetCreatorGUID() const override { return ObjectGuid::Empty; }
|
||||
ObjectGuid GetOwnerGUID() const override { return ObjectGuid::Empty; }
|
||||
uint32 GetFaction() const override { return 0; }
|
||||
|
||||
// serialized via entity fragments, not Object's CGObject path
|
||||
void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
void BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
void BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
// must be called before Create (Create adds the entity to the map)
|
||||
void SetHouseGUID(ObjectGuid houseGuid);
|
||||
void SetHouseRoomID(int32 roomId);
|
||||
void SetFlags(int32 flags);
|
||||
void SetFloorIndex(int32 floorIndex);
|
||||
int32 GetFloorIndex() const { return _floorIndex; }
|
||||
void AddMeshObject(ObjectGuid meshObjectGuid);
|
||||
void ReplaceMeshObjects(std::vector<ObjectGuid> const& newGuids);
|
||||
void AddDoor(int32 roomComponentID, Position const& offset, uint8 connectionType, ObjectGuid attachedRoomGuid = ObjectGuid::Empty);
|
||||
bool UpdateDoorConnection(int32 roomComponentID, ObjectGuid attachedRoomGuid);
|
||||
|
||||
void SetMirroredPosition(Position const& pos, QuaternionData const& rot, float scale,
|
||||
ObjectGuid attachParent = ObjectGuid::Empty, uint8 attachFlags = 3);
|
||||
|
||||
UF::UpdateField<UF::HousingRoomData, int32(WowCS::EntityFragment::FHousingRoom_C), 0> m_housingRoomData;
|
||||
UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
|
||||
protected:
|
||||
UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const override;
|
||||
void ClearValuesChangesMask() override;
|
||||
bool AddToObjectUpdate() override;
|
||||
void RemoveFromObjectUpdate() override;
|
||||
|
||||
private:
|
||||
// not a client wire field anymore
|
||||
int32 _floorIndex = 0;
|
||||
};
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_ROOM_ENTITY_H
|
||||
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "MeshObject.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "Log.h"
|
||||
#include "Map.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "UpdateData.h"
|
||||
|
||||
MeshObject::MeshObject() : WorldObject(false), MapObject()
|
||||
{
|
||||
m_objectTypeId = TYPEID_MESH_OBJECT;
|
||||
|
||||
// Stationary would shift client parsing of the MeshObject block
|
||||
m_updateFlag.Stationary = false;
|
||||
m_updateFlag.MeshObject = true;
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_MeshObject, false);
|
||||
}
|
||||
|
||||
MeshObject::~MeshObject() = default;
|
||||
|
||||
void MeshObject::AddToWorld()
|
||||
{
|
||||
if (!IsInWorld())
|
||||
{
|
||||
GetMap()->GetObjectsStore().Insert<MeshObject>(this);
|
||||
WorldObject::AddToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshObject::RemoveFromWorld()
|
||||
{
|
||||
if (IsInWorld())
|
||||
{
|
||||
WorldObject::RemoveFromWorld();
|
||||
GetMap()->GetObjectsStore().Remove<MeshObject>(this);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshObject::Update(uint32 diff)
|
||||
{
|
||||
WorldObject::Update(diff);
|
||||
}
|
||||
|
||||
MeshObject* MeshObject::CreateMeshObject(Map* map, Position const& pos,
|
||||
QuaternionData const& rotation, float scale,
|
||||
int32 fileDataID, bool isWMO,
|
||||
ObjectGuid attachParent /*= ObjectGuid::Empty*/, uint8 attachFlags /*= 0*/,
|
||||
Position const* worldPos /*= nullptr*/)
|
||||
{
|
||||
MeshObject* mesh = new MeshObject();
|
||||
if (!mesh->Create(map, pos, rotation, scale, fileDataID, isWMO, attachParent, attachFlags, worldPos))
|
||||
{
|
||||
delete mesh;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
bool MeshObject::Create(Map* map, Position const& pos, QuaternionData const& rotation,
|
||||
float scale, int32 fileDataID, bool isWMO,
|
||||
ObjectGuid attachParent, uint8 attachFlags,
|
||||
Position const* worldPos)
|
||||
{
|
||||
SetMap(map);
|
||||
|
||||
// pos is the local-space offset; grid placement uses the parent's world position
|
||||
if (worldPos)
|
||||
Relocate(*worldPos);
|
||||
else
|
||||
Relocate(pos);
|
||||
|
||||
if (!IsPositionValid())
|
||||
{
|
||||
TC_LOG_ERROR("entities.meshobject", "MeshObject not created. Invalid coordinates (X: {} Y: {})",
|
||||
GetPositionX(), GetPositionY());
|
||||
return false;
|
||||
}
|
||||
|
||||
// always visible so phase changes on plot exit don't hide meshes (incl. neighbour houses)
|
||||
PhasingHandler::InitDbPhaseShift(GetPhaseShift(), PHASE_USE_FLAGS_ALWAYS_VISIBLE, 0, 0);
|
||||
|
||||
_Create(ObjectGuid::Create<HighGuid::MeshObject>(GetMapId(), 0,
|
||||
GetMap()->GenerateLowGuid<HighGuid::MeshObject>()));
|
||||
|
||||
SetObjectScale(1.0f);
|
||||
|
||||
// client looks up placed decor by EntryID
|
||||
SetEntry(fileDataID);
|
||||
|
||||
auto meshData = m_values.ModifyValue(&MeshObject::m_meshObjectData);
|
||||
SetUpdateFieldValue(meshData.ModifyValue(&UF::MeshObjectData::FileDataID), fileDataID);
|
||||
SetUpdateFieldValue(meshData.ModifyValue(&UF::MeshObjectData::IsWMO), isWMO);
|
||||
SetUpdateFieldValue(meshData.ModifyValue(&UF::MeshObjectData::IsRoom), false);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMeshObjectData_C, false,
|
||||
WowCS::GetRawFragmentData(m_meshObjectData));
|
||||
|
||||
_attachParentGUID = attachParent;
|
||||
_positionLocalSpace = pos;
|
||||
_rotationLocalSpace = rotation;
|
||||
_scaleLocalSpace = scale;
|
||||
_attachmentFlags = attachFlags;
|
||||
|
||||
auto posData = m_values.ModifyValue(&MeshObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rotation);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachFlags);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false,
|
||||
WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
|
||||
SetZoneScript();
|
||||
UpdatePositionData();
|
||||
|
||||
// NOTE: caller must AddToMap after all InitHousing* calls (the create packet is sent there)
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::Create: guid={} fileDataID={} isWMO={} at ({:.1f}, {:.1f}, {:.1f}) on map {} (not yet added to map)",
|
||||
GetGUID().ToString(), fileDataID, isWMO,
|
||||
pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), GetMapId());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshObject::InitHousingDecorData(ObjectGuid decorGuid, ObjectGuid houseGuid, uint8 flags,
|
||||
ObjectGuid roomEntityGuid /*= ObjectGuid::Empty*/, uint8 sourceType /*= 0*/, std::string sourceValue /*= {}*/)
|
||||
{
|
||||
if (m_housingDecorData.has_value())
|
||||
return;
|
||||
|
||||
auto decorData = m_values.ModifyValue(&Object::m_housingDecorData, 0);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::DecorGUID), decorGuid);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::AttachParentGUID), roomEntityGuid);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::Flags), flags);
|
||||
SetUpdateFieldValue(decorData.ModifyValue(&UF::HousingDecorData::TargetGameObjectGUID), ObjectGuid::Empty);
|
||||
|
||||
auto persistedRef = decorData.ModifyValue(&UF::HousingDecorData::PersistedData, 0);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceType), sourceType);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceValue), std::move(sourceValue));
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingDecor_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingDecorData));
|
||||
|
||||
// HasDecor movement flag not set; the room entity GUID lives in FHousingDecor_C
|
||||
_decorRoomEntityGUID = roomEntityGuid;
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::InitHousingDecorData: guid={} decorGuid={} houseGuid={} flags={} roomEntity={} (FHousingDecor_C ON)",
|
||||
GetGUID().ToString(), decorGuid.ToString(), houseGuid.ToString(), flags, roomEntityGuid.ToString());
|
||||
}
|
||||
|
||||
void MeshObject::InitHousingFixtureData(ObjectGuid houseGuid, ObjectGuid fixtureGuid,
|
||||
ObjectGuid parentFixtureGuid, int32 exteriorComponentID,
|
||||
int32 houseExteriorWmoDataID, uint8 exteriorComponentType /*= 9*/,
|
||||
uint8 houseSize /*= 2*/, int32 exteriorComponentHookID /*= -1*/, bool isRoot /*= false*/)
|
||||
{
|
||||
if (m_housingFixtureData.has_value())
|
||||
return;
|
||||
|
||||
auto fixtureData = m_values.ModifyValue(&Object::m_housingFixtureData, 0);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::ExteriorComponentID), exteriorComponentID);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::HouseExteriorWmoDataID), houseExteriorWmoDataID);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::ExteriorComponentHookID), exteriorComponentHookID);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::HouseGUID), houseGuid);
|
||||
// the client builds its fixture tree from this
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::AttachParentGUID), parentFixtureGuid);
|
||||
// must be a Housing-type GUID - the client's resolver crashes on non-Housing GUIDs
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::Guid), fixtureGuid);
|
||||
|
||||
// Door components (Type 11) carry the GO entry's GUID; other fixture types stay empty.
|
||||
{
|
||||
ObjectGuid goGuid = ObjectGuid::Empty;
|
||||
if (exteriorComponentID > 0)
|
||||
{
|
||||
ExteriorComponentEntry const* extComp = sExteriorComponentStore.LookupEntry(
|
||||
static_cast<uint32>(exteriorComponentID));
|
||||
if (extComp && extComp->GameObjectID > 0)
|
||||
// reuse the MeshObject's counter in the GO GUID
|
||||
goGuid = ObjectGuid::Create<HighGuid::GameObject>(GetMap()->GetId(),
|
||||
static_cast<uint32>(extComp->GameObjectID), GetGUID().GetCounter());
|
||||
}
|
||||
|
||||
if (!goGuid.IsEmpty())
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::GameObjectGUID), goGuid);
|
||||
}
|
||||
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::ExteriorComponentType), exteriorComponentType);
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::Field_59), uint8(1)); // always 1
|
||||
SetUpdateFieldValue(fixtureData.ModifyValue(&UF::HousingFixtureData::Size), houseSize);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingFixture_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingFixtureData));
|
||||
|
||||
_exteriorComponentHookID = exteriorComponentHookID;
|
||||
_exteriorComponentID = exteriorComponentID;
|
||||
_fixtureGuid = fixtureGuid;
|
||||
_isExteriorRoot = isRoot;
|
||||
|
||||
// Tag_HouseExteriorRoot belongs to the root entity; tagging the base Root breaks house dragging
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, IsInWorld());
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::InitHousingFixtureData: meshGuid={} fixtureGuid={} "
|
||||
"parentFixtureGuid={} houseGuid={} extCompID={} wmoDataID={} hookID={} type={} size={} isRoot={}",
|
||||
GetGUID().ToString(), fixtureGuid.ToString(), parentFixtureGuid.ToString(),
|
||||
houseGuid.ToString(), exteriorComponentID, houseExteriorWmoDataID,
|
||||
exteriorComponentHookID, exteriorComponentType, houseSize, isRoot);
|
||||
}
|
||||
|
||||
void MeshObject::UpdateLocalScale(float scale)
|
||||
{
|
||||
_scaleLocalSpace = scale;
|
||||
auto posData = m_values.ModifyValue(&MeshObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
}
|
||||
|
||||
void MeshObject::UpdateLocalTransform(Position const& pos, QuaternionData const& rotation, float scale)
|
||||
{
|
||||
_positionLocalSpace = pos;
|
||||
_rotationLocalSpace = rotation;
|
||||
_scaleLocalSpace = scale;
|
||||
auto posData = m_values.ModifyValue(&MeshObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), rotation);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), scale);
|
||||
}
|
||||
|
||||
void MeshObject::SetAttachParentGUID(ObjectGuid attachParent)
|
||||
{
|
||||
_attachParentGUID = attachParent;
|
||||
auto posData = m_values.ModifyValue(&MeshObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
}
|
||||
|
||||
void MeshObject::UpdateExteriorComponentID(int32 id)
|
||||
{
|
||||
if (!m_housingFixtureData.has_value())
|
||||
return;
|
||||
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::ExteriorComponentID), id);
|
||||
_exteriorComponentID = id;
|
||||
}
|
||||
|
||||
void MeshObject::InitHousingRoomData(ObjectGuid houseGuid, int32 houseRoomID,
|
||||
int32 flags, int32 floorIndex)
|
||||
{
|
||||
if (m_housingRoomData.has_value())
|
||||
return;
|
||||
|
||||
// this MeshObject carries FHousingRoom_C, so the client reads it via MeshObjectSystem
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&MeshObject::m_meshObjectData)
|
||||
.ModifyValue(&UF::MeshObjectData::IsRoom), true);
|
||||
|
||||
auto roomData = m_values.ModifyValue(&Object::m_housingRoomData, 0);
|
||||
SetUpdateFieldValue(roomData.ModifyValue(&UF::HousingRoomData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(roomData.ModifyValue(&UF::HousingRoomData::HouseRoomID), houseRoomID);
|
||||
SetUpdateFieldValue(roomData.ModifyValue(&UF::HousingRoomData::Flags), flags);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingRoom_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingRoomData));
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HousingRoom, IsInWorld());
|
||||
|
||||
// HasRoom movement flag not set; the house GUID lives in FHousingRoom_C
|
||||
_roomHouseGUID = houseGuid;
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::InitHousingRoomData: guid={} houseGuid={} "
|
||||
"roomID={} flags={} floor={} (Room flag=ON)",
|
||||
GetGUID().ToString(), houseGuid.ToString(), houseRoomID, flags, floorIndex);
|
||||
}
|
||||
|
||||
void MeshObject::AddRoomMeshObject(ObjectGuid meshObjectGuid)
|
||||
{
|
||||
if (!m_housingRoomData.has_value())
|
||||
return;
|
||||
|
||||
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Object::m_housingRoomData, 0)
|
||||
.ModifyValue(&UF::HousingRoomData::MeshObjects)) = meshObjectGuid;
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::AddRoomMeshObject: room={} added meshObject={}",
|
||||
GetGUID().ToString(), meshObjectGuid.ToString());
|
||||
}
|
||||
|
||||
void MeshObject::AddRoomDoor(int32 roomComponentID, Position const& offset, uint8 roomComponentType, ObjectGuid attachedRoomGuid)
|
||||
{
|
||||
if (!m_housingRoomData.has_value())
|
||||
return;
|
||||
|
||||
auto&& doorRef = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Object::m_housingRoomData, 0)
|
||||
.ModifyValue(&UF::HousingRoomData::Doors));
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentID).SetValue(roomComponentID);
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentOffset).SetValue(
|
||||
TaggedPosition<Position::XYZ>(offset.GetPositionX(), offset.GetPositionY(), offset.GetPositionZ()));
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::RoomComponentType).SetValue(roomComponentType);
|
||||
doorRef.ModifyValue(&UF::HousingDoorData::AttachedRoomGUID).SetValue(attachedRoomGuid);
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::AddRoomDoor: room={} componentID={} type={} offset=({:.1f},{:.1f},{:.1f}) attachedRoom={}",
|
||||
GetGUID().ToString(), roomComponentID, roomComponentType,
|
||||
offset.GetPositionX(), offset.GetPositionY(), offset.GetPositionZ(),
|
||||
attachedRoomGuid.ToString());
|
||||
}
|
||||
|
||||
void MeshObject::InitHousingRoomComponentData(ObjectGuid roomGuid,
|
||||
int32 roomComponentOptionID, int32 roomComponentID,
|
||||
uint8 roomComponentType, int32 field24, uint8 field20,
|
||||
int32 houseThemeID, int32 roomComponentTextureID,
|
||||
int32 roomComponentTypeParam,
|
||||
float geoboxMinX, float geoboxMinY, float geoboxMinZ,
|
||||
float geoboxMaxX, float geoboxMaxY, float geoboxMaxZ)
|
||||
{
|
||||
if (m_housingRoomComponentMeshData.has_value())
|
||||
return;
|
||||
|
||||
// room component MeshObjects always have IsRoom=true
|
||||
auto meshData = m_values.ModifyValue(&MeshObject::m_meshObjectData);
|
||||
SetUpdateFieldValue(meshData.ModifyValue(&UF::MeshObjectData::IsRoom), true);
|
||||
|
||||
UF::AaBox geobox;
|
||||
geobox.Low = TaggedPosition<Position::XYZ>(geoboxMinX, geoboxMinY, geoboxMinZ);
|
||||
geobox.High = TaggedPosition<Position::XYZ>(geoboxMaxX, geoboxMaxY, geoboxMaxZ);
|
||||
SetUpdateFieldValue(meshData.ModifyValue(&UF::MeshObjectData::Geobox, uint32(0)), std::move(geobox));
|
||||
|
||||
auto compData = m_values.ModifyValue(&Object::m_housingRoomComponentMeshData, 0);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomGUID), roomGuid);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentOptionID), roomComponentOptionID);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentID), roomComponentID);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::Field_20), field20);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentType), roomComponentType);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::Field_24), field24);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::HouseThemeID), houseThemeID);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTextureID), roomComponentTextureID);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTypeParam), roomComponentTypeParam);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingRoomComponentMesh_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingRoomComponentMeshData));
|
||||
}
|
||||
|
||||
void MeshObject::UpdateRoomComponentVisuals(int32 roomComponentOptionID, int32 houseThemeID,
|
||||
int32 roomComponentTextureID, int32 roomComponentTypeParam /*= -1*/)
|
||||
{
|
||||
if (!m_housingRoomComponentMeshData.has_value())
|
||||
return;
|
||||
|
||||
auto compData = m_values.ModifyValue(&Object::m_housingRoomComponentMeshData, 0);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentOptionID), roomComponentOptionID);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::HouseThemeID), houseThemeID);
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTextureID), roomComponentTextureID);
|
||||
if (roomComponentTypeParam >= 0)
|
||||
SetUpdateFieldValue(compData.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTypeParam), roomComponentTypeParam);
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::UpdateRoomComponentVisuals: guid={} optionID={} themeID={} textureID={} typeParam={}",
|
||||
GetGUID().ToString(), roomComponentOptionID, houseThemeID, roomComponentTextureID, roomComponentTypeParam);
|
||||
}
|
||||
|
||||
int32 MeshObject::GetRoomComponentID() const
|
||||
{
|
||||
if (!m_housingRoomComponentMeshData.has_value())
|
||||
return 0;
|
||||
return m_housingRoomComponentMeshData->RoomComponentID;
|
||||
}
|
||||
|
||||
int32 MeshObject::GetRoomComponentOptionID() const
|
||||
{
|
||||
if (!m_housingRoomComponentMeshData.has_value())
|
||||
return 0;
|
||||
return m_housingRoomComponentMeshData->RoomComponentOptionID;
|
||||
}
|
||||
|
||||
int32 MeshObject::GetHouseThemeID() const
|
||||
{
|
||||
if (!m_housingRoomComponentMeshData.has_value())
|
||||
return 0;
|
||||
return m_housingRoomComponentMeshData->HouseThemeID;
|
||||
}
|
||||
|
||||
int32 MeshObject::GetRoomComponentTextureID() const
|
||||
{
|
||||
if (!m_housingRoomComponentMeshData.has_value())
|
||||
return 0;
|
||||
return m_housingRoomComponentMeshData->RoomComponentTextureID;
|
||||
}
|
||||
|
||||
void MeshObject::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// the client may not handle CreateObject2 for entity-fragment types
|
||||
CreateObjectBits flags = m_updateFlag;
|
||||
|
||||
ByteBuffer& buf = data->GetBuffer();
|
||||
buf << uint8(UPDATETYPE_CREATE_OBJECT);
|
||||
buf << GetGUID();
|
||||
buf << uint8(m_objectTypeId);
|
||||
|
||||
BuildMovementUpdate(buf, flags, target);
|
||||
|
||||
UF::UpdateFieldFlag fieldFlags = GetUpdateFieldFlagsFor(target);
|
||||
std::size_t sizePos = buf.wpos();
|
||||
buf << uint32(0);
|
||||
buf << uint8(fieldFlags);
|
||||
BuildEntityFragments(buf, m_entityFragments.GetIds());
|
||||
|
||||
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
|
||||
{
|
||||
WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
|
||||
if (WowCS::IsIndirectFragment(fragmentId))
|
||||
buf << uint8(1);
|
||||
|
||||
WowCS::EntityFragmentInfo->SerializeCreate[static_cast<std::size_t>(fragmentId)](
|
||||
m_entityFragments.Updateable.Data[i], fieldFlags, buf, target, this);
|
||||
}
|
||||
|
||||
buf.put<uint32>(sizePos, buf.wpos() - sizePos - 4);
|
||||
data->AddUpdateBlock();
|
||||
}
|
||||
|
||||
void MeshObject::BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const
|
||||
{
|
||||
// only ObjectData belongs to CGObject; other fields go via their entity fragments
|
||||
m_objectData->WriteCreate(flags, data, target, this);
|
||||
}
|
||||
|
||||
void MeshObject::BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const
|
||||
{
|
||||
// entity fragment data is handled separately via SerializeUpdate
|
||||
data << uint32(m_values.GetChangedObjectTypeMask());
|
||||
|
||||
if (m_values.HasChanged(TYPEID_OBJECT))
|
||||
m_objectData->WriteUpdate(flags, data, target, this);
|
||||
}
|
||||
|
||||
void MeshObject::ClearValuesChangesMask()
|
||||
{
|
||||
m_values.ClearChangesMask(&MeshObject::m_meshObjectData);
|
||||
m_values.ClearChangesMask(&MeshObject::m_mirroredPositionData);
|
||||
m_values.ClearChangesMask(&Object::m_housingRoomComponentMeshData);
|
||||
m_values.ClearChangesMask(&Object::m_housingDecorData);
|
||||
m_values.ClearChangesMask(&Object::m_housingFixtureData);
|
||||
WorldObject::ClearValuesChangesMask();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_MESHOBJECT_H
|
||||
#define TRINITYCORE_MESHOBJECT_H
|
||||
|
||||
#include "Object.h"
|
||||
#include "GridObject.h"
|
||||
#include "MapObject.h"
|
||||
|
||||
class TC_GAME_API MeshObject final : public WorldObject, public GridObject<MeshObject>, public MapObject
|
||||
{
|
||||
public:
|
||||
MeshObject();
|
||||
~MeshObject();
|
||||
|
||||
void AddToWorld() override;
|
||||
void RemoveFromWorld() override;
|
||||
void Update(uint32 diff) override;
|
||||
|
||||
ObjectGuid GetCreatorGUID() const override { return ObjectGuid::Empty; }
|
||||
ObjectGuid GetOwnerGUID() const override { return ObjectGuid::Empty; }
|
||||
uint32 GetFaction() const override { return 0; }
|
||||
std::string GetNameForLocaleIdx(LocaleConstant /*locale*/) const override { return "MeshObject"; }
|
||||
|
||||
// pos: local-space; worldPos: grid placement (null = use pos)
|
||||
static MeshObject* CreateMeshObject(Map* map, Position const& pos,
|
||||
QuaternionData const& rotation, float scale,
|
||||
int32 fileDataID, bool isWMO,
|
||||
ObjectGuid attachParent = ObjectGuid::Empty, uint8 attachFlags = 0,
|
||||
Position const* worldPos = nullptr);
|
||||
|
||||
int32 GetFileDataID() const { return m_meshObjectData->FileDataID; }
|
||||
ObjectGuid const& GetAttachParentGUID() const { return _attachParentGUID; }
|
||||
void SetAttachParentGUID(ObjectGuid attachParent);
|
||||
QuaternionData const& GetLocalRotation() const { return _rotationLocalSpace; }
|
||||
Position const& GetLocalPosition() const { return _positionLocalSpace; }
|
||||
float GetLocalScale() const { return _scaleLocalSpace; }
|
||||
void UpdateLocalScale(float scale);
|
||||
// local-space, relative to the attach parent
|
||||
void UpdateLocalTransform(Position const& pos, QuaternionData const& rotation, float scale);
|
||||
uint8 GetAttachmentFlags() const { return _attachmentFlags; }
|
||||
bool IsExteriorRoot() const { return _isExteriorRoot; }
|
||||
int32 GetExteriorComponentHookID() const { return _exteriorComponentHookID; }
|
||||
int32 GetExteriorComponentID() const { return _exteriorComponentID; }
|
||||
void UpdateExteriorComponentID(int32 id);
|
||||
|
||||
// Room/Decor data serialized in the movement block (BaseEntity::BuildMovementUpdate)
|
||||
ObjectGuid const& GetRoomHouseGUID() const { return _roomHouseGUID; }
|
||||
ObjectGuid const& GetDecorRoomEntityGUID() const { return _decorRoomEntityGUID; }
|
||||
|
||||
// isRoot only marks the base for server-side lookups; the root tag belongs to the root entity
|
||||
void InitHousingFixtureData(ObjectGuid houseGuid, ObjectGuid fixtureGuid,
|
||||
ObjectGuid parentFixtureGuid, int32 exteriorComponentID,
|
||||
int32 houseExteriorWmoDataID, uint8 exteriorComponentType = 9,
|
||||
uint8 houseSize = 2, int32 exteriorComponentHookID = -1, bool isRoot = false);
|
||||
ObjectGuid const& GetFixtureGuid() const { return _fixtureGuid; }
|
||||
|
||||
void InitHousingDecorData(ObjectGuid decorGuid, ObjectGuid houseGuid, uint8 flags,
|
||||
ObjectGuid roomEntityGuid = ObjectGuid::Empty, uint8 sourceType = 0, std::string sourceValue = {});
|
||||
|
||||
// Housing room data entity identifying the plot's room type to the client.
|
||||
void InitHousingRoomData(ObjectGuid houseGuid, int32 houseRoomID, int32 flags, int32 floorIndex);
|
||||
|
||||
// Requires InitHousingRoomData() first.
|
||||
void AddRoomMeshObject(ObjectGuid meshObjectGuid);
|
||||
|
||||
// Requires InitHousingRoomData() first.
|
||||
void AddRoomDoor(int32 roomComponentID, Position const& offset, uint8 roomComponentType, ObjectGuid attachedRoomGuid);
|
||||
|
||||
// sets IsRoom + Geobox for the client's OutsidePlotBounds check
|
||||
void InitHousingRoomComponentData(ObjectGuid roomGuid,
|
||||
int32 roomComponentOptionID, int32 roomComponentID,
|
||||
uint8 roomComponentType, int32 field24, uint8 field20,
|
||||
int32 houseThemeID, int32 roomComponentTextureID,
|
||||
int32 roomComponentTypeParam,
|
||||
float geoboxMinX, float geoboxMinY, float geoboxMinZ,
|
||||
float geoboxMaxX, float geoboxMaxY, float geoboxMaxZ);
|
||||
|
||||
// client expects an UPDATE_OBJECT, not destroy+create
|
||||
void UpdateRoomComponentVisuals(int32 roomComponentOptionID, int32 houseThemeID,
|
||||
int32 roomComponentTextureID, int32 roomComponentTypeParam = -1);
|
||||
|
||||
int32 GetRoomComponentID() const;
|
||||
int32 GetRoomComponentOptionID() const;
|
||||
int32 GetHouseThemeID() const;
|
||||
int32 GetRoomComponentTextureID() const;
|
||||
|
||||
UF::UpdateField<UF::MeshObjectData, int32(WowCS::EntityFragment::FMeshObjectData_C), TYPEID_MESH_OBJECT> m_meshObjectData;
|
||||
UF::UpdateField<UF::MirroredPositionData, int32(WowCS::EntityFragment::FMirroredPositionData_C), 0> m_mirroredPositionData;
|
||||
|
||||
void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
|
||||
protected:
|
||||
void BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
void BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
void ClearValuesChangesMask() override;
|
||||
|
||||
private:
|
||||
bool Create(Map* map, Position const& pos, QuaternionData const& rotation,
|
||||
float scale, int32 fileDataID, bool isWMO,
|
||||
ObjectGuid attachParent, uint8 attachFlags,
|
||||
Position const* worldPos);
|
||||
|
||||
// Movement block data (serialized by BaseEntity::BuildMovementUpdate)
|
||||
ObjectGuid _attachParentGUID;
|
||||
Position _positionLocalSpace; // local-space offset from parent
|
||||
QuaternionData _rotationLocalSpace;
|
||||
float _scaleLocalSpace = 1.0f;
|
||||
uint8 _attachmentFlags = 0;
|
||||
|
||||
// written when the corresponding CreateObjectBits are set (not part of the FHousing* fragments)
|
||||
ObjectGuid _roomHouseGUID;
|
||||
ObjectGuid _decorRoomEntityGUID;
|
||||
bool _isExteriorRoot = false;
|
||||
int32 _exteriorComponentHookID = -1;
|
||||
int32 _exteriorComponentID = 0;
|
||||
ObjectGuid _fixtureGuid;
|
||||
};
|
||||
|
||||
#endif // TRINITYCORE_MESHOBJECT_H
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "MeshObject.h"
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
@@ -186,7 +187,7 @@ void BaseEntity::BuildValuesUpdateBlockForPlayer(UpdateData* data, Player const*
|
||||
data->AddUpdateBlock();
|
||||
}
|
||||
|
||||
inline void BaseEntity::BuildEntityFragments(ByteBuffer& data, std::span<WowCS::EntityFragment const> fragments)
|
||||
void BaseEntity::BuildEntityFragments(ByteBuffer& data, std::span<WowCS::EntityFragment const> fragments)
|
||||
{
|
||||
data.append(fragments.data(), fragments.size());
|
||||
data << uint8(WowCS::EntityFragment::End);
|
||||
@@ -634,21 +635,28 @@ void BaseEntity::BuildMovementUpdate(ByteBuffer& data, CreateObjectBits flags, P
|
||||
}
|
||||
|
||||
if (flags.Room)
|
||||
data << m_housingMovement.HouseGUID;
|
||||
{
|
||||
MeshObject const* meshObj = static_cast<MeshObject const*>(this);
|
||||
data << meshObj->GetRoomHouseGUID();
|
||||
}
|
||||
|
||||
if (flags.Decor)
|
||||
data << m_housingMovement.RoomGUID;
|
||||
{
|
||||
MeshObject const* meshObj = static_cast<MeshObject const*>(this);
|
||||
data << meshObj->GetDecorRoomEntityGUID();
|
||||
}
|
||||
|
||||
if (flags.MeshObject)
|
||||
{
|
||||
data << m_housingMovement.AttachParentGUID;
|
||||
data << TaggedPosition<Position::XYZ>(m_housingMovement.PositionLocalSpace);
|
||||
data << float(m_housingMovement.RotationLocalSpace.x);
|
||||
data << float(m_housingMovement.RotationLocalSpace.y);
|
||||
data << float(m_housingMovement.RotationLocalSpace.z);
|
||||
data << float(m_housingMovement.RotationLocalSpace.w);
|
||||
data << float(m_housingMovement.ScaleLocalSpace);
|
||||
data << uint8(m_housingMovement.AttachmentFlags);
|
||||
MeshObject const* meshObj = static_cast<MeshObject const*>(this);
|
||||
data << meshObj->GetAttachParentGUID();
|
||||
// the stored local-space position, not GetPositionX/Y/Z() (world position set by Relocate for grid placement)
|
||||
Position const& localPos = meshObj->GetLocalPosition();
|
||||
data << TaggedPosition<Position::XYZ>(localPos.GetPositionX(), localPos.GetPositionY(), localPos.GetPositionZ());
|
||||
QuaternionData const& rot = meshObj->GetLocalRotation();
|
||||
data << rot.x << rot.y << rot.z << rot.w;
|
||||
data << meshObj->GetLocalScale();
|
||||
data << meshObj->GetAttachmentFlags();
|
||||
}
|
||||
|
||||
if (!PauseTimes.empty())
|
||||
@@ -702,3 +710,7 @@ std::string BaseEntity::GetDebugInfo() const
|
||||
{
|
||||
return Trinity::StringFormat("{}", GetGUID());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -94,6 +94,9 @@ namespace UF
|
||||
template<typename Derived, typename T, int32 BlockBit, uint32 Bit>
|
||||
inline void ClearChangesMask(UpdateField<T, BlockBit, Bit>(Derived::* field));
|
||||
|
||||
template<typename Derived, typename T, int32 BlockBit, uint32 Bit>
|
||||
inline void ClearChangesMask(OptionalUpdateField<T, BlockBit, Bit>(Derived::* field));
|
||||
|
||||
uint32 GetChangedObjectTypeMask() const { return _changesMask; }
|
||||
|
||||
bool HasChanged(uint32 index) const { return (_changesMask & UpdateMaskHelpers::GetBlockFlag(index)) != 0; }
|
||||
@@ -206,7 +209,7 @@ class TC_GAME_API BaseEntity
|
||||
virtual void DestroyForPlayer(Player const* target) const;
|
||||
void SendOutOfRangeForPlayer(Player const* target) const;
|
||||
|
||||
void ClearUpdateMask(bool remove);
|
||||
virtual void ClearUpdateMask(bool remove);
|
||||
|
||||
virtual std::string GetNameForLocaleIdx(LocaleConstant locale) const = 0;
|
||||
|
||||
@@ -464,12 +467,23 @@ inline UF::MutableFieldReference<T, false> UF::UpdateFieldHolder::ModifyValue(Op
|
||||
template <typename Derived, typename T, int32 BlockBit, uint32 Bit>
|
||||
inline void UF::UpdateFieldHolder::ClearChangesMask(UpdateField<T, BlockBit, Bit> Derived::* field)
|
||||
{
|
||||
static_assert(WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject);
|
||||
|
||||
BaseEntity* owner = GetOwner();
|
||||
if constexpr (WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject)
|
||||
_changesMask &= ~UpdateMaskHelpers::GetBlockFlag(Bit);
|
||||
|
||||
(static_cast<Derived*>(owner)->*field)._value.ClearChangesMask();
|
||||
}
|
||||
|
||||
template <typename Derived, typename T, int32 BlockBit, uint32 Bit>
|
||||
inline void UF::UpdateFieldHolder::ClearChangesMask(OptionalUpdateField<T, BlockBit, Bit> Derived::* field)
|
||||
{
|
||||
BaseEntity* owner = GetOwner();
|
||||
if constexpr (WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject)
|
||||
_changesMask &= ~UpdateMaskHelpers::GetBlockFlag(Bit);
|
||||
|
||||
auto& uf = (static_cast<Derived*>(owner)->*field);
|
||||
if (uf.has_value())
|
||||
uf._value->ClearChangesMask();
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_BASE_ENTITY_H
|
||||
|
||||
@@ -1,712 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingDynamicEntity.h"
|
||||
#include "HousingMapContext.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Map.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "Player.h"
|
||||
#include "UpdateData.h"
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool IsSafeHousingTransform(float positionX, float positionY, float positionZ,
|
||||
float rotationX, float rotationY, float rotationZ, float rotationW, float scale)
|
||||
{
|
||||
if (!std::isfinite(positionX) || !std::isfinite(positionY) || !std::isfinite(positionZ) ||
|
||||
!std::isfinite(rotationX) || !std::isfinite(rotationY) || !std::isfinite(rotationZ) ||
|
||||
!std::isfinite(rotationW) || !std::isfinite(scale) || scale < 0.01f || scale > 100.0f)
|
||||
return false;
|
||||
|
||||
float const rotationLengthSquared = rotationX * rotationX + rotationY * rotationY +
|
||||
rotationZ * rotationZ + rotationW * rotationW;
|
||||
return std::isfinite(rotationLengthSquared) && rotationLengthSquared > 0.000001f &&
|
||||
rotationLengthSquared < 4.0f;
|
||||
}
|
||||
|
||||
void QuarantineHousingEntity(std::string_view entityType, std::string const& entityGuid,
|
||||
std::string_view reason)
|
||||
{
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_entity_quarantine (EntityType, EntityGuid, Reason, HitCount) "
|
||||
"VALUES ('{}', '{}', '{}', 1) "
|
||||
"ON DUPLICATE KEY UPDATE Reason = VALUES(Reason), HitCount = HitCount + 1, "
|
||||
"LastSeenAt = CURRENT_TIMESTAMP",
|
||||
entityType, entityGuid, reason);
|
||||
}
|
||||
}
|
||||
|
||||
DynamicEntity::DynamicEntity(Kind kind, ObjectGuid const& guid) : _kind(kind)
|
||||
{
|
||||
_Create(guid);
|
||||
|
||||
// Retail Housing/3 player-house CREATE blocks use the Housing entity type.
|
||||
// Keep the other experimental entity kinds on their existing path while
|
||||
// they remain quarantined from the client.
|
||||
if (_kind == Kind::PlayerHouse)
|
||||
m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
else
|
||||
{
|
||||
m_objectTypeId = NUM_CLIENT_OBJECT_TYPES;
|
||||
SetIsNewObject(true);
|
||||
}
|
||||
|
||||
switch (_kind)
|
||||
{
|
||||
case Kind::PlayerHouse:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingPlayerHouse_C, false, &_playerHouse);
|
||||
break;
|
||||
case Kind::Room:
|
||||
m_entityFragments.Remove(WowCS::EntityFragment::CGObject);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingRoom_C, false, &_room);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, &_roomMirroredPosition);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HousingRoom, false);
|
||||
break;
|
||||
case Kind::Neighborhood:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FNeighborhoodMirrorData_C, false, &_neighborhood);
|
||||
break;
|
||||
case Kind::Storage:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingStorage_C, false, &_storage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicEntity::InitializePlayerHouse(ObjectGuid const& bnetAccount, ObjectGuid const& cosmeticOwner,
|
||||
int32 plotIndex, uint32 level, uint64 favor, ObjectGuid const& entityGuid)
|
||||
{
|
||||
ASSERT(_kind == Kind::PlayerHouse);
|
||||
UF::MutableFieldReference<UF::HousingPlayerHouseData, false> fields(_playerHouse);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::BnetAccount), bnetAccount);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::CosmeticOwner), cosmeticOwner);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::PlotIndex), plotIndex);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::Level), level);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::Favor), favor);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::InteriorDecorPlacementBudget), uint32(910));
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::ExteriorDecorPlacementBudget), uint32(200));
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::ExteriorFixtureBudget), uint32(1000));
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::RoomPlacementBudget), uint32(19));
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingPlayerHouseData::EntityGUID), entityGuid);
|
||||
}
|
||||
|
||||
void DynamicEntity::InitializeRoom(ObjectGuid const& houseGuid, int32 houseRoomId, int32 flags,
|
||||
TaggedPosition<Position::XYZ> const& localPosition, QuaternionData const& localRotation,
|
||||
float localScale, uint8 attachmentFlags)
|
||||
{
|
||||
ASSERT(_kind == Kind::Room);
|
||||
UF::MutableFieldReference<UF::HousingRoomData, false> fields(_room);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::HouseRoomID), houseRoomId);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::Flags), flags);
|
||||
|
||||
UF::MutableFieldReference<UF::MirroredPositionData, false> mirrored(_roomMirroredPosition);
|
||||
auto position = mirrored.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), ObjectGuid::Empty);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(localPosition));
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), localRotation);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), localScale);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachmentFlags);
|
||||
SetHousingRoomMovement(houseGuid);
|
||||
}
|
||||
|
||||
void DynamicEntity::AddRoomMeshObject(ObjectGuid const& meshGuid)
|
||||
{
|
||||
ASSERT(_kind == Kind::Room);
|
||||
UF::MutableFieldReference<UF::HousingRoomData, false> fields(_room);
|
||||
AddDynamicUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::MeshObjects)) = meshGuid;
|
||||
}
|
||||
|
||||
void DynamicEntity::InitializeNeighborhood(std::string const& name, ObjectGuid const& ownerGuid)
|
||||
{
|
||||
ASSERT(_kind == Kind::Neighborhood);
|
||||
UF::MutableFieldReference<UF::NeighborhoodMirrorData, false> fields(_neighborhood);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::NeighborhoodMirrorData::Name), name);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::NeighborhoodMirrorData::OwnerGUID), ownerGuid);
|
||||
}
|
||||
|
||||
void DynamicEntity::InitializeStorage(uint32 decorMaxOwnedCount)
|
||||
{
|
||||
ASSERT(_kind == Kind::Storage);
|
||||
UF::MutableFieldReference<UF::HousingStorageData, false> fields(_storage);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingStorageData::DecorMaxOwnedCount), decorMaxOwnedCount);
|
||||
}
|
||||
|
||||
void DynamicEntity::AddStorageDecor(ObjectGuid const& decorGuid, ObjectGuid const& houseGuid,
|
||||
uint8 placementStatus, uint8 sourceType, std::string const& sourceValue)
|
||||
{
|
||||
ASSERT(_kind == Kind::Storage);
|
||||
if (decorGuid.GetHigh() != HighGuid::Housing || decorGuid.GetSubType() != 1 ||
|
||||
!decorGuid.GetEntry() ||
|
||||
(!houseGuid.IsEmpty() && (houseGuid.GetHigh() != HighGuid::Housing || houseGuid.GetSubType() != 3)) ||
|
||||
placementStatus > 2 || sourceType > 8 || sourceValue.size() > 255)
|
||||
return;
|
||||
|
||||
UF::MutableFieldReference<UF::HousingStorageData, false> fields(_storage);
|
||||
auto decor = fields.ModifyValue(&UF::HousingStorageData::Decor, decorGuid);
|
||||
SetUpdateFieldValue(decor.ModifyValue(&UF::DecorStoragePersistedData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(decor.ModifyValue(&UF::DecorStoragePersistedData::PlacementStatus), placementStatus);
|
||||
SetUpdateFieldValue(decor.ModifyValue(&UF::DecorStoragePersistedData::SourceType), sourceType);
|
||||
SetUpdateFieldValue(decor.ModifyValue(&UF::DecorStoragePersistedData::SourceValue), sourceValue);
|
||||
}
|
||||
|
||||
void SendPlayerHouseCreate(Player* player, ObjectGuid const& houseGuid, ObjectGuid const& bnetAccount,
|
||||
ObjectGuid const& cosmeticOwner, int32 plotIndex, uint32 level, uint64 favor)
|
||||
{
|
||||
if (!player || houseGuid.IsEmpty())
|
||||
return;
|
||||
|
||||
DynamicEntity entity(DynamicEntity::Kind::PlayerHouse, houseGuid);
|
||||
entity.InitializePlayerHouse(bnetAccount, cosmeticOwner, plotIndex, level, favor, ObjectGuid::Empty);
|
||||
entity.AddToWorld();
|
||||
entity.SendUpdateToPlayer(player);
|
||||
entity.RemoveFromWorld();
|
||||
}
|
||||
|
||||
void SendHousingStorageCreate(Player* player, ObjectGuid const& bnetAccount, uint32 decorMaxOwnedCount,
|
||||
std::vector<StorageDecorEntry> const& decorEntries)
|
||||
{
|
||||
if (!player || bnetAccount.IsEmpty())
|
||||
return;
|
||||
|
||||
// Captured 12.1 traffic creates FHousingStorage_C on the Battle.net account
|
||||
// object itself, not on a synthetic Housing GUID.
|
||||
DynamicEntity entity(DynamicEntity::Kind::Storage, bnetAccount);
|
||||
entity.InitializeStorage(decorMaxOwnedCount);
|
||||
for (StorageDecorEntry const& decor : decorEntries)
|
||||
entity.AddStorageDecor(decor.DecorGuid, decor.HouseGuid, decor.PlacementStatus,
|
||||
decor.SourceType, decor.SourceValue);
|
||||
entity.AddToWorld();
|
||||
entity.SendUpdateToPlayer(player);
|
||||
entity.RemoveFromWorld();
|
||||
}
|
||||
|
||||
RoomWorldEntity::RoomWorldEntity(ObjectGuid const& guid) : WorldObject(false)
|
||||
{
|
||||
_Create(guid);
|
||||
m_objectTypeId = TYPEID_HOUSING_ENTITY;
|
||||
m_updateFlag.HasEntityPosition = true;
|
||||
m_updateFlag.Stationary = true;
|
||||
SetIsNewObject(true);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingRoom_C, false, &_room);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, &_mirroredPosition);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HousingRoom, false);
|
||||
}
|
||||
|
||||
bool RoomWorldEntity::AttachToMap(Map* map, Position const& worldPosition)
|
||||
{
|
||||
if (!map)
|
||||
return false;
|
||||
|
||||
SetMap(map);
|
||||
Relocate(worldPosition);
|
||||
SetObjectScale(1.0f);
|
||||
if (!IsPositionValid())
|
||||
return false;
|
||||
|
||||
PhasingHandler::InitDbPhaseShift(GetPhaseShift(), 0, 0, 0);
|
||||
SetZoneScript();
|
||||
UpdatePositionData();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RoomWorldEntity::AddToWorld()
|
||||
{
|
||||
if (!IsInWorld())
|
||||
{
|
||||
GetMap()->GetObjectsStore().Insert<RoomWorldEntity>(this);
|
||||
WorldObject::AddToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
void RoomWorldEntity::RemoveFromWorld()
|
||||
{
|
||||
if (IsInWorld())
|
||||
{
|
||||
WorldObject::RemoveFromWorld();
|
||||
GetMap()->GetObjectsStore().Remove<RoomWorldEntity>(this);
|
||||
}
|
||||
}
|
||||
|
||||
void RoomWorldEntity::Update(uint32 diff)
|
||||
{
|
||||
WorldObject::Update(diff);
|
||||
}
|
||||
|
||||
void RoomWorldEntity::InitializeRoom(ObjectGuid const& houseGuid, int32 houseRoomId, int32 flags,
|
||||
TaggedPosition<Position::XYZ> const& localPosition, QuaternionData const& localRotation,
|
||||
float localScale, uint8 attachmentFlags)
|
||||
{
|
||||
UF::MutableFieldReference<UF::HousingRoomData, false> fields(_room);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::HouseRoomID), houseRoomId);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::Flags), flags);
|
||||
|
||||
UF::MutableFieldReference<UF::MirroredPositionData, false> mirrored(_mirroredPosition);
|
||||
auto position = mirrored.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), ObjectGuid::Empty);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace), localPosition);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), localRotation);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), localScale);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachmentFlags);
|
||||
SetHousingRoomMovement(houseGuid);
|
||||
}
|
||||
|
||||
void RoomWorldEntity::AddRoomMeshObject(ObjectGuid const& meshGuid)
|
||||
{
|
||||
UF::MutableFieldReference<UF::HousingRoomData, false> fields(_room);
|
||||
AddDynamicUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::MeshObjects)) = meshGuid;
|
||||
}
|
||||
|
||||
void RoomWorldEntity::ClearValuesChangesMask()
|
||||
{
|
||||
_room.ClearChangesMask();
|
||||
_mirroredPosition.ClearChangesMask();
|
||||
Object::ClearValuesChangesMask();
|
||||
}
|
||||
|
||||
void RoomWorldEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
ByteBuffer& buffer = data->GetBuffer();
|
||||
buffer << uint8(UPDATETYPE_CREATE_OBJECT);
|
||||
buffer << GetGUID();
|
||||
buffer << uint8(m_objectTypeId);
|
||||
BuildMovementUpdate(buffer, m_updateFlag, target);
|
||||
|
||||
UF::UpdateFieldFlag fieldFlags = GetUpdateFieldFlagsFor(target);
|
||||
std::size_t sizePosition = buffer.wpos();
|
||||
buffer << uint32(0);
|
||||
buffer << uint8(fieldFlags);
|
||||
std::span<WowCS::EntityFragment const> fragments = m_entityFragments.GetIds();
|
||||
buffer.append(fragments.data(), fragments.size());
|
||||
buffer << uint8(WowCS::EntityFragment::End);
|
||||
|
||||
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
|
||||
{
|
||||
WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
|
||||
if (WowCS::IsIndirectFragment(fragmentId))
|
||||
buffer << uint8(1);
|
||||
|
||||
WowCS::EntityFragmentInfo->SerializeCreate[static_cast<std::size_t>(fragmentId)](
|
||||
m_entityFragments.Updateable.Data[i], fieldFlags, buffer, target, this);
|
||||
}
|
||||
|
||||
buffer.put<uint32>(sizePosition, buffer.wpos() - sizePosition - 4);
|
||||
data->AddUpdateBlock();
|
||||
}
|
||||
|
||||
DynamicWorldEntity::DynamicWorldEntity(Kind kind, ObjectGuid const& guid) : WorldObject(false), _kind(kind)
|
||||
{
|
||||
_Create(guid);
|
||||
m_objectTypeId = TYPEID_MESH_OBJECT;
|
||||
m_updateFlag.MeshObject = true;
|
||||
SetIsNewObject(true);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMeshObjectData_C, false, &_mesh);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, false, &_mirroredPosition);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_MeshObject, false);
|
||||
|
||||
switch (_kind)
|
||||
{
|
||||
case Kind::Fixture:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingFixture_C, false, &_fixture);
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, false);
|
||||
break;
|
||||
case Kind::RoomComponentMesh:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingRoomComponentMesh_C, false, &_roomComponent);
|
||||
break;
|
||||
case Kind::Decor:
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingDecor_C, false, &_decor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicWorldEntity::AttachToMap(Map* map, Position const& worldPosition)
|
||||
{
|
||||
if (!map)
|
||||
return false;
|
||||
|
||||
SetMap(map);
|
||||
Relocate(worldPosition);
|
||||
if (!IsPositionValid())
|
||||
return false;
|
||||
|
||||
PhasingHandler::InitDbPhaseShift(GetPhaseShift(), 0, 0, 0);
|
||||
SetZoneScript();
|
||||
UpdatePositionData();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::AddToWorld()
|
||||
{
|
||||
if (!IsInWorld())
|
||||
{
|
||||
GetMap()->GetObjectsStore().Insert<DynamicWorldEntity>(this);
|
||||
WorldObject::AddToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::RemoveFromWorld()
|
||||
{
|
||||
if (IsInWorld())
|
||||
{
|
||||
WorldObject::RemoveFromWorld();
|
||||
GetMap()->GetObjectsStore().Remove<DynamicWorldEntity>(this);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::Update(uint32 diff)
|
||||
{
|
||||
WorldObject::Update(diff);
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::InitializeMesh(uint32 fileDataID, bool isWmo, bool isRoom,
|
||||
ObjectGuid const& attachParent, TaggedPosition<Position::XYZ> const& localPosition,
|
||||
QuaternionData const& localRotation, float localScale, uint8 attachmentFlags)
|
||||
{
|
||||
SetEntry(fileDataID);
|
||||
SetObjectScale(1.0f);
|
||||
|
||||
UF::MutableFieldReference<UF::MeshObjectData, false> mesh(_mesh);
|
||||
SetUpdateFieldValue(mesh.ModifyValue(&UF::MeshObjectData::IsWMO), isWmo);
|
||||
SetUpdateFieldValue(mesh.ModifyValue(&UF::MeshObjectData::IsRoom), isRoom);
|
||||
SetUpdateFieldValue(mesh.ModifyValue(&UF::MeshObjectData::FileDataID), int32(fileDataID));
|
||||
|
||||
UF::MutableFieldReference<UF::MirroredPositionData, false> mirrored(_mirroredPosition);
|
||||
auto position = mirrored.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(localPosition));
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), localRotation);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), localScale);
|
||||
SetUpdateFieldValue(position.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachmentFlags);
|
||||
|
||||
SetHousingMeshMovement(attachParent, localPosition, localRotation, localScale, attachmentFlags);
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::InitializeFixture(ObjectGuid const& houseGuid, int32 exteriorComponentID,
|
||||
int32 houseExteriorWmoDataID, int32 hookID, uint8 componentType, uint8 size)
|
||||
{
|
||||
ASSERT(_kind == Kind::Fixture);
|
||||
UF::MutableFieldReference<UF::HousingFixtureData, false> fields(_fixture);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::ExteriorComponentID), exteriorComponentID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::HouseExteriorWmoDataID), houseExteriorWmoDataID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::ExteriorComponentHookID), hookID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::AttachParentGUID), m_housingMovement.AttachParentGUID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::Guid), GetGUID());
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::GameObjectGUID), ObjectGuid::Empty);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::ExteriorComponentType), componentType);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::Field_59), uint8(1));
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingFixtureData::Size), size);
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::InitializeRoomComponent(ObjectGuid const& roomGuid, int32 optionID,
|
||||
int32 componentID, uint8 componentType, int32 houseThemeID, int32 textureID, int32 typeParam)
|
||||
{
|
||||
ASSERT(_kind == Kind::RoomComponentMesh);
|
||||
UF::MutableFieldReference<UF::HousingRoomComponentMeshData, false> fields(_roomComponent);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomGUID), roomGuid);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentOptionID), optionID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentID), componentID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentType), componentType);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::HouseThemeID), houseThemeID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTextureID), textureID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTypeParam), typeParam);
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::InitializeDecor(ObjectGuid const& decorGuid, ObjectGuid const& roomGuid,
|
||||
ObjectGuid const& attachParentGuid, uint8 flags)
|
||||
{
|
||||
ASSERT(_kind == Kind::Decor);
|
||||
UF::MutableFieldReference<UF::HousingDecorData, false> fields(_decor);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingDecorData::DecorGUID), decorGuid);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingDecorData::AttachParentGUID), attachParentGuid);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingDecorData::Flags), flags);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingDecorData::TargetGameObjectGUID), ObjectGuid::Empty);
|
||||
SetHousingDecorMovement(roomGuid);
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
ByteBuffer& buffer = data->GetBuffer();
|
||||
buffer << uint8(UPDATETYPE_CREATE_OBJECT);
|
||||
buffer << GetGUID();
|
||||
buffer << uint8(m_objectTypeId);
|
||||
BuildMovementUpdate(buffer, m_updateFlag, target);
|
||||
|
||||
UF::UpdateFieldFlag fieldFlags = GetUpdateFieldFlagsFor(target);
|
||||
std::size_t sizePosition = buffer.wpos();
|
||||
buffer << uint32(0);
|
||||
buffer << uint8(fieldFlags);
|
||||
std::span<WowCS::EntityFragment const> fragments = m_entityFragments.GetIds();
|
||||
buffer.append(fragments.data(), fragments.size());
|
||||
buffer << uint8(WowCS::EntityFragment::End);
|
||||
|
||||
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
|
||||
{
|
||||
WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
|
||||
if (WowCS::IsIndirectFragment(fragmentId))
|
||||
buffer << uint8(1);
|
||||
|
||||
WowCS::EntityFragmentInfo->SerializeCreate[static_cast<std::size_t>(fragmentId)](
|
||||
m_entityFragments.Updateable.Data[i], fieldFlags, buffer, target, this);
|
||||
}
|
||||
|
||||
buffer.put<uint32>(sizePosition, buffer.wpos() - sizePosition - 4);
|
||||
data->AddUpdateBlock();
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const
|
||||
{
|
||||
m_objectData->WriteCreate(flags, data, target, this);
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const
|
||||
{
|
||||
m_objectData->WriteUpdate(flags, data, target, this);
|
||||
}
|
||||
|
||||
void DynamicWorldEntity::ClearValuesChangesMask()
|
||||
{
|
||||
Object::ClearValuesChangesMask();
|
||||
}
|
||||
|
||||
void SendHouseDynamicEntities(Player* player, ObjectGuid const& houseGuid, uint8 /*plotId*/)
|
||||
{
|
||||
if (!player || houseGuid.IsEmpty())
|
||||
return;
|
||||
|
||||
// A house graph belongs only to its neighborhood or its own interior.
|
||||
if (!player->IsInWorld() || !IsHouseInMap(player->GetMap(), houseGuid))
|
||||
return;
|
||||
|
||||
std::string const escapedHouseGuid = houseGuid.ToString();
|
||||
|
||||
QueryResult rooms = CharacterDatabase.PQuery(
|
||||
"SELECT RoomGuid, HouseRoomID, Flags, PositionX, PositionY, PositionZ, "
|
||||
"RotationX, RotationY, RotationZ, RotationW, Scale, AttachmentFlags FROM housing_room_entity "
|
||||
"WHERE HouseGuid = '{}' ORDER BY HouseRoomID, RoomGuid", escapedHouseGuid);
|
||||
if (rooms)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = rooms->Fetch();
|
||||
ObjectGuid const roomGuid = ObjectGuid::FromString(fields[0].GetString());
|
||||
if (roomGuid.GetHigh() != HighGuid::Housing ||
|
||||
!IsSafeHousingTransform(fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(),
|
||||
fields[6].GetFloat(), fields[7].GetFloat(), fields[8].GetFloat(), fields[9].GetFloat(),
|
||||
fields[10].GetFloat()))
|
||||
{
|
||||
QuarantineHousingEntity("room", fields[0].GetString(), "invalid-guid-or-transform");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player->GetMap()->GetHousingRoomEntity(roomGuid))
|
||||
continue;
|
||||
|
||||
RoomWorldEntity* room = new RoomWorldEntity(roomGuid);
|
||||
room->InitializeRoom(houseGuid, fields[1].GetInt32(), fields[2].GetInt32(),
|
||||
{ fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat() },
|
||||
{ fields[6].GetFloat(), fields[7].GetFloat(), fields[8].GetFloat(), fields[9].GetFloat() },
|
||||
fields[10].GetFloat(), fields[11].GetUInt8());
|
||||
|
||||
QueryResult roomMeshes = CharacterDatabase.PQuery(
|
||||
"SELECT EntityGuid FROM housing_room_component_entity WHERE HouseGuid = '{}' "
|
||||
"AND RoomGuid = '{}' ORDER BY EntityGuid", escapedHouseGuid, fields[0].GetString());
|
||||
if (roomMeshes)
|
||||
{
|
||||
do
|
||||
{
|
||||
ObjectGuid const meshGuid = ObjectGuid::FromString(roomMeshes->Fetch()[0].GetString());
|
||||
if (meshGuid.GetHigh() == HighGuid::MeshObject)
|
||||
room->AddRoomMeshObject(meshGuid);
|
||||
} while (roomMeshes->NextRow());
|
||||
}
|
||||
|
||||
if (!room->AttachToMap(player->GetMap(), *player) || !player->GetMap()->AddToMap(room))
|
||||
delete room;
|
||||
} while (rooms->NextRow());
|
||||
}
|
||||
|
||||
// Resolve room-to-room links only after every persisted room parent has
|
||||
// been materialized. Sending AttachedRoomGUID in the first parent's CREATE
|
||||
// could otherwise reference a second room the client has not received yet.
|
||||
QueryResult roomDoors = CharacterDatabase.PQuery(
|
||||
"SELECT RoomGuid, RoomComponentID, OffsetX, OffsetY, OffsetZ, RoomComponentType, AttachedRoomGuid "
|
||||
"FROM housing_room_connection WHERE HouseGuid = '{}' ORDER BY RoomGuid, RoomComponentID",
|
||||
escapedHouseGuid);
|
||||
if (roomDoors)
|
||||
{
|
||||
ObjectGuid activeRoomGuid;
|
||||
std::vector<RoomDoorState> activeDoors;
|
||||
|
||||
auto flushDoors = [&]()
|
||||
{
|
||||
if (activeRoomGuid.IsEmpty())
|
||||
return;
|
||||
|
||||
if (RoomWorldEntity* room = player->GetMap()->GetHousingRoomEntity(activeRoomGuid))
|
||||
room->ReplaceRoomDoors(activeDoors);
|
||||
};
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = roomDoors->Fetch();
|
||||
ObjectGuid const roomGuid = ObjectGuid::FromString(fields[0].GetString());
|
||||
ObjectGuid const attachedRoomGuid = ObjectGuid::FromString(fields[6].GetString());
|
||||
if (roomGuid.GetHigh() != HighGuid::Housing || roomGuid.GetSubType() != 2 ||
|
||||
attachedRoomGuid.GetHigh() != HighGuid::Housing || attachedRoomGuid.GetSubType() != 2 ||
|
||||
fields[1].GetUInt32() == 0 || fields[5].GetUInt8() == 0)
|
||||
{
|
||||
QuarantineHousingEntity("room-door", fields[0].GetString(), "invalid-persisted-door-link");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (activeRoomGuid != roomGuid)
|
||||
{
|
||||
flushDoors();
|
||||
activeRoomGuid = roomGuid;
|
||||
activeDoors.clear();
|
||||
}
|
||||
|
||||
RoomDoorState& door = activeDoors.emplace_back();
|
||||
door.RoomComponentID = fields[1].GetInt32();
|
||||
door.RoomComponentOffset = TaggedPosition<Position::XYZ>(fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat());
|
||||
door.RoomComponentType = fields[5].GetUInt8();
|
||||
door.AttachedRoomGUID = attachedRoomGuid;
|
||||
} while (roomDoors->NextRow());
|
||||
|
||||
flushDoors();
|
||||
}
|
||||
|
||||
QueryResult components = CharacterDatabase.PQuery(
|
||||
"SELECT EntityGuid, RoomGuid, FileDataID, IsWMO, IsRoom, AttachParentGuid, "
|
||||
"PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, RotationW, Scale, "
|
||||
"AttachmentFlags, RoomComponentOptionID, RoomComponentID, RoomComponentType, "
|
||||
"HouseThemeID, RoomComponentTextureID, RoomComponentTypeParam "
|
||||
"FROM housing_room_component_entity WHERE HouseGuid = '{}' ORDER BY RoomGuid, EntityGuid",
|
||||
escapedHouseGuid);
|
||||
if (components)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = components->Fetch();
|
||||
ObjectGuid const entityGuid = ObjectGuid::FromString(fields[0].GetString());
|
||||
ObjectGuid const roomGuid = ObjectGuid::FromString(fields[1].GetString());
|
||||
ObjectGuid const parentGuid = ObjectGuid::FromString(fields[5].GetString());
|
||||
if (entityGuid.GetHigh() != HighGuid::MeshObject || roomGuid.GetHigh() != HighGuid::Housing ||
|
||||
!fields[2].GetUInt32() ||
|
||||
!IsSafeHousingTransform(fields[6].GetFloat(), fields[7].GetFloat(), fields[8].GetFloat(),
|
||||
fields[9].GetFloat(), fields[10].GetFloat(), fields[11].GetFloat(), fields[12].GetFloat(),
|
||||
fields[13].GetFloat()))
|
||||
{
|
||||
QuarantineHousingEntity("room-component", fields[0].GetString(), "invalid-guid-file-or-transform");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player->GetMap()->GetHousingDynamicEntity(entityGuid))
|
||||
continue;
|
||||
|
||||
DynamicWorldEntity* entity = new DynamicWorldEntity(DynamicWorldEntity::Kind::RoomComponentMesh, entityGuid);
|
||||
entity->InitializeMesh(fields[2].GetUInt32(), fields[3].GetUInt8() != 0, fields[4].GetUInt8() != 0,
|
||||
parentGuid, { fields[6].GetFloat(), fields[7].GetFloat(), fields[8].GetFloat() },
|
||||
{ fields[9].GetFloat(), fields[10].GetFloat(), fields[11].GetFloat(), fields[12].GetFloat() },
|
||||
fields[13].GetFloat(), fields[14].GetUInt8());
|
||||
entity->InitializeRoomComponent(roomGuid, fields[15].GetInt32(), fields[16].GetInt32(),
|
||||
fields[17].GetUInt8(), fields[18].GetInt32(), fields[19].GetInt32(), fields[20].GetInt32());
|
||||
if (!entity->AttachToMap(player->GetMap(), *player) || !player->GetMap()->AddToMap(entity))
|
||||
delete entity;
|
||||
} while (components->NextRow());
|
||||
}
|
||||
|
||||
QueryResult fixtures = CharacterDatabase.PQuery(
|
||||
"SELECT EntityGuid, FileDataID, IsWMO, IsRoom, AttachParentGuid, "
|
||||
"PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, RotationW, Scale, "
|
||||
"AttachmentFlags, ExteriorComponentID, HouseExteriorWmoDataID, ExteriorComponentHookID, "
|
||||
"ExteriorComponentType, Size FROM housing_fixture_entity "
|
||||
"WHERE HouseGuid = '{}' ORDER BY EntityGuid", escapedHouseGuid);
|
||||
if (fixtures)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = fixtures->Fetch();
|
||||
ObjectGuid const entityGuid = ObjectGuid::FromString(fields[0].GetString());
|
||||
ObjectGuid const parentGuid = ObjectGuid::FromString(fields[4].GetString());
|
||||
if (entityGuid.GetHigh() != HighGuid::MeshObject || !fields[1].GetUInt32() ||
|
||||
!IsSafeHousingTransform(fields[5].GetFloat(), fields[6].GetFloat(), fields[7].GetFloat(),
|
||||
fields[8].GetFloat(), fields[9].GetFloat(), fields[10].GetFloat(), fields[11].GetFloat(),
|
||||
fields[12].GetFloat()))
|
||||
{
|
||||
QuarantineHousingEntity("fixture", fields[0].GetString(), "invalid-guid-file-or-transform");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player->GetMap()->GetHousingDynamicEntity(entityGuid))
|
||||
continue;
|
||||
|
||||
DynamicWorldEntity* entity = new DynamicWorldEntity(DynamicWorldEntity::Kind::Fixture, entityGuid);
|
||||
entity->InitializeMesh(fields[1].GetUInt32(), fields[2].GetUInt8() != 0, fields[3].GetUInt8() != 0,
|
||||
parentGuid, { fields[5].GetFloat(), fields[6].GetFloat(), fields[7].GetFloat() },
|
||||
{ fields[8].GetFloat(), fields[9].GetFloat(), fields[10].GetFloat(), fields[11].GetFloat() },
|
||||
fields[12].GetFloat(), fields[13].GetUInt8());
|
||||
entity->InitializeFixture(houseGuid, fields[14].GetInt32(), fields[15].GetInt32(),
|
||||
fields[16].GetInt32(), fields[17].GetUInt8(), fields[18].GetUInt8());
|
||||
if (!entity->AttachToMap(player->GetMap(), *player) || !player->GetMap()->AddToMap(entity))
|
||||
delete entity;
|
||||
} while (fixtures->NextRow());
|
||||
}
|
||||
|
||||
QueryResult decor = CharacterDatabase.PQuery(
|
||||
"SELECT EntityGuid, DecorGuid, RoomGuid, FileDataID, IsWMO, IsRoom, AttachParentGuid, "
|
||||
"PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, RotationW, Scale, "
|
||||
"AttachmentFlags, Flags FROM housing_placed_decor "
|
||||
"WHERE HouseGuid = '{}' AND EntityGuid IS NOT NULL ORDER BY DecorGuid", escapedHouseGuid);
|
||||
if (decor)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = decor->Fetch();
|
||||
ObjectGuid const entityGuid = ObjectGuid::FromString(fields[0].GetString());
|
||||
ObjectGuid const decorGuid = ObjectGuid::FromString(fields[1].GetString());
|
||||
ObjectGuid const roomGuid = ObjectGuid::FromString(fields[2].GetString());
|
||||
ObjectGuid const parentGuid = ObjectGuid::FromString(fields[6].GetString());
|
||||
if (entityGuid.GetHigh() != HighGuid::MeshObject || decorGuid.GetHigh() != HighGuid::Housing ||
|
||||
!fields[3].GetUInt32() ||
|
||||
!IsSafeHousingTransform(fields[7].GetFloat(), fields[8].GetFloat(), fields[9].GetFloat(),
|
||||
fields[10].GetFloat(), fields[11].GetFloat(), fields[12].GetFloat(), fields[13].GetFloat(),
|
||||
fields[14].GetFloat()))
|
||||
{
|
||||
QuarantineHousingEntity("decor", fields[0].GetString(), "invalid-guid-file-or-transform");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player->GetMap()->GetHousingDynamicEntity(entityGuid))
|
||||
continue;
|
||||
|
||||
DynamicWorldEntity* entity = new DynamicWorldEntity(DynamicWorldEntity::Kind::Decor, entityGuid);
|
||||
entity->InitializeMesh(fields[3].GetUInt32(), fields[4].GetUInt8() != 0, fields[5].GetUInt8() != 0,
|
||||
parentGuid, { fields[7].GetFloat(), fields[8].GetFloat(), fields[9].GetFloat() },
|
||||
{ fields[10].GetFloat(), fields[11].GetFloat(), fields[12].GetFloat(), fields[13].GetFloat() },
|
||||
fields[14].GetFloat(), fields[15].GetUInt8());
|
||||
entity->InitializeDecor(decorGuid, roomGuid, parentGuid, fields[16].GetUInt8());
|
||||
if (!entity->AttachToMap(player->GetMap(), *player) || !player->GetMap()->AddToMap(entity))
|
||||
delete entity;
|
||||
} while (decor->NextRow());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_DYNAMIC_ENTITY_H
|
||||
#define TRINITYCORE_HOUSING_DYNAMIC_ENTITY_H
|
||||
|
||||
#include "BaseEntity.h"
|
||||
#include "GridObject.h"
|
||||
#include "MapObject.h"
|
||||
#include "Object.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Map;
|
||||
class Player;
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
struct RoomDoorState
|
||||
{
|
||||
int32 RoomComponentID = 0;
|
||||
TaggedPosition<Position::XYZ> RoomComponentOffset;
|
||||
uint8 RoomComponentType = 0;
|
||||
ObjectGuid AttachedRoomGUID;
|
||||
};
|
||||
|
||||
class TC_GAME_API DynamicEntity final : public BaseEntity
|
||||
{
|
||||
public:
|
||||
enum class Kind : uint8
|
||||
{
|
||||
PlayerHouse,
|
||||
Room,
|
||||
Neighborhood,
|
||||
Storage
|
||||
};
|
||||
|
||||
DynamicEntity(Kind kind, ObjectGuid const& guid);
|
||||
|
||||
void InitializePlayerHouse(ObjectGuid const& bnetAccount, ObjectGuid const& cosmeticOwner,
|
||||
int32 plotIndex, uint32 level, uint64 favor, ObjectGuid const& entityGuid);
|
||||
void InitializeRoom(ObjectGuid const& houseGuid, int32 houseRoomId, int32 flags,
|
||||
TaggedPosition<Position::XYZ> const& localPosition, QuaternionData const& localRotation,
|
||||
float localScale, uint8 attachmentFlags);
|
||||
void AddRoomMeshObject(ObjectGuid const& meshGuid);
|
||||
void InitializeNeighborhood(std::string const& name, ObjectGuid const& ownerGuid);
|
||||
void InitializeStorage(uint32 decorMaxOwnedCount);
|
||||
void AddStorageDecor(ObjectGuid const& decorGuid, ObjectGuid const& houseGuid,
|
||||
uint8 placementStatus, uint8 sourceType, std::string const& sourceValue);
|
||||
|
||||
std::string GetNameForLocaleIdx(LocaleConstant /*locale*/) const override { return "HousingDynamicEntity"; }
|
||||
|
||||
private:
|
||||
bool AddToObjectUpdate() override { return false; }
|
||||
void RemoveFromObjectUpdate() override {}
|
||||
|
||||
Kind _kind;
|
||||
UF::HousingPlayerHouseData _playerHouse;
|
||||
UF::HousingRoomData _room;
|
||||
UF::MirroredPositionData _roomMirroredPosition;
|
||||
UF::NeighborhoodMirrorData _neighborhood;
|
||||
UF::HousingStorageData _storage;
|
||||
};
|
||||
|
||||
class TC_GAME_API RoomWorldEntity final : public WorldObject, public GridObject<RoomWorldEntity>, public MapObject
|
||||
{
|
||||
public:
|
||||
explicit RoomWorldEntity(ObjectGuid const& guid);
|
||||
~RoomWorldEntity() override = default;
|
||||
|
||||
bool AttachToMap(Map* map, Position const& worldPosition);
|
||||
void AddToWorld() override;
|
||||
void RemoveFromWorld() override;
|
||||
void Update(uint32 diff) override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant /*locale*/) const override { return "HousingRoomWorldEntity"; }
|
||||
|
||||
void InitializeRoom(ObjectGuid const& houseGuid, int32 houseRoomId, int32 flags,
|
||||
TaggedPosition<Position::XYZ> const& localPosition, QuaternionData const& localRotation,
|
||||
float localScale, uint8 attachmentFlags);
|
||||
void AddRoomMeshObject(ObjectGuid const& meshGuid);
|
||||
|
||||
void AddRoomDoor(RoomDoorState const& door)
|
||||
{
|
||||
if (door.RoomComponentID <= 0 || door.RoomComponentType == 0 ||
|
||||
door.AttachedRoomGUID.GetHigh() != HighGuid::Housing || door.AttachedRoomGUID.GetSubType() != 2)
|
||||
return;
|
||||
|
||||
UF::MutableFieldReference<UF::HousingRoomData, false> fields(_room);
|
||||
auto entry = AddDynamicUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::Doors));
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentID).SetValue(door.RoomComponentID);
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentOffset).SetValue(door.RoomComponentOffset);
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentType).SetValue(door.RoomComponentType);
|
||||
entry.ModifyValue(&UF::HousingDoorData::AttachedRoomGUID).SetValue(door.AttachedRoomGUID);
|
||||
}
|
||||
|
||||
void ReplaceRoomDoors(std::vector<RoomDoorState> const& doors)
|
||||
{
|
||||
UF::MutableFieldReference<UF::HousingRoomData, false> fields(_room);
|
||||
ClearDynamicUpdateFieldValues(fields.ModifyValue(&UF::HousingRoomData::Doors));
|
||||
for (RoomDoorState const& door : doors)
|
||||
{
|
||||
if (door.RoomComponentID <= 0 || door.RoomComponentType == 0 ||
|
||||
door.AttachedRoomGUID.GetHigh() != HighGuid::Housing || door.AttachedRoomGUID.GetSubType() != 2)
|
||||
continue;
|
||||
|
||||
auto entry = AddDynamicUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::Doors));
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentID).SetValue(door.RoomComponentID);
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentOffset).SetValue(door.RoomComponentOffset);
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentType).SetValue(door.RoomComponentType);
|
||||
entry.ModifyValue(&UF::HousingDoorData::AttachedRoomGUID).SetValue(door.AttachedRoomGUID);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaceRoomGraph(std::vector<ObjectGuid> const& meshGuids, std::vector<RoomDoorState> const& doors)
|
||||
{
|
||||
UF::MutableFieldReference<UF::HousingRoomData, false> fields(_room);
|
||||
ClearDynamicUpdateFieldValues(fields.ModifyValue(&UF::HousingRoomData::MeshObjects));
|
||||
for (ObjectGuid const& meshGuid : meshGuids)
|
||||
if (meshGuid.GetHigh() == HighGuid::MeshObject)
|
||||
AddDynamicUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::MeshObjects)) = meshGuid;
|
||||
|
||||
ClearDynamicUpdateFieldValues(fields.ModifyValue(&UF::HousingRoomData::Doors));
|
||||
for (RoomDoorState const& door : doors)
|
||||
{
|
||||
if (door.RoomComponentID <= 0 || door.RoomComponentType == 0 ||
|
||||
door.AttachedRoomGUID.GetHigh() != HighGuid::Housing || door.AttachedRoomGUID.GetSubType() != 2)
|
||||
continue;
|
||||
|
||||
auto entry = AddDynamicUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomData::Doors));
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentID).SetValue(door.RoomComponentID);
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentOffset).SetValue(door.RoomComponentOffset);
|
||||
entry.ModifyValue(&UF::HousingDoorData::RoomComponentType).SetValue(door.RoomComponentType);
|
||||
entry.ModifyValue(&UF::HousingDoorData::AttachedRoomGUID).SetValue(door.AttachedRoomGUID);
|
||||
}
|
||||
}
|
||||
|
||||
ObjectGuid GetCreatorGUID() const override { return ObjectGuid::Empty; }
|
||||
ObjectGuid GetOwnerGUID() const override { return ObjectGuid::Empty; }
|
||||
uint32 GetFaction() const override { return 0; }
|
||||
void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
|
||||
private:
|
||||
void BuildValuesCreate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const override {}
|
||||
void BuildValuesUpdate(UF::UpdateFieldFlag /*flags*/, ByteBuffer& /*data*/, Player const* /*target*/) const override {}
|
||||
void ClearValuesChangesMask() override;
|
||||
|
||||
UF::HousingRoomData _room;
|
||||
UF::MirroredPositionData _mirroredPosition;
|
||||
};
|
||||
|
||||
class TC_GAME_API DynamicWorldEntity final : public WorldObject, public GridObject<DynamicWorldEntity>, public MapObject
|
||||
{
|
||||
public:
|
||||
enum class Kind : uint8
|
||||
{
|
||||
Fixture,
|
||||
RoomComponentMesh,
|
||||
Decor
|
||||
};
|
||||
|
||||
DynamicWorldEntity(Kind kind, ObjectGuid const& guid);
|
||||
~DynamicWorldEntity() override = default;
|
||||
|
||||
bool AttachToMap(Map* map, Position const& worldPosition);
|
||||
void AddToWorld() override;
|
||||
void RemoveFromWorld() override;
|
||||
void Update(uint32 diff) override;
|
||||
std::string GetNameForLocaleIdx(LocaleConstant /*locale*/) const override { return "HousingDynamicWorldEntity"; }
|
||||
|
||||
void InitializeMesh(uint32 fileDataID, bool isWmo, bool isRoom, ObjectGuid const& attachParent,
|
||||
TaggedPosition<Position::XYZ> const& localPosition, QuaternionData const& localRotation, float localScale,
|
||||
uint8 attachmentFlags);
|
||||
void InitializeFixture(ObjectGuid const& houseGuid, int32 exteriorComponentID,
|
||||
int32 houseExteriorWmoDataID, int32 hookID, uint8 componentType, uint8 size);
|
||||
void InitializeRoomComponent(ObjectGuid const& roomGuid, int32 optionID, int32 componentID,
|
||||
uint8 componentType, int32 houseThemeID, int32 textureID, int32 typeParam);
|
||||
|
||||
bool UpdateRoomComponentVisual(uint32 fileDataID, int32 optionID, uint8 componentType,
|
||||
int32 houseThemeID, int32 textureID, int32 typeParam)
|
||||
{
|
||||
if (_kind != Kind::RoomComponentMesh || fileDataID == 0)
|
||||
return false;
|
||||
|
||||
SetEntry(fileDataID);
|
||||
UF::MutableFieldReference<UF::MeshObjectData, false> mesh(_mesh);
|
||||
SetUpdateFieldValue(mesh.ModifyValue(&UF::MeshObjectData::FileDataID), int32(fileDataID));
|
||||
|
||||
UF::MutableFieldReference<UF::HousingRoomComponentMeshData, false> fields(_roomComponent);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentOptionID), optionID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentType), componentType);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::HouseThemeID), houseThemeID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTextureID), textureID);
|
||||
SetUpdateFieldValue(fields.ModifyValue(&UF::HousingRoomComponentMeshData::RoomComponentTypeParam), typeParam);
|
||||
return true;
|
||||
}
|
||||
|
||||
void InitializeDecor(ObjectGuid const& decorGuid, ObjectGuid const& roomGuid,
|
||||
ObjectGuid const& attachParentGuid, uint8 flags);
|
||||
|
||||
ObjectGuid GetCreatorGUID() const override { return ObjectGuid::Empty; }
|
||||
ObjectGuid GetOwnerGUID() const override { return ObjectGuid::Empty; }
|
||||
uint32 GetFaction() const override { return 0; }
|
||||
void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
|
||||
|
||||
private:
|
||||
void BuildValuesCreate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
void BuildValuesUpdate(UF::UpdateFieldFlag flags, ByteBuffer& data, Player const* target) const override;
|
||||
void ClearValuesChangesMask() override;
|
||||
|
||||
Kind _kind;
|
||||
UF::MeshObjectData _mesh;
|
||||
UF::MirroredPositionData _mirroredPosition;
|
||||
UF::HousingFixtureData _fixture;
|
||||
UF::HousingRoomComponentMeshData _roomComponent;
|
||||
UF::HousingDecorData _decor;
|
||||
};
|
||||
|
||||
TC_GAME_API void SendPlayerHouseCreate(Player* player, ObjectGuid const& houseGuid,
|
||||
ObjectGuid const& bnetAccount, ObjectGuid const& cosmeticOwner, int32 plotIndex,
|
||||
uint32 level, uint64 favor);
|
||||
|
||||
struct StorageDecorEntry
|
||||
{
|
||||
ObjectGuid DecorGuid;
|
||||
ObjectGuid HouseGuid;
|
||||
uint8 PlacementStatus = 0;
|
||||
uint8 SourceType = 0;
|
||||
std::string SourceValue;
|
||||
};
|
||||
|
||||
// Retail attaches FHousingStorage_C directly to the Battle.net account object.
|
||||
TC_GAME_API void SendHousingStorageCreate(Player* player, ObjectGuid const& bnetAccount,
|
||||
uint32 decorMaxOwnedCount, std::vector<StorageDecorEntry> const& decorEntries);
|
||||
|
||||
// Sends the persisted room -> component/fixture/decor graph after map transfer.
|
||||
TC_GAME_API void SendHouseDynamicEntities(Player* player, ObjectGuid const& houseGuid, uint8 plotId);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -136,6 +136,10 @@ void Object::BuildEntityFragmentsForValuesUpdateForPlayerWithMask(ByteBuffer& da
|
||||
void Object::ClearValuesChangesMask()
|
||||
{
|
||||
m_values.ClearChangesMask(&Object::m_objectData);
|
||||
m_values.ClearChangesMask(&Object::m_housingDecorData);
|
||||
m_values.ClearChangesMask(&Object::m_housingRoomData);
|
||||
m_values.ClearChangesMask(&Object::m_housingRoomComponentMeshData);
|
||||
m_values.ClearChangesMask(&Object::m_housingFixtureData);
|
||||
}
|
||||
|
||||
void Object::BuildValuesUpdateWithFlag(UF::UpdateFieldFlag /*flags*/, ByteBuffer& data, Player const* /*target*/) const
|
||||
@@ -3229,3 +3233,46 @@ template TC_GAME_API void WorldObject::GetCreatureListWithOptionsInGrid(std::vec
|
||||
template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::list<Player*>&, float, bool) const;
|
||||
template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::deque<Player*>&, float, bool) const;
|
||||
template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::vector<Player*>&, float, bool) const;
|
||||
|
||||
|
||||
|
||||
void Object::SetHousingDecorDyeSlots(std::array<uint32, 3> const& dyeSlots)
|
||||
{
|
||||
if (!m_housingDecorData.has_value())
|
||||
return;
|
||||
|
||||
// Retail: the decor's FHousingDecor_C PersistedData gets HasDyeSlots + DyeSlots when dyed.
|
||||
auto persistedRef = m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::PersistedData, 0);
|
||||
if (std::ranges::any_of(dyeSlots, [](uint32 dye) { return dye != 0; }))
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::DyeSlots, 0)
|
||||
.ModifyValue(&UF::DecorDyeSlots::DyeColorID), { int32(dyeSlots[0]), int32(dyeSlots[1]), int32(dyeSlots[2]) });
|
||||
else
|
||||
RemoveOptionalUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::DyeSlots));
|
||||
}
|
||||
|
||||
void Object::SetHousingDecorPet(ObjectGuid battlePetGuid, uint32 creatureId, std::string petName, uint8 petBehavior,
|
||||
ObjectGuid spawnedPet /*= ObjectGuid::Empty*/)
|
||||
{
|
||||
if (!m_housingDecorData.has_value())
|
||||
return;
|
||||
|
||||
// Retail: binding a battle pet to CAN_ATTACH_PET decor fills FHousingDecor_C.PetInfo;
|
||||
// an empty GUID clears the optional. SpawnGroup stays empty (no spawn-group linkage).
|
||||
if (battlePetGuid.IsEmpty())
|
||||
{
|
||||
RemoveOptionalUpdateFieldValue(m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::PetInfo));
|
||||
return;
|
||||
}
|
||||
|
||||
auto petRef = m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::PetInfo, 0);
|
||||
SetUpdateFieldValue(petRef.ModifyValue(&UF::DecorPetInfo::BattlePetGUID), battlePetGuid);
|
||||
SetUpdateFieldValue(petRef.ModifyValue(&UF::DecorPetInfo::CreatureID), creatureId);
|
||||
if (!petName.empty())
|
||||
SetUpdateFieldValue(petRef.ModifyValue(&UF::DecorPetInfo::PetName), std::move(petName));
|
||||
SetUpdateFieldValue(petRef.ModifyValue(&UF::DecorPetInfo::PetBehavior), petBehavior);
|
||||
if (!spawnedPet.IsEmpty())
|
||||
SetUpdateFieldValue(petRef.ModifyValue(&UF::DecorPetInfo::SpawnedPet), spawnedPet);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class GameObject;
|
||||
class InstanceScript;
|
||||
class Item;
|
||||
class Map;
|
||||
class MeshObject;
|
||||
class Object;
|
||||
class Player;
|
||||
class Scenario;
|
||||
@@ -159,8 +160,25 @@ class TC_GAME_API Object : public BaseEntity
|
||||
static Conversation* ToConversation(Object* o) { return o && o->IsConversation() ? reinterpret_cast<Conversation*>(o) : nullptr; }
|
||||
static Conversation const* ToConversation(Object const* o) { return o && o->IsConversation() ? reinterpret_cast<Conversation const*>(o) : nullptr; }
|
||||
|
||||
MeshObject* ToMeshObject() { return IsMeshObject() ? reinterpret_cast<MeshObject*>(this) : nullptr; }
|
||||
MeshObject const* ToMeshObject() const { return IsMeshObject() ? reinterpret_cast<MeshObject const*>(this) : nullptr; }
|
||||
static MeshObject* ToMeshObject(Object* o) { return o && o->IsMeshObject() ? reinterpret_cast<MeshObject*>(o) : nullptr; }
|
||||
static MeshObject const* ToMeshObject(Object const* o) { return o && o->IsMeshObject() ? reinterpret_cast<MeshObject const*>(o) : nullptr; }
|
||||
|
||||
UF::UpdateField<UF::ObjectData, int32(WowCS::EntityFragment::CGObject), TYPEID_OBJECT> m_objectData;
|
||||
|
||||
// Housing entity fragments (optional - only set on housing entities)
|
||||
bool HasHousingDecorData() const { return m_housingDecorData.has_value(); }
|
||||
// DyeColor.db2 IDs per DecorDyeSlot (0 = undyed); all zero drops the optional DyeSlots.
|
||||
void SetHousingDecorDyeSlots(std::array<uint32, 3> const& dyeSlots);
|
||||
// Fills FHousingDecor_C.PetInfo (battle pet bound to this decor); empty GUID clears it.
|
||||
void SetHousingDecorPet(ObjectGuid battlePetGuid, uint32 creatureId, std::string petName, uint8 petBehavior,
|
||||
ObjectGuid spawnedPet = ObjectGuid::Empty);
|
||||
UF::OptionalUpdateField<UF::HousingDecorData, int32(WowCS::EntityFragment::FHousingDecor_C), 0> m_housingDecorData;
|
||||
UF::OptionalUpdateField<UF::HousingRoomData, int32(WowCS::EntityFragment::FHousingRoom_C), 0> m_housingRoomData;
|
||||
UF::OptionalUpdateField<UF::HousingRoomComponentMeshData, int32(WowCS::EntityFragment::FHousingRoomComponentMesh_C), 0> m_housingRoomComponentMeshData;
|
||||
UF::OptionalUpdateField<UF::HousingFixtureData, int32(WowCS::EntityFragment::FHousingFixture_C), 0> m_housingFixtureData;
|
||||
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
Trinity::unique_weak_ptr<Object> GetWeakPtr() const { return m_scriptRef; }
|
||||
|
||||
@@ -706,6 +706,7 @@ namespace
|
||||
{
|
||||
case 1:
|
||||
case 4:
|
||||
case 5:
|
||||
ctx.advance_to(AppendComponent<no_padding, dec>(ctx, subType));
|
||||
ctx.advance_to(AppendComponent<no_padding, dec>(ctx, uint32(guid.GetRawValue(1) >> 32) & 0xFFFF));
|
||||
ctx.advance_to(AppendComponent<no_padding, dec>(ctx, uint32(guid.GetRawValue(1)) & 0xFFFFFFFF));
|
||||
@@ -743,6 +744,7 @@ namespace
|
||||
{
|
||||
case 1:
|
||||
case 4:
|
||||
case 5:
|
||||
if (!ParseComponent<dec>(guidString, &arg1)
|
||||
|| !ParseComponent<dec>(guidString, &arg2)
|
||||
|| !ParseComponent<hex>(guidString, &arg3)
|
||||
@@ -1056,6 +1058,7 @@ ObjectGuid ObjectGuidFactory::CreateHousing(uint32 subType, uint32 arg1, uint32
|
||||
{
|
||||
case 1:
|
||||
case 4:
|
||||
case 5: // sub5 never appears on the wire (editor/placement mode); kept as an in-memory pass-through
|
||||
return ObjectGuid(uint64((uint64(HighGuid::Housing) << 58)
|
||||
| (uint64(subType & 0x1F) << 53)
|
||||
| (uint64(arg1 & 0xFFFF) << 32)
|
||||
@@ -1135,3 +1138,7 @@ ByteBuffer& operator>>(ByteBuffer& buf, ObjectGuid& guid)
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ enum TypeID : uint8
|
||||
|
||||
NUM_CLIENT_OBJECT_TYPES,
|
||||
|
||||
// objectType 18 in retail; must stay outside NUM_CLIENT_OBJECT_TYPES (BaseEntity's sentinel)
|
||||
TYPEID_HOUSING_ENTITY = 18
|
||||
};
|
||||
|
||||
|
||||
@@ -288,6 +288,9 @@ namespace UF
|
||||
template<typename T>
|
||||
struct SetUpdateFieldSetter
|
||||
{
|
||||
template<typename F>
|
||||
friend bool InsertSetUpdateFieldValue(SetUpdateFieldSetter<F>& setter, std::type_identity_t<F> const& key);
|
||||
|
||||
template<typename F>
|
||||
friend bool RemoveSetUpdateFieldValue(SetUpdateFieldSetter<F>& setter, std::type_identity_t<F> const& key);
|
||||
|
||||
|
||||
@@ -1903,139 +1903,140 @@ namespace UF
|
||||
};
|
||||
|
||||
struct DecorDyeSlots : public IsUpdateFieldStructureTag
|
||||
{
|
||||
std::array<int32, 3> DyeColorID = {};
|
||||
{
|
||||
std::array<int32, 3> DyeColorID = {};
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(DecorDyeSlots const& right) const;
|
||||
bool operator!=(DecorDyeSlots const& right) const { return !(*this == right); }
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(DecorDyeSlots const& right) const;
|
||||
bool operator!=(DecorDyeSlots const& right) const { return !(*this == right); }
|
||||
};
|
||||
|
||||
struct DecorStoragePersistedData : public IsUpdateFieldStructureTag, public HasChangesMask<5>
|
||||
{
|
||||
UpdateField<ObjectGuid, -1, 0> HouseGUID;
|
||||
UpdateField<uint8, -1, 1> PlacementStatus;
|
||||
OptionalUpdateField<UF::DecorDyeSlots, -1, 2> DyeSlots;
|
||||
UpdateField<uint8, -1, 3> SourceType;
|
||||
UpdateField<std::string, -1, 4> SourceValue;
|
||||
{
|
||||
UpdateField<ObjectGuid, -1, 0> HouseGUID;
|
||||
UpdateField<uint8, -1, 1> PlacementStatus;
|
||||
OptionalUpdateField<UF::DecorDyeSlots, -1, 2> DyeSlots;
|
||||
UpdateField<uint8, -1, 3> SourceType;
|
||||
UpdateField<std::string, -1, 4> SourceValue;
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool ignoreChangesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool ignoreChangesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct DecorPetInfo : public IsUpdateFieldStructureTag, public HasChangesMask<6>
|
||||
{
|
||||
UpdateField<ObjectGuid, -1, 0> BattlePetGUID;
|
||||
UpdateField<ObjectGuid, -1, 1> SpawnGroup;
|
||||
UpdateField<ObjectGuid, -1, 2> SpawnedPet;
|
||||
UpdateField<uint32, -1, 3> CreatureID;
|
||||
UpdateField<std::string, -1, 4> PetName;
|
||||
UpdateField<uint8, -1, 5> PetBehavior;
|
||||
{
|
||||
UpdateField<ObjectGuid, -1, 0> BattlePetGUID;
|
||||
UpdateField<ObjectGuid, -1, 1> SpawnGroup;
|
||||
UpdateField<ObjectGuid, -1, 2> SpawnedPet;
|
||||
UpdateField<uint32, -1, 3> CreatureID;
|
||||
UpdateField<std::string, -1, 4> PetName;
|
||||
UpdateField<uint8, -1, 5> PetBehavior;
|
||||
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(bool ignoreChangesMask, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(bool ignoreChangesMask, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct HousingDecorData : public IsUpdateFieldStructureTag, public HasChangesMask<7>
|
||||
{
|
||||
UpdateField<ObjectGuid, 0, 1> DecorGUID;
|
||||
UpdateField<ObjectGuid, 0, 2> AttachParentGUID;
|
||||
UpdateField<uint8, 0, 3> Flags;
|
||||
OptionalUpdateField<UF::DecorStoragePersistedData, 0, 4> PersistedData;
|
||||
OptionalUpdateField<UF::DecorPetInfo, 0, 5> PetInfo;
|
||||
UpdateField<ObjectGuid, 0, 6> TargetGameObjectGUID;
|
||||
{
|
||||
UpdateField<ObjectGuid, 0, 1> DecorGUID;
|
||||
UpdateField<ObjectGuid, 0, 2> AttachParentGUID;
|
||||
UpdateField<uint8, 0, 3> Flags;
|
||||
OptionalUpdateField<UF::DecorStoragePersistedData, 0, 4> PersistedData;
|
||||
OptionalUpdateField<UF::DecorPetInfo, 0, 5> PetInfo;
|
||||
UpdateField<ObjectGuid, 0, 6> TargetGameObjectGUID;
|
||||
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Object const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Object const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct HousingDoorData : public IsUpdateFieldStructureTag, public HasChangesMask<5>
|
||||
{
|
||||
UpdateField<int32, 0, 1> RoomComponentID;
|
||||
UpdateField<TaggedPosition<Position::XYZ>, 0, 2> RoomComponentOffset;
|
||||
UpdateField<uint8, 0, 3> RoomComponentType;
|
||||
UpdateField<ObjectGuid, 0, 4> AttachedRoomGUID;
|
||||
{
|
||||
UpdateField<int32, 0, 1> RoomComponentID;
|
||||
UpdateField<TaggedPosition<Position::XYZ>, 0, 2> RoomComponentOffset;
|
||||
UpdateField<uint8, 0, 3> RoomComponentType;
|
||||
UpdateField<ObjectGuid, 0, 4> AttachedRoomGUID;
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool ignoreChangesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool ignoreChangesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
// 12.0.7 dropped the trailing int32 FloorIndex from this fragment; the floor is server-side only, do not re-add it.
|
||||
struct HousingRoomData : public IsUpdateFieldStructureTag, public HasChangesMask<6>
|
||||
{
|
||||
DynamicUpdateField<ObjectGuid, 0, 1> MeshObjects;
|
||||
DynamicUpdateField<UF::HousingDoorData, 0, 2> Doors;
|
||||
UpdateField<ObjectGuid, 0, 3> HouseGUID;
|
||||
UpdateField<int32, 0, 4> HouseRoomID;
|
||||
UpdateField<int32, 0, 5> Flags;
|
||||
{
|
||||
DynamicUpdateField<ObjectGuid, 0, 1> MeshObjects;
|
||||
DynamicUpdateField<UF::HousingDoorData, 0, 2> Doors;
|
||||
UpdateField<ObjectGuid, 0, 3> HouseGUID;
|
||||
UpdateField<int32, 0, 4> HouseRoomID;
|
||||
UpdateField<int32, 0, 5> Flags;
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct HousingRoomComponentMeshData : public IsUpdateFieldStructureTag, public HasChangesMask<10>
|
||||
{
|
||||
UpdateField<ObjectGuid, 0, 1> RoomGUID;
|
||||
UpdateField<int32, 0, 2> RoomComponentOptionID;
|
||||
UpdateField<int32, 0, 3> RoomComponentID;
|
||||
UpdateField<uint8, 0, 4> Field_20;
|
||||
UpdateField<uint8, 0, 5> RoomComponentType;
|
||||
UpdateField<int32, 0, 6> Field_24;
|
||||
UpdateField<int32, 0, 7> HouseThemeID;
|
||||
UpdateField<int32, 0, 8> RoomComponentTextureID;
|
||||
UpdateField<int32, 0, 9> RoomComponentTypeParam;
|
||||
{
|
||||
UpdateField<ObjectGuid, 0, 1> RoomGUID;
|
||||
UpdateField<int32, 0, 2> RoomComponentOptionID;
|
||||
UpdateField<int32, 0, 3> RoomComponentID;
|
||||
UpdateField<uint8, 0, 4> Field_20;
|
||||
UpdateField<uint8, 0, 5> RoomComponentType;
|
||||
UpdateField<int32, 0, 6> Field_24;
|
||||
UpdateField<int32, 0, 7> HouseThemeID;
|
||||
UpdateField<int32, 0, 8> RoomComponentTextureID;
|
||||
UpdateField<int32, 0, 9> RoomComponentTypeParam;
|
||||
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Object const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Object const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct HousingPlayerHouseData : public IsUpdateFieldStructureTag, public HasChangesMask<11>
|
||||
{
|
||||
UpdateField<ObjectGuid, 0, 1> BnetAccount;
|
||||
UpdateField<ObjectGuid, 0, 2> CosmeticOwner;
|
||||
UpdateField<int32, 0, 3> PlotIndex;
|
||||
UpdateField<uint32, 0, 4> Level;
|
||||
UpdateField<uint64, 0, 5> Favor;
|
||||
UpdateField<uint32, 0, 6> InteriorDecorPlacementBudget;
|
||||
UpdateField<uint32, 0, 7> ExteriorDecorPlacementBudget;
|
||||
UpdateField<uint32, 0, 8> ExteriorFixtureBudget;
|
||||
UpdateField<uint32, 0, 9> RoomPlacementBudget;
|
||||
UpdateField<ObjectGuid, 0, 10> EntityGUID;
|
||||
{
|
||||
UpdateField<ObjectGuid, 0, 1> BnetAccount;
|
||||
UpdateField<ObjectGuid, 0, 2> CosmeticOwner;
|
||||
UpdateField<int32, 0, 3> PlotIndex;
|
||||
UpdateField<uint32, 0, 4> Level;
|
||||
UpdateField<uint64, 0, 5> Favor;
|
||||
UpdateField<uint32, 0, 6> InteriorDecorPlacementBudget;
|
||||
UpdateField<uint32, 0, 7> ExteriorDecorPlacementBudget;
|
||||
UpdateField<uint32, 0, 8> ExteriorFixtureBudget;
|
||||
UpdateField<uint32, 0, 9> RoomPlacementBudget;
|
||||
UpdateField<ObjectGuid, 0, 10> EntityGUID;
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct HousingCornerstoneData : public IsUpdateFieldStructureTag, public HasChangesMask<3>
|
||||
{
|
||||
UpdateField<uint64, 0, 1> Cost;
|
||||
UpdateField<int32, 0, 2> PlotIndex;
|
||||
{
|
||||
UpdateField<uint64, 0, 1> Cost;
|
||||
UpdateField<int32, 0, 2> PlotIndex;
|
||||
|
||||
using OwnerObject = GameObject;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, GameObject const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, GameObject const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, GameObject const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = GameObject;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, GameObject const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, GameObject const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, GameObject const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct PlayerHouseInfo : public IsUpdateFieldStructureTag
|
||||
{
|
||||
@@ -2050,30 +2051,30 @@ namespace UF
|
||||
};
|
||||
|
||||
struct HousingOwner : public IsUpdateFieldStructureTag
|
||||
{
|
||||
ObjectGuid BnetAccountGUID;
|
||||
ObjectGuid PlayerGUID;
|
||||
{
|
||||
ObjectGuid BnetAccountGUID;
|
||||
ObjectGuid PlayerGUID;
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(HousingOwner const& right) const;
|
||||
bool operator!=(HousingOwner const& right) const { return !(*this == right); }
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(HousingOwner const& right) const;
|
||||
bool operator!=(HousingOwner const& right) const { return !(*this == right); }
|
||||
};
|
||||
|
||||
struct NeighborhoodMirrorData : public IsUpdateFieldStructureTag, public HasChangesMask<5>
|
||||
{
|
||||
DynamicUpdateField<UF::PlayerHouseInfo, 0, 1> Houses;
|
||||
DynamicUpdateField<UF::HousingOwner, 0, 2> Managers;
|
||||
UpdateField<std::string, 0, 3> Name;
|
||||
UpdateField<ObjectGuid, 0, 4> OwnerGUID;
|
||||
{
|
||||
DynamicUpdateField<UF::PlayerHouseInfo, 0, 1> Houses;
|
||||
DynamicUpdateField<UF::HousingOwner, 0, 2> Managers;
|
||||
UpdateField<std::string, 0, 3> Name;
|
||||
UpdateField<ObjectGuid, 0, 4> OwnerGUID;
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct MirroredMeshObjectData : public IsUpdateFieldStructureTag, public HasChangesMask<6>
|
||||
{
|
||||
@@ -2118,96 +2119,96 @@ namespace UF
|
||||
};
|
||||
|
||||
struct NeighborhoodCharterSignature : public IsUpdateFieldStructureTag
|
||||
{
|
||||
ObjectGuid Guid;
|
||||
{
|
||||
ObjectGuid Guid;
|
||||
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, Player const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(NeighborhoodCharterSignature const& right) const;
|
||||
bool operator!=(NeighborhoodCharterSignature const& right) const { return !(*this == right); }
|
||||
};
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, Player const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(NeighborhoodCharterSignature const& right) const;
|
||||
bool operator!=(NeighborhoodCharterSignature const& right) const { return !(*this == right); }
|
||||
};
|
||||
|
||||
struct NeighborhoodCharter : public IsUpdateFieldStructureTag
|
||||
{
|
||||
std::vector<UF::NeighborhoodCharterSignature> Signatures;
|
||||
int32 Field_0 = 0;
|
||||
int32 Field_4 = 0;
|
||||
std::string Name;
|
||||
{
|
||||
std::vector<UF::NeighborhoodCharterSignature> Signatures;
|
||||
int32 Field_0 = 0;
|
||||
int32 Field_4 = 0;
|
||||
std::string Name;
|
||||
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, Player const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(NeighborhoodCharter const& right) const;
|
||||
bool operator!=(NeighborhoodCharter const& right) const { return !(*this == right); }
|
||||
};
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, Player const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(NeighborhoodCharter const& right) const;
|
||||
bool operator!=(NeighborhoodCharter const& right) const { return !(*this == right); }
|
||||
};
|
||||
|
||||
struct NeighborhoodOwnershipTransfer : public IsUpdateFieldStructureTag
|
||||
{
|
||||
ObjectGuid NeighborhoodGUID;
|
||||
ObjectGuid CurrentOwnerGUID;
|
||||
std::string NeighborhoodName;
|
||||
{
|
||||
ObjectGuid NeighborhoodGUID;
|
||||
ObjectGuid CurrentOwnerGUID;
|
||||
std::string NeighborhoodName;
|
||||
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, Player const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(NeighborhoodOwnershipTransfer const& right) const;
|
||||
bool operator!=(NeighborhoodOwnershipTransfer const& right) const { return !(*this == right); }
|
||||
};
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(bool /*ignoreChangesMask*/, ByteBuffer& data, Player const* receiver, Player const* owner) const { WriteCreate(data, receiver, owner); }
|
||||
bool operator==(NeighborhoodOwnershipTransfer const& right) const;
|
||||
bool operator!=(NeighborhoodOwnershipTransfer const& right) const { return !(*this == right); }
|
||||
};
|
||||
|
||||
struct PlayerHouseInfoComponentData : public IsUpdateFieldStructureTag, public HasChangesMask<11>
|
||||
{
|
||||
DynamicUpdateField<ObjectGuid, 0, 1> Field_8;
|
||||
DynamicUpdateField<UF::PlayerMirrorHouse, 0, 2> Houses;
|
||||
DynamicUpdateField<ObjectGuid, 0, 3> Field_88;
|
||||
DynamicUpdateField<ObjectGuid, 0, 4> Field_C0;
|
||||
DynamicUpdateField<ObjectGuid, 0, 5> Field_F8;
|
||||
DynamicUpdateField<ObjectGuid, 0, 6> Field_130;
|
||||
UpdateField<UF::NeighborhoodCharter, 0, 7> Charter;
|
||||
UpdateField<uint8, 0, 8> EditorMode;
|
||||
UpdateField<UF::NeighborhoodOwnershipTransfer, 0, 9> NeighborhoodOwnershipTransfer;
|
||||
UpdateField<ObjectGuid, 0, 10> CurrentHouse;
|
||||
{
|
||||
DynamicUpdateField<ObjectGuid, 0, 1> Field_8;
|
||||
DynamicUpdateField<UF::PlayerMirrorHouse, 0, 2> Houses;
|
||||
DynamicUpdateField<ObjectGuid, 0, 3> Field_88;
|
||||
DynamicUpdateField<ObjectGuid, 0, 4> Field_C0;
|
||||
DynamicUpdateField<ObjectGuid, 0, 5> Field_F8;
|
||||
DynamicUpdateField<ObjectGuid, 0, 6> Field_130;
|
||||
UpdateField<UF::NeighborhoodCharter, 0, 7> Charter;
|
||||
UpdateField<uint8, 0, 8> EditorMode;
|
||||
UpdateField<UF::NeighborhoodOwnershipTransfer, 0, 9> NeighborhoodOwnershipTransfer;
|
||||
UpdateField<ObjectGuid, 0, 10> CurrentHouse;
|
||||
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Player const* owner, bool ignoreNestedChangesMask) const;
|
||||
static void AppendAllowedFieldsMaskForFlag(Mask& allowedMaskForTarget, EnumFlag<UpdateFieldFlag> fieldVisibilityFlags);
|
||||
static void FilterDisallowedFieldsMaskForFlag(Mask& changesMask, EnumFlag<UpdateFieldFlag> fieldVisibilityFlags);
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = Player;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Player const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Player const* owner, bool ignoreNestedChangesMask) const;
|
||||
static void AppendAllowedFieldsMaskForFlag(Mask& allowedMaskForTarget, EnumFlag<UpdateFieldFlag> fieldVisibilityFlags);
|
||||
static void FilterDisallowedFieldsMaskForFlag(Mask& changesMask, EnumFlag<UpdateFieldFlag> fieldVisibilityFlags);
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct HousingStorageData : public IsUpdateFieldStructureTag, public HasChangesMask<3>
|
||||
{
|
||||
MapUpdateField<ObjectGuid, UF::DecorStoragePersistedData, 0, 1> Decor;
|
||||
UpdateField<uint32, 0, 2> DecorMaxOwnedCount;
|
||||
{
|
||||
MapUpdateField<ObjectGuid, UF::DecorStoragePersistedData, 0, 1> Decor;
|
||||
UpdateField<uint32, 0, 2> DecorMaxOwnedCount;
|
||||
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = BaseEntity;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, BaseEntity const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, BaseEntity const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct HousingFixtureData : public IsUpdateFieldStructureTag, public HasChangesMask<11>
|
||||
{
|
||||
UpdateField<int32, 0, 1> ExteriorComponentID;
|
||||
UpdateField<int32, 0, 2> HouseExteriorWmoDataID;
|
||||
UpdateField<int32, 0, 3> ExteriorComponentHookID;
|
||||
UpdateField<ObjectGuid, 0, 4> HouseGUID;
|
||||
UpdateField<ObjectGuid, 0, 5> AttachParentGUID;
|
||||
UpdateField<ObjectGuid, 0, 6> Guid;
|
||||
UpdateField<ObjectGuid, 0, 7> GameObjectGUID;
|
||||
UpdateField<uint8, 0, 8> ExteriorComponentType;
|
||||
UpdateField<uint8, 0, 9> Field_59;
|
||||
UpdateField<uint8, 0, 10> Size;
|
||||
{
|
||||
UpdateField<int32, 0, 1> ExteriorComponentID;
|
||||
UpdateField<int32, 0, 2> HouseExteriorWmoDataID;
|
||||
UpdateField<int32, 0, 3> ExteriorComponentHookID;
|
||||
UpdateField<ObjectGuid, 0, 4> HouseGUID;
|
||||
UpdateField<ObjectGuid, 0, 5> AttachParentGUID;
|
||||
UpdateField<ObjectGuid, 0, 6> Guid;
|
||||
UpdateField<ObjectGuid, 0, 7> GameObjectGUID;
|
||||
UpdateField<uint8, 0, 8> ExteriorComponentType;
|
||||
UpdateField<uint8, 0, 9> Field_59;
|
||||
UpdateField<uint8, 0, 10> Size;
|
||||
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Object const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
using OwnerObject = Object;
|
||||
void WriteCreate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(EnumFlag<UpdateFieldFlag> fieldVisibilityFlags, ByteBuffer& data, Player const* receiver, Object const* owner) const;
|
||||
void WriteUpdate(Mask const& changesMask, ByteBuffer& data, Player const* receiver, Object const* owner, bool ignoreNestedChangesMask) const;
|
||||
void ClearChangesMask();
|
||||
};
|
||||
|
||||
struct PlayerInitiativeInfo : public IsUpdateFieldStructureTag, public HasChangesMask<7>
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -99,6 +99,7 @@ enum GarrisonType : int32;
|
||||
//class Housing;
|
||||
class Group;
|
||||
class Guild;
|
||||
class Housing;
|
||||
class Item;
|
||||
class LootRoll;
|
||||
class LootStore;
|
||||
@@ -3105,26 +3106,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
// WoD garrison and silently no-ops for order hall / covenant followers.
|
||||
Garrison* GetGarrisonWithFollower(uint64 followerDbID) const;
|
||||
|
||||
// void CreateHousing(ObjectGuid neighborhoodGuid, uint8 plotIndex);
|
||||
// void DeleteHousing(ObjectGuid neighborhoodGuid);
|
||||
// Housing* GetHousing() const;
|
||||
// Housing* GetHousingForNeighborhood(ObjectGuid neighborhoodGuid) const;
|
||||
// std::vector<Housing const*> GetAllHousings() const;
|
||||
// void SetHousingEditorModeUpdateField(uint8 mode);
|
||||
// void UpdateHousingMapId(ObjectGuid houseGuid, int32 mapId);
|
||||
// void UpdateInitiativeFavor(uint32 favor);
|
||||
|
||||
// 12.0.5 plot-entry mechanism: writes PlayerHouseInfoComponentData.CurrentHouse to
|
||||
// the given house GUID (or ObjectGuid::Empty on plot-leave). Client tracks plot
|
||||
// occupancy by observing this field's UPDATE_OBJECT changes ? it replaces the
|
||||
// removed SMSG_NEIGHBORHOOD_PLAYER_ENTER_PLOT / LEAVE_PLOT opcodes and the
|
||||
// per-AT FHousingPlotAreaTrigger_C fragment that were deleted in 12.0.5.
|
||||
// void SetCurrentHouse(ObjectGuid houseGuid);
|
||||
|
||||
// Transient ? set by the door GO script before a visit teleport so
|
||||
// MapManager routes the visitor to the owner's HouseInteriorMap
|
||||
// instance (instanceId = owner's GUID counter). Empty means "enter my
|
||||
// own interior" (the default case). Cleared by MapManager once read.
|
||||
// House-visit teleport target: set by the door GO script, consumed by MapManager. Empty = own interior.
|
||||
void SetHouseVisitTarget(ObjectGuid ownerGuid) { _houseVisitTargetOwner = ownerGuid; }
|
||||
ObjectGuid GetHouseVisitTarget() const { return _houseVisitTargetOwner; }
|
||||
void ClearHouseVisitTarget() { _houseVisitTargetOwner = ObjectGuid::Empty; }
|
||||
@@ -3244,6 +3226,26 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
void SendCovenantCallingsUpdate();
|
||||
// A calling quest was turned in - free its slot so the next daily reset issues a replacement.
|
||||
void OnCovenantCallingCompleted(uint32 questId);
|
||||
void CreateHousing(ObjectGuid neighborhoodGuid, uint8 plotIndex);
|
||||
void DeleteHousing(ObjectGuid neighborhoodGuid);
|
||||
Housing* GetHousing() const;
|
||||
Housing* GetHousingForNeighborhood(ObjectGuid neighborhoodGuid) const;
|
||||
Housing* GetHousingByOwner(ObjectGuid ownerGuid) const;
|
||||
Housing* GetHousingByHouseGuid(ObjectGuid houseGuid) const;
|
||||
std::vector<Housing const*> GetAllHousings() const;
|
||||
void SetHousingEditorModeUpdateField(uint8 mode);
|
||||
void UpdateHousingMapId(ObjectGuid houseGuid, int32 mapId);
|
||||
void UpdateInitiativeFavor(uint32 favor);
|
||||
void UpdateHousingLevelFavor(ObjectGuid houseGuid, uint32 level, uint32 favor);
|
||||
|
||||
// Writes PlayerHouseInfoComponentData.CurrentHouse (empty on plot-leave); the client tracks plot occupancy from it.
|
||||
void SetCurrentHouse(ObjectGuid houseGuid);
|
||||
|
||||
// The housing tutorial runs while Housing.TutorialsEnabled is set and the character has
|
||||
// not rewarded the whole HOUSING_TUTORIAL_QUEST_CHAIN.
|
||||
bool HousingTutorialChainComplete() const;
|
||||
// Updates housingTutorialsEnabled in GLOBAL_CONFIG_CACHE and re-sends the account data timestamps.
|
||||
void UpdateHousingTutorialCVars();
|
||||
|
||||
bool IsAdvancedCombatLoggingEnabled() const { return _advancedCombatLoggingEnabled; }
|
||||
void SetAdvancedCombatLogging(bool enabled) { _advancedCombatLoggingEnabled = enabled; }
|
||||
@@ -3342,6 +3344,14 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
void AddIllusionBlock(uint32 blockValue) { AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TransmogIllusions)) = blockValue; }
|
||||
void AddIllusionFlag(uint32 slot, uint32 flag) { SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TransmogIllusions, slot), flag); }
|
||||
|
||||
// Account-wide HouseRoom collection (ActivePlayerData::HouseRooms, one bit per HouseRoom ID).
|
||||
bool HasHouseRoom(uint32 houseRoomId) const;
|
||||
void LearnHouseRoom(uint32 houseRoomId);
|
||||
|
||||
// Account-wide house type collection (ActivePlayerData::HouseTypes, one bit per HouseExteriorWmoData ID).
|
||||
bool HasHouseType(uint32 houseExteriorWmoDataId) const;
|
||||
void LearnHouseType(uint32 houseExteriorWmoDataId);
|
||||
|
||||
void AddWarbandScenesBlock(uint32 blockValue) { AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::WarbandScenes)) = blockValue; }
|
||||
void AddWarbandScenesFlag(uint32 slot, uint32 flag) { SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::WarbandScenes, slot), flag); }
|
||||
|
||||
@@ -3594,6 +3604,13 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
void _LoadCurrency(PreparedQueryResult result);
|
||||
void _LoadCUFProfiles(PreparedQueryResult result);
|
||||
void _LoadPlayerData(PreparedQueryResult elementsResult, PreparedQueryResult flagsResult);
|
||||
void _LoadHouseRooms();
|
||||
void _LoadAccountHousings();
|
||||
void SetHouseRoomBit(uint32 houseRoomId);
|
||||
void _LoadHouseTypes();
|
||||
void SetHouseTypeBit(uint32 houseExteriorWmoDataId);
|
||||
std::vector<uint32> m_houseRoomCollection; // for the login SMSG_ACCOUNT_ROOM_COLLECTION_UPDATE
|
||||
bool m_houseRoomCollectionSent = false;
|
||||
void _LoadCharacterBankTabSettings(PreparedQueryResult result);
|
||||
void _LoadAccountBankTabSettings(PreparedQueryResult result);
|
||||
void _LoadAccountBankItems(PreparedQueryResult result, uint32 timeDiff);
|
||||
@@ -3925,7 +3942,6 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
uint32 _activeCheats;
|
||||
|
||||
std::unordered_map<int32 /*GarrisonType*/, std::unique_ptr<Garrison>> _garrisons;
|
||||
// std::vector<std::unique_ptr<Housing>> _housings;
|
||||
|
||||
uint8 _warbandMaxLevelCharCount = 0;
|
||||
|
||||
@@ -3939,7 +3955,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
};
|
||||
Optional<PendingArchaeologyFind> _pendingArchaeologyFind;
|
||||
std::unordered_map<uint32 /*researchSiteId*/, std::pair<float, float>> _researchSiteFindLocations;
|
||||
//WowCommunity
|
||||
std::vector<std::unique_ptr<Housing>> _housings;
|
||||
bool _advancedCombatLoggingEnabled;
|
||||
|
||||
// variables to save health and mana before duel and restore them after duel
|
||||
|
||||
@@ -32,9 +32,8 @@ class Player;
|
||||
class AreaTrigger;
|
||||
class SceneObject;
|
||||
class Conversation;
|
||||
//WowCommunity
|
||||
namespace Housing { class DynamicWorldEntity; class RoomWorldEntity; }
|
||||
//WowCommunity
|
||||
class HousingRoomEntity;
|
||||
class MeshObject;
|
||||
|
||||
#define MAX_NUMBER_OF_CELLS 8
|
||||
|
||||
@@ -73,8 +72,8 @@ typedef GridRefManager<Player> PlayerMapType;
|
||||
typedef GridRefManager<AreaTrigger> AreaTriggerMapType;
|
||||
typedef GridRefManager<SceneObject> SceneObjectMapType;
|
||||
typedef GridRefManager<Conversation> ConversationMapType;
|
||||
typedef GridRefManager<Housing::DynamicWorldEntity> HousingDynamicEntityMapType;
|
||||
typedef GridRefManager<Housing::RoomWorldEntity> HousingRoomEntityMapType;
|
||||
typedef GridRefManager<MeshObject> MeshObjectMapType;
|
||||
typedef GridRefManager<HousingRoomEntity> HousingRoomEntityMapType;
|
||||
|
||||
enum GridMapTypeMask
|
||||
{
|
||||
@@ -86,15 +85,16 @@ enum GridMapTypeMask
|
||||
GRID_MAP_TYPE_MASK_AREATRIGGER = 0x20,
|
||||
GRID_MAP_TYPE_MASK_SCENEOBJECT = 0x40,
|
||||
GRID_MAP_TYPE_MASK_CONVERSATION = 0x80,
|
||||
GRID_MAP_TYPE_MASK_HOUSING_ENTITY = 0x100,
|
||||
GRID_MAP_TYPE_MASK_ALL = 0x1FF
|
||||
GRID_MAP_TYPE_MASK_MESHOBJECT = 0x100,
|
||||
GRID_MAP_TYPE_MASK_HOUSINGROMENTITY = 0x200,
|
||||
GRID_MAP_TYPE_MASK_ALL = 0x3FF
|
||||
};
|
||||
|
||||
// Creature used instead pet to simplify *::Visit templates (not required duplicate code for Creature->Pet case)
|
||||
extern template struct TypeListContainer<GridRefManagerContainer, GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation, Housing::DynamicWorldEntity, Housing::RoomWorldEntity>;
|
||||
extern template struct TypeListContainer<GridRefManagerContainer, GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation, HousingRoomEntity, MeshObject>;
|
||||
extern template struct TypeListContainer<GridRefManagerContainer, Player, Creature/*pets*/, Corpse/*resurrectable*/, DynamicObject/*farsight target*/>;
|
||||
|
||||
typedef TypeListContainer<GridRefManagerContainer, GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation> GridTypeMapContainer;
|
||||
typedef TypeListContainer<GridRefManagerContainer, GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation, HousingRoomEntity, MeshObject> GridTypeMapContainer;
|
||||
typedef TypeListContainer<GridRefManagerContainer, Player, Creature/*pets*/, Corpse/*resurrectable*/, DynamicObject/*farsight target*/> WorldTypeMapContainer;
|
||||
|
||||
extern template class Grid<WorldTypeMapContainer, GridTypeMapContainer>;
|
||||
|
||||
@@ -32,5 +32,10 @@ GridInfo::GridInfo(time_t expiry, bool unload /*= true */) : i_timer(expiry), vi
|
||||
template class TC_GAME_API Grid<WorldTypeMapContainer, GridTypeMapContainer>;
|
||||
template class TC_GAME_API NGrid<MAX_NUMBER_OF_CELLS, WorldTypeMapContainer, GridTypeMapContainer>;
|
||||
|
||||
template struct TC_GAME_API TypeListContainer<GridRefManagerContainer, GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation, Housing::DynamicWorldEntity>;
|
||||
template struct TC_GAME_API TypeListContainer<GridRefManagerContainer, Player, Creature/*pets*/, Corpse/*resurrectable*/, DynamicObject/*farsight target*/>;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template struct TC_GAME_API TypeListContainer<GridRefManagerContainer, GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation, HousingRoomEntity, MeshObject>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "HousingNeighborhoodMirrorEntity.h"
|
||||
#include "HousingPlayerHouseEntity.h"
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
@@ -16,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "GridNotifiers.h"
|
||||
#include "Account.h"
|
||||
#include "CellImpl.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
@@ -24,7 +27,6 @@
|
||||
#include "UpdateData.h"
|
||||
#include "WorldPacket.h"
|
||||
//WowCommunity
|
||||
#include "HousingDynamicEntity.h"
|
||||
#include "Creature.h"
|
||||
#include "ScriptHelpers.h"
|
||||
//WowCommunity
|
||||
@@ -49,29 +51,52 @@ void VisibleNotifier::SendToSelf()
|
||||
{
|
||||
switch (passenger->GetTypeId())
|
||||
{
|
||||
case TYPEID_GAMEOBJECT:
|
||||
i_player.UpdateVisibilityOf(passenger->ToGameObject(), i_data, i_visibleNow);
|
||||
break;
|
||||
case TYPEID_PLAYER:
|
||||
i_player.UpdateVisibilityOf(passenger->ToPlayer(), i_data, i_visibleNow);
|
||||
if (!passenger->isNeedNotify(NOTIFY_VISIBILITY_CHANGED))
|
||||
passenger->ToPlayer()->UpdateVisibilityOf(&i_player);
|
||||
break;
|
||||
case TYPEID_UNIT:
|
||||
i_player.UpdateVisibilityOf(passenger->ToCreature(), i_data, i_visibleNow);
|
||||
break;
|
||||
case TYPEID_DYNAMICOBJECT:
|
||||
i_player.UpdateVisibilityOf(passenger->ToDynObject(), i_data, i_visibleNow);
|
||||
break;
|
||||
case TYPEID_AREATRIGGER:
|
||||
i_player.UpdateVisibilityOf(passenger->ToAreaTrigger(), i_data, i_visibleNow);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case TYPEID_GAMEOBJECT:
|
||||
i_player.UpdateVisibilityOf(passenger->ToGameObject(), i_data, i_visibleNow);
|
||||
break;
|
||||
case TYPEID_PLAYER:
|
||||
i_player.UpdateVisibilityOf(passenger->ToPlayer(), i_data, i_visibleNow);
|
||||
if (!passenger->isNeedNotify(NOTIFY_VISIBILITY_CHANGED))
|
||||
passenger->ToPlayer()->UpdateVisibilityOf(&i_player);
|
||||
break;
|
||||
case TYPEID_UNIT:
|
||||
i_player.UpdateVisibilityOf(passenger->ToCreature(), i_data, i_visibleNow);
|
||||
break;
|
||||
case TYPEID_DYNAMICOBJECT:
|
||||
i_player.UpdateVisibilityOf(passenger->ToDynObject(), i_data, i_visibleNow);
|
||||
break;
|
||||
case TYPEID_AREATRIGGER:
|
||||
i_player.UpdateVisibilityOf(passenger->ToAreaTrigger(), i_data, i_visibleNow);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Map* map = i_player.GetMap())
|
||||
{
|
||||
for (const ObjectGuid& obj : map->GetInfiniteGameObjects()) {
|
||||
if (vis_guids.find(obj) != vis_guids.end())
|
||||
vis_guids.erase(obj);
|
||||
|
||||
if (GameObject* gob = i_player.GetMap()->GetGameObject(obj))
|
||||
i_player.UpdateVisibilityOf(gob, i_data, i_visibleNow);
|
||||
}
|
||||
}
|
||||
|
||||
// The session's own entities (Battle.net account, player house, neighborhood mirror) live outside the grid, so
|
||||
// no grid visit ever marks them visible - they are not out of range. Retail never destroys them; doing so drops
|
||||
// the client's house state while it is still in use (the mirror went out of range on every visibility pass).
|
||||
if (WorldSession* session = i_player.GetSession())
|
||||
{
|
||||
vis_guids.erase(session->GetBattlenetAccount().GetGUID());
|
||||
if (session->HasHousingPlayerHouseEntity())
|
||||
vis_guids.erase(session->GetHousingPlayerHouseEntity().GetGUID());
|
||||
if (session->HasHousingNeighborhoodMirrorEntity())
|
||||
vis_guids.erase(session->GetHousingNeighborhoodMirrorEntity().GetGUID());
|
||||
}
|
||||
|
||||
for (ObjectGuid const& outOfRangeGuid : vis_guids)
|
||||
{
|
||||
@@ -85,7 +110,6 @@ void VisibleNotifier::SendToSelf()
|
||||
(i_player.InBattleground() && ScriptHelpers::GetBotTeam(creature) == i_player.GetTeam())))
|
||||
ScriptHelpers::SendForceClearFakeParty(&i_player);
|
||||
//WowCommunity
|
||||
|
||||
i_player.m_clientGUIDs.erase(outOfRangeGuid);
|
||||
i_data.AddOutOfRangeGUID(outOfRangeGuid);
|
||||
|
||||
@@ -341,5 +365,11 @@ template void ObjectUpdater::Visit<DynamicObject>(DynamicObjectMapType&);
|
||||
template void ObjectUpdater::Visit<AreaTrigger>(AreaTriggerMapType&);
|
||||
template void ObjectUpdater::Visit<SceneObject>(SceneObjectMapType&);
|
||||
template void ObjectUpdater::Visit<Conversation>(ConversationMapType&);
|
||||
template void ObjectUpdater::Visit<Housing::DynamicWorldEntity>(HousingDynamicEntityMapType&);
|
||||
template void ObjectUpdater::Visit<Housing::RoomWorldEntity>(HousingRoomEntityMapType&);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template void ObjectUpdater::Visit<MeshObject>(MeshObjectMapType &);
|
||||
|
||||
template void ObjectUpdater::Visit<HousingRoomEntity>(HousingRoomEntityMapType &);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "Conversation.h"
|
||||
#include "DynamicObject.h"
|
||||
#include "GameObject.h"
|
||||
#include "HousingRoomEntity.h"
|
||||
#include "MeshObject.h"
|
||||
#include "Player.h"
|
||||
#include "SceneObject.h"
|
||||
#include "Spell.h"
|
||||
@@ -31,7 +33,6 @@
|
||||
#include "UnitAI.h"
|
||||
#include "UpdateData.h"
|
||||
//WowCommunity
|
||||
#include "HousingDynamicEntity.h"
|
||||
//WowCommunity
|
||||
|
||||
namespace Trinity
|
||||
@@ -47,9 +48,8 @@ namespace Trinity
|
||||
template<> struct GridMapTypeMaskForType<AreaTrigger> : std::integral_constant<GridMapTypeMask, GRID_MAP_TYPE_MASK_AREATRIGGER> { };
|
||||
template<> struct GridMapTypeMaskForType<SceneObject> : std::integral_constant<GridMapTypeMask, GRID_MAP_TYPE_MASK_SCENEOBJECT> { };
|
||||
template<> struct GridMapTypeMaskForType<Conversation> : std::integral_constant<GridMapTypeMask, GRID_MAP_TYPE_MASK_CONVERSATION> { };
|
||||
//WowCommunity
|
||||
template<> struct GridMapTypeMaskForType<Housing::DynamicWorldEntity> : std::integral_constant<GridMapTypeMask, GRID_MAP_TYPE_MASK_HOUSING_ENTITY> {};
|
||||
//WowCommunity
|
||||
template<> struct GridMapTypeMaskForType<MeshObject> : std::integral_constant<GridMapTypeMask, GRID_MAP_TYPE_MASK_MESHOBJECT> { };
|
||||
template<> struct GridMapTypeMaskForType<HousingRoomEntity> : std::integral_constant<GridMapTypeMask, GRID_MAP_TYPE_MASK_HOUSINGROMENTITY> { };
|
||||
|
||||
struct TC_GAME_API VisibleNotifier
|
||||
{
|
||||
@@ -149,9 +149,8 @@ namespace Trinity
|
||||
void Visit(AreaTriggerMapType &m) { updateObjects<AreaTrigger>(m); }
|
||||
void Visit(SceneObjectMapType &m) { updateObjects<SceneObject>(m); }
|
||||
void Visit(ConversationMapType &m) { updateObjects<Conversation>(m); }
|
||||
//WowCommunity
|
||||
void Visit(HousingDynamicEntityMapType& m) { updateObjects<Housing::DynamicWorldEntity>(m); }
|
||||
//WowCommunity
|
||||
void Visit(MeshObjectMapType &m) { updateObjects<MeshObject>(m); }
|
||||
void Visit(HousingRoomEntityMapType &m) { updateObjects<HousingRoomEntity>(m); }
|
||||
};
|
||||
|
||||
struct PacketSenderRef
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "HousingRoomEntity.h"
|
||||
#include "MeshObject.h"
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
@@ -31,7 +33,6 @@
|
||||
#include "SceneObject.h"
|
||||
#include <type_traits>
|
||||
//WowCommunity
|
||||
#include "HousingDynamicEntity.h"
|
||||
//WowCommunity
|
||||
|
||||
void ObjectGridEvacuator::Visit(CreatureMapType &m)
|
||||
@@ -192,8 +193,6 @@ template void ObjectGridUnloader::Visit(AreaTriggerMapType&);
|
||||
template void ObjectGridUnloader::Visit(SceneObjectMapType&);
|
||||
template void ObjectGridUnloader::Visit(ConversationMapType&);
|
||||
//WowCommunity
|
||||
template void ObjectGridUnloader::Visit(HousingRoomEntityMapType&);
|
||||
template void ObjectGridUnloader::Visit(HousingDynamicEntityMapType&);
|
||||
//WowCommunity
|
||||
|
||||
template void ObjectGridCleaner::Visit(CreatureMapType &);
|
||||
@@ -204,6 +203,16 @@ template void ObjectGridCleaner::Visit<AreaTrigger>(AreaTriggerMapType &);
|
||||
template void ObjectGridCleaner::Visit<SceneObject>(SceneObjectMapType &);
|
||||
template void ObjectGridCleaner::Visit<Conversation>(ConversationMapType &);
|
||||
//WowCommunity
|
||||
template void ObjectGridCleaner::Visit<Housing::RoomWorldEntity>(HousingRoomEntityMapType&);
|
||||
template void ObjectGridCleaner::Visit<Housing::DynamicWorldEntity>(HousingDynamicEntityMapType&);
|
||||
//WowCommunity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template void ObjectGridUnloader::Visit(MeshObjectMapType&);
|
||||
|
||||
template void ObjectGridUnloader::Visit(HousingRoomEntityMapType&);
|
||||
|
||||
template void ObjectGridCleaner::Visit<MeshObject>(MeshObjectMapType &);
|
||||
|
||||
template void ObjectGridCleaner::Visit<HousingRoomEntity>(HousingRoomEntityMapType &);
|
||||
|
||||
@@ -56,6 +56,16 @@ class TC_GAME_API ObjectGridLoader : public ObjectGridLoaderBase
|
||||
: ObjectGridLoaderBase(grid, map)
|
||||
{ }
|
||||
|
||||
void Visit(GameObjectMapType &m);
|
||||
void Visit(CreatureMapType &m);
|
||||
void Visit(AreaTriggerMapType &m);
|
||||
void Visit(CorpseMapType &) const { }
|
||||
void Visit(DynamicObjectMapType&) const { }
|
||||
void Visit(SceneObjectMapType&) const { }
|
||||
void Visit(ConversationMapType&) const { }
|
||||
void Visit(MeshObjectMapType&) const { }
|
||||
void Visit(HousingRoomEntityMapType&) const { }
|
||||
|
||||
void LoadN();
|
||||
};
|
||||
|
||||
@@ -66,6 +76,16 @@ class TC_GAME_API PersonalPhaseGridLoader : public ObjectGridLoaderBase
|
||||
: ObjectGridLoaderBase(grid, map), _phaseOwner(phaseOwner)
|
||||
{ }
|
||||
|
||||
void Visit(GameObjectMapType &m);
|
||||
void Visit(CreatureMapType &m);
|
||||
void Visit(AreaTriggerMapType&) const { }
|
||||
void Visit(CorpseMapType&) const { }
|
||||
void Visit(DynamicObjectMapType&) const { }
|
||||
void Visit(SceneObjectMapType&) const { }
|
||||
void Visit(ConversationMapType&) const { }
|
||||
void Visit(MeshObjectMapType&) const { }
|
||||
void Visit(HousingRoomEntityMapType&) const { }
|
||||
|
||||
void Load(uint32 phaseId);
|
||||
|
||||
private:
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
#include "Guild.h"
|
||||
#include "HousingSessionLifecycle.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "AchievementMgr.h"
|
||||
#include "Bag.h"
|
||||
@@ -1201,7 +1200,6 @@ void Guild::Disband()
|
||||
{
|
||||
// Call scripts before guild data removed from database
|
||||
sScriptMgr->OnGuildDisband(this);
|
||||
Housing::HandleGuildDisband(GetId(), GetLeaderGUID());
|
||||
|
||||
WorldPackets::Guild::GuildEventDisbanded guildEventDisbanded;
|
||||
guildEventDisbanded.Write();
|
||||
@@ -2990,7 +2988,6 @@ bool Guild::DeleteMember(CharacterDatabaseTransaction trans, ObjectGuid guid, bo
|
||||
}
|
||||
// Call script on remove before member is actually removed from guild (and database)
|
||||
sScriptMgr->OnGuildRemoveMember(this, guid, isDisbanding, isKicked);
|
||||
Housing::HandleGuildMemberRemoved(GetId(), guid);
|
||||
|
||||
m_members.erase(guid);
|
||||
|
||||
|
||||
@@ -196,30 +196,59 @@ void WorldSession::SendFeatureSystemStatusGlueScreen()
|
||||
{ "masterLooterEnabled"sv, "1"sv },
|
||||
// Housing game rules ? ALL values verified against 12.0.1.65940 sniff packet data (Feb 2026)
|
||||
// Service & feature flags (read from config, default true)
|
||||
|
||||
{ "performHousingExpansionCheckClient"sv, "1"sv },
|
||||
|
||||
{ "housingServiceEnabled"sv, "1"sv },
|
||||
{ "housingEnableBuyHouse"sv, "1"sv },
|
||||
{ "housingEnableDeleteHouse"sv, "1"sv },
|
||||
{ "housingEnableMoveHouse"sv, "1"sv },
|
||||
{ "housingEnableCreateCharterNeighborhood"sv, "1"sv },
|
||||
{ "housingEnableCreateGuildNeighborhood"sv, "1"sv },
|
||||
|
||||
{ "housingEnableBuyHouse"sv, sWorld->getBoolConfig(CONFIG_HOUSING_ENABLE_BUY_HOUSE) ? "1"sv : "0"sv },
|
||||
|
||||
{ "housingEnableDeleteHouse"sv, sWorld->getBoolConfig(CONFIG_HOUSING_ENABLE_DELETE_HOUSE) ? "1"sv : "0"sv },
|
||||
|
||||
{ "housingEnableMoveHouse"sv, sWorld->getBoolConfig(CONFIG_HOUSING_ENABLE_MOVE_HOUSE) ? "1"sv : "0"sv },
|
||||
|
||||
{ "housingEnableCreateCharterNeighborhood"sv, sWorld->getBoolConfig(CONFIG_HOUSING_ENABLE_CREATE_CHARTER_NEIGHBORHOOD) ? "1"sv : "0"sv },
|
||||
|
||||
{ "housingEnableCreateGuildNeighborhood"sv, sWorld->getBoolConfig(CONFIG_HOUSING_ENABLE_CREATE_GUILD_NEIGHBORHOOD) ? "1"sv : "0"sv },
|
||||
{ "housingDecorReportScreenshotFacingDotThreshold"sv, "0.500000"sv },
|
||||
{ "housingDecorReportScreenshotDistanceThreshold"sv, "150.000000"sv },
|
||||
{ "housingMarketViewInStoreTelemThrottle"sv, "5"sv },
|
||||
{ "housingMarketViewBundleTelemThrottle"sv, "10"sv },
|
||||
{ "housingMarketAddToCartTelemThrottle"sv, "15"sv },
|
||||
{ "housingMarketClearCartTelemThrottle"sv, "5"sv },
|
||||
{ "housingMarketRemoveFromCartTelemThrottle"sv, "20"sv },
|
||||
{ "housingMarketThrottleTimePeriodMs"sv, "10000"sv },
|
||||
// Market
|
||||
|
||||
{ "housingMarketEnabled"sv, "1"sv },
|
||||
|
||||
{ "housingMarketShopEnabled"sv, "1"sv },
|
||||
|
||||
{ "housingMarketCartFullRemoveEnabled"sv, "1"sv },
|
||||
|
||||
{ "housingBlueprintsEnabled"sv, "1"sv },
|
||||
|
||||
{ "housingBlueprintImportEnabled"sv, "1"sv },
|
||||
|
||||
{ "housingBlueprintExportEnabled"sv, "1"sv },
|
||||
// Neighborhood & exterior
|
||||
|
||||
{ "housingExteriorTypeByNeighborhoodFactionRestriction"sv, "1"sv },
|
||||
|
||||
{ "minNeighborhoodGroupMembers"sv, "3"sv },
|
||||
// Decoration limits
|
||||
|
||||
{ "housingBasicDecor_MaxPreviewLimit"sv, "100"sv },
|
||||
|
||||
{ "housingCatalog_CartSizeLimit"sv, "20"sv },
|
||||
// Decor scale limits
|
||||
|
||||
{ "housingExpertDecor_Scale_Indoor_Min"sv, "0.200000"sv },
|
||||
|
||||
{ "housingExpertDecor_Scale_Indoor_Max"sv, "2.000000"sv },
|
||||
|
||||
{ "housingExpertDecor_Scale_Outdoor_Min"sv, "0.200000"sv },
|
||||
|
||||
{ "housingExpertDecor_Scale_Outdoor_Max"sv, "2.000000"sv },
|
||||
// Advanced flying keyboard factors - retail sends no advFly MirrorVars and uses these
|
||||
// client defaults; pushed explicitly to undo previously synced non-default values.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "Neighborhood.h"
|
||||
#include "NeighborhoodMgr.h"
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
@@ -477,6 +479,26 @@ bool LoginQueryHolder::Initialize()
|
||||
stmt->setUInt64(2, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_WARBAND_MAX_LEVEL_COUNT, stmt);
|
||||
//WowCommunity
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOUSING);
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOUSING, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOUSING_DECOR);
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOUSING_DECOR, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOUSING_ROOMS);
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOUSING_ROOMS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOUSING_FIXTURES);
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOUSING_FIXTURES, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOUSING_CATALOG);
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOUSING_CATALOG, stmt);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1862,6 +1884,10 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder const& holder)
|
||||
|
||||
sScriptMgr->OnPlayerLogin(pCurrChar, firstLogin);
|
||||
|
||||
// The other residents' bulletin boards show this player online (NeighborhoodRosterMemberUpdateInfo.isOnline).
|
||||
for (Neighborhood const* neighborhood : sNeighborhoodMgr.GetNeighborhoodsForPlayer(pCurrChar->GetGUID()))
|
||||
neighborhood->BroadcastMemberStatus(pCurrChar->GetGUID(), true);
|
||||
|
||||
TC_METRIC_EVENT("player_events", "Login", pCurrChar->GetName());
|
||||
}
|
||||
|
||||
@@ -1948,30 +1974,31 @@ void WorldSession::HandleTutorialFlag(WorldPackets::Misc::TutorialSetFlag& packe
|
||||
{
|
||||
switch (packet.Action)
|
||||
{
|
||||
case TUTORIAL_ACTION_UPDATE:
|
||||
{
|
||||
uint8 index = uint8(packet.TutorialBit >> 5);
|
||||
if (index >= MAX_ACCOUNT_TUTORIAL_VALUES)
|
||||
case TUTORIAL_ACTION_UPDATE:
|
||||
{
|
||||
TC_LOG_ERROR("network", "CMSG_TUTORIAL_FLAG received bad TutorialBit {}.", packet.TutorialBit);
|
||||
return;
|
||||
uint8 index = uint8(packet.TutorialBit >> 5);
|
||||
if (index >= MAX_ACCOUNT_TUTORIAL_VALUES)
|
||||
{
|
||||
TC_LOG_ERROR("network", "CMSG_TUTORIAL_FLAG received bad TutorialBit {}.", packet.TutorialBit);
|
||||
return;
|
||||
}
|
||||
uint32 flag = GetTutorialInt(index);
|
||||
flag |= (1 << (packet.TutorialBit & 0x1F));
|
||||
SetTutorialInt(index, flag);
|
||||
break;
|
||||
}
|
||||
uint32 flag = GetTutorialInt(index);
|
||||
flag |= (1 << (packet.TutorialBit & 0x1F));
|
||||
SetTutorialInt(index, flag);
|
||||
break;
|
||||
}
|
||||
case TUTORIAL_ACTION_CLEAR:
|
||||
for (uint8 i = 0; i < MAX_ACCOUNT_TUTORIAL_VALUES; ++i)
|
||||
SetTutorialInt(i, 0xFFFFFFFF);
|
||||
break;
|
||||
case TUTORIAL_ACTION_RESET:
|
||||
for (uint8 i = 0; i < MAX_ACCOUNT_TUTORIAL_VALUES; ++i)
|
||||
SetTutorialInt(i, 0x00000000);
|
||||
break;
|
||||
default:
|
||||
TC_LOG_ERROR("network", "CMSG_TUTORIAL_FLAG received unknown TutorialAction {}.", packet.Action);
|
||||
return;
|
||||
case TUTORIAL_ACTION_CLEAR:
|
||||
for (uint8 i = 0; i < MAX_ACCOUNT_TUTORIAL_VALUES; ++i)
|
||||
SetTutorialInt(i, 0xFFFFFFFF);
|
||||
break;
|
||||
case TUTORIAL_ACTION_RESET:
|
||||
// RESET zeroes the bits ("show tutorials again"); housing mode-unlock bits live in the client CVar (see HOUSING_MODES_UNLOCKED_CVAR), not here
|
||||
for (uint8 i = 0; i < MAX_ACCOUNT_TUTORIAL_VALUES; ++i)
|
||||
SetTutorialInt(i, 0x00000000);
|
||||
break;
|
||||
default:
|
||||
TC_LOG_ERROR("network", "CMSG_TUTORIAL_FLAG received unknown TutorialAction {}.", packet.Action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3634,3 +3661,7 @@ void WorldSession::HandleRafRecruitPresenceSubscribe(WorldPackets::RaF::RafRecru
|
||||
}
|
||||
|
||||
//WowCommunity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "WorldSession.h"
|
||||
#include "HousingSessionLifecycle.h"
|
||||
#include "HousingRoomSession.h"
|
||||
#include "HousingDecorSession.h"
|
||||
#include "HousingDecorQuantitySession.h"
|
||||
#include "HousingDecorRedemptionSession.h"
|
||||
#include "HousingFixtureSession.h"
|
||||
#include "HousingStorageSession.h"
|
||||
#include "HousingTutorialSession.h"
|
||||
|
||||
void WorldSession::HandleHouseExteriorLock(WorldPackets::Housing::HouseExteriorLock& packet)
|
||||
{
|
||||
Housing::HandleHouseExteriorLock(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorSetEditMode(WorldPackets::Housing::HousingDecorSetEditMode& packet)
|
||||
{
|
||||
Housing::HandleDecorSetEditMode(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingFixtureSetEditMode(WorldPackets::Housing::HousingFixtureSetEditMode& packet)
|
||||
{
|
||||
Housing::HandleFixtureSetEditMode(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingFixtureDeleteFixture(WorldPackets::Housing::HousingFixtureDeleteFixture& packet)
|
||||
{
|
||||
Housing::HandleFixtureDelete(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingFixtureCreateFixture(WorldPackets::Housing::HousingFixtureCreateFixture& packet)
|
||||
{
|
||||
Housing::HandleFixtureCreate(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingFixtureSetCoreFixture(WorldPackets::Housing::HousingFixtureSetCoreFixture& packet)
|
||||
{
|
||||
Housing::HandleFixtureSetCore(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingFixtureSetHouseSize(WorldPackets::Housing::HousingFixtureSetHouseSize& packet)
|
||||
{
|
||||
Housing::HandleFixtureSetHouseSize(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingFixtureSetHouseType(WorldPackets::Housing::HousingFixtureSetHouseType& packet)
|
||||
{
|
||||
Housing::HandleFixtureSetHouseType(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetCurrentHouseInfo(WorldPackets::Housing::GetCurrentHouseInfo& packet)
|
||||
{
|
||||
Housing::HandleGetCurrentHouseInfo(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetPlayerPermissions(WorldPackets::Housing::GetPlayerPermissions& packet)
|
||||
{
|
||||
Housing::HandleGetPlayerPermissions(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingHouseStatus(WorldPackets::Housing::HouseStatus& packet)
|
||||
{
|
||||
Housing::HandleHouseStatus(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomSetLayoutEditMode(WorldPackets::Housing::HousingRoomSetLayoutEditMode& packet)
|
||||
{
|
||||
Housing::HandleRoomSetLayoutEditMode(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomAdd(WorldPackets::Housing::HousingRoomAdd& packet)
|
||||
{
|
||||
Housing::HandleRoomAdd(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomRemove(WorldPackets::Housing::HousingRoomRemove& packet)
|
||||
{
|
||||
Housing::HandleRoomRemove(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetHouseFinderInfo(WorldPackets::Housing::HousingGetHouseFinderInfo& packet)
|
||||
{
|
||||
Housing::HandleGetHouseFinderInfo(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetHouseFinderNeighborhood(WorldPackets::Housing::HousingGetHouseFinderNeighborhood& packet)
|
||||
{
|
||||
Housing::HandleGetHouseFinderNeighborhood(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingHouseFinderIgnoreNeighborhood(WorldPackets::Housing::HousingHouseFinderIgnoreNeighborhood& packet)
|
||||
{
|
||||
Housing::HandleHouseFinderIgnoreNeighborhood(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleInvitePlayerToNeighborhood(WorldPackets::Housing::InvitePlayerToNeighborhood& packet)
|
||||
{
|
||||
Housing::HandleInvitePlayerToNeighborhood(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDeleteAllNeighborhoodInvites(WorldPackets::Housing::HousingDeleteAllNeighborhoodInvites& packet)
|
||||
{
|
||||
Housing::HandleDeleteAllNeighborhoodInvites(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodCancelInvitation(WorldPackets::Housing::NeighborhoodCancelInvitation& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodCancelInvitation(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingSvcsGuildCreateNeighborhood(WorldPackets::Housing::HousingSvcsGuildCreateNeighborhood& packet)
|
||||
{
|
||||
Housing::HandleHousingSvcsGuildCreateNeighborhood(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodUpdateName(WorldPackets::Housing::NeighborhoodUpdateName& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodUpdateName(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodSetPublicFlag(WorldPackets::Housing::NeighborhoodSetPublicFlag& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodSetPublicFlag(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodAddSecondaryOwner(WorldPackets::Housing::NeighborhoodAddSecondaryOwner& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodAddSecondaryOwner(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodRemoveSecondaryOwner(WorldPackets::Housing::NeighborhoodRemoveSecondaryOwner& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodRemoveSecondaryOwner(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodGetRoster(WorldPackets::Housing::NeighborhoodGetRoster& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodGetRoster(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodGetInvites(WorldPackets::Housing::NeighborhoodGetInvites& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodGetInvites(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodPlayerGetInvite(WorldPackets::Housing::NeighborhoodPlayerGetInvite& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodPlayerGetInvite(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodPlayerDeclineInvite(WorldPackets::Housing::NeighborhoodPlayerDeclineInvite& packet)
|
||||
{
|
||||
Housing::HandleNeighborhoodPlayerDeclineInvite(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetPlayerHousesInfo(WorldPackets::Housing::GetPlayerHousesInfo& packet)
|
||||
{
|
||||
Housing::HandleGetPlayerHousesInfo(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingReservePlot(WorldPackets::Housing::HousingReservePlot& packet)
|
||||
{
|
||||
Housing::HandleReservePlot(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingPlayerViewHousesByPlayer(WorldPackets::Housing::PlayerViewHousesByPlayer& packet)
|
||||
{
|
||||
Housing::HandlePlayerViewHousesByPlayer(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetPotentialHouseOwners(WorldPackets::Housing::HousingGetPotentialHouseOwners& packet)
|
||||
{
|
||||
Housing::HandleGetPotentialHouseOwners(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRelinquishHouse(WorldPackets::Housing::HousingRelinquishHouse& packet)
|
||||
{
|
||||
Housing::HandleRelinquishHouse(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingTeleportToPlot(WorldPackets::Housing::HousingTeleportToPlot& packet)
|
||||
{
|
||||
Housing::HandleTeleportToPlot(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingUpdateHouseSettings(WorldPackets::Housing::HousingUpdateHouseSettings& packet)
|
||||
{
|
||||
Housing::HandleUpdateHouseSettings(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Housing::NeighborhoodBuyHouse& packet)
|
||||
{
|
||||
Housing::HandleBuyHouse(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodMoveHouse(WorldPackets::Housing::NeighborhoodMoveHouse& packet)
|
||||
{
|
||||
Housing::HandleMoveHouse(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingSvcsStartTutorial(WorldPackets::Housing::HousingSvcsStartTutorial& packet)
|
||||
{
|
||||
Housing::HandleStartTutorial(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomRotate(WorldPackets::Housing::HousingRoomRotate& packet)
|
||||
{
|
||||
Housing::HandleConnectedRoomRotate(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomMoveRoom(WorldPackets::Housing::HousingRoomMoveRoom& packet)
|
||||
{
|
||||
Housing::HandleConnectedRoomMove(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomSetComponentTheme(WorldPackets::Housing::HousingRoomSetComponentTheme& packet)
|
||||
{
|
||||
Housing::HandleRoomSetComponentTheme(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomApplyComponentMaterials(WorldPackets::Housing::HousingRoomApplyComponentMaterials& packet)
|
||||
{
|
||||
Housing::HandleRoomApplyComponentMaterials(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomSetDoorType(WorldPackets::Housing::HousingRoomSetDoorType& packet)
|
||||
{
|
||||
Housing::HandleRoomSetDoorType(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingRoomSetCeilingType(WorldPackets::Housing::HousingRoomSetCeilingType& packet)
|
||||
{
|
||||
Housing::HandleRoomSetCeilingType(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleGetAllLicensedDecorQuantities(WorldPackets::Housing::GetAllLicensedDecorQuantities& packet)
|
||||
{
|
||||
Housing::HandleGetAllLicensedDecorQuantities(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorRequestStorage(WorldPackets::Housing::HousingDecorRequestStorage& packet)
|
||||
{
|
||||
Housing::HandleDecorRequestStorage(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorPlace(WorldPackets::Housing::HousingDecorPlace& packet)
|
||||
{
|
||||
Housing::HandleDecorPlace(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorRemove(WorldPackets::Housing::HousingDecorRemove& packet)
|
||||
{
|
||||
Housing::HandleDecorRemove(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorMove(WorldPackets::Housing::HousingDecorMove& packet)
|
||||
{
|
||||
Housing::HandleDecorMove(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorLock(WorldPackets::Housing::HousingDecorLock& packet)
|
||||
{
|
||||
Housing::HandleDecorLock(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorSetDyeSlots(WorldPackets::Housing::HousingDecorSetDyeSlots& packet)
|
||||
{
|
||||
Housing::HandleDecorSetDyeSlots(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorDeleteFromStorage(WorldPackets::Housing::HousingDecorDeleteFromStorage& packet)
|
||||
{
|
||||
Housing::HandleDecorDeleteFromStorage(this, packet);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingDecorRedeemDeferredDecor(WorldPackets::Housing::HousingDecorRedeemDeferredDecor& packet)
|
||||
{
|
||||
Housing::HandleDecorRedeemDeferred(this, packet);
|
||||
}
|
||||
@@ -1735,3 +1735,7 @@ void WorldSession::HandleRafGenerateRecruitmentLink(WorldPackets::RaF::RafGenera
|
||||
void WorldSession::HandleRequestInstanceEncounterEventSync(WorldPackets::Instance::RequestInstanceEncounterEventSync& /*packet*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
#include "Neighborhood.h"
|
||||
#include "NeighborhoodMgr.h"
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
@@ -39,10 +41,29 @@
|
||||
|
||||
void WorldSession::BuildNameQueryData(ObjectGuid guid, WorldPackets::Query::NameCacheLookupResult& lookupData)
|
||||
{
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
|
||||
lookupData.Player = guid;
|
||||
|
||||
// Housing GUIDs (HighGuid::Housing, type 55) are resolved via HouseData
|
||||
// instead of the player name cache. The client's NameCacheLookupResult
|
||||
// structure includes an Optional<HouseLookupData> field for this purpose.
|
||||
if (guid.GetHigh() == HighGuid::Housing)
|
||||
{
|
||||
Neighborhood const* neighborhood = sNeighborhoodMgr.GetNeighborhood(guid);
|
||||
if (neighborhood && !neighborhood->GetName().empty())
|
||||
{
|
||||
lookupData.Result = RESPONSE_SUCCESS;
|
||||
lookupData.HouseData.emplace();
|
||||
lookupData.HouseData->Guid = guid;
|
||||
// GetName() returns const std::string& with stable lifetime
|
||||
lookupData.HouseData->Name = neighborhood->GetName();
|
||||
}
|
||||
else
|
||||
lookupData.Result = RESPONSE_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
|
||||
lookupData.Data.emplace();
|
||||
if (lookupData.Data->Initialize(guid, player))
|
||||
lookupData.Result = RESPONSE_SUCCESS; // name known
|
||||
@@ -404,3 +425,7 @@ void WorldSession::HandleQueryTreasurePicker(WorldPackets::Query::QueryTreasureP
|
||||
|
||||
SendPacket(treasurePickerResponse.Write());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -658,3 +658,7 @@ void WorldSession::HandleRequestCrowdControlSpell(WorldPackets::Spells::RequestC
|
||||
|
||||
SendPacket(result.Write());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef HouseInteriorMap_h__
|
||||
#define HouseInteriorMap_h__
|
||||
|
||||
#include "Housing.h"
|
||||
#include "Map.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class HousingRoomEntity;
|
||||
class MeshObject;
|
||||
class Player;
|
||||
struct RoomComponentData;
|
||||
struct RoomComponentOptionEntry;
|
||||
|
||||
/// Map instance for a player's house interior (MapID 2783), one per player/account; similar to GarrisonMap.
|
||||
class TC_GAME_API HouseInteriorMap : public Map
|
||||
{
|
||||
public:
|
||||
HouseInteriorMap(uint32 id, time_t expiry, uint32 instanceId, ObjectGuid const& owner);
|
||||
|
||||
void LoadGridObjects(NGridType* grid) override;
|
||||
void InitVisibilityDistance() override;
|
||||
bool AddPlayerToMap(Player* player, bool initPlayer = true) override;
|
||||
void RemovePlayerFromMap(Player* player, bool remove) override;
|
||||
|
||||
ObjectGuid GetOwnerGuid() const { return _owner; }
|
||||
// The house moved to another character of the account (house settings owner change)
|
||||
void SetOwnerGuid(ObjectGuid owner) { _owner = owner; }
|
||||
float GetOriginX() const { return _originX; }
|
||||
float GetOriginY() const { return _originY; }
|
||||
float GetOriginZ() const { return _originZ; }
|
||||
|
||||
/// Get the Housing data for the owner (needed for room/decor state).
|
||||
Housing* GetOwnerHousing();
|
||||
Housing* GetOwnerHousing() const;
|
||||
// The house belongs to the player's account (the owner or another character of the same account).
|
||||
bool IsHouseOwnerAccount(Player const* player) const;
|
||||
|
||||
/// The neighborhood map ID the owner came from (for exit teleport).
|
||||
uint32 GetSourceNeighborhoodMapId() const { return _sourceNeighborhoodMapId; }
|
||||
void SetSourceNeighborhoodMapId(uint32 mapId) { _sourceNeighborhoodMapId = mapId; }
|
||||
|
||||
/// The plot index the owner's house is on (for exit teleport position).
|
||||
uint8 GetSourcePlotIndex() const { return _sourcePlotIndex; }
|
||||
void SetSourcePlotIndex(uint8 plotIndex) { _sourcePlotIndex = plotIndex; }
|
||||
|
||||
/// Spawn all room meshes for the owner's house layout (factionRestriction: NEIGHBORHOOD_FACTION_*).
|
||||
void SpawnRoomMeshObjects(Housing* housing, int32 factionRestriction);
|
||||
|
||||
/// Overload taking raw rooms — for visits to an offline owner's house (data from Neighborhood::PlotInfo.Rooms);
|
||||
/// houseGuid becomes the parent of every spawned HousingRoomEntity.
|
||||
void SpawnRoomMeshObjectsFromList(std::vector<Housing::Room const*> const& rooms, int32 factionRestriction, ObjectGuid houseGuid);
|
||||
|
||||
/// Despawn all room meshes (e.g., when the interior is rebuilt).
|
||||
void DespawnAllRoomMeshObjects();
|
||||
|
||||
/// Update room component textures in-place (material/wallpaper change; no model change).
|
||||
void UpdateRoomComponentTextures(ObjectGuid roomGuid, Housing::Room const& room,
|
||||
std::vector<uint32> const* componentIDs, int32 textureID);
|
||||
|
||||
/// Rebuild some component slots of one room from its stored look (DESTROY + CREATE of the slot's pieces).
|
||||
void RebuildRoomComponents(std::vector<Housing::Room const*> const& rooms, Housing::Room const& room,
|
||||
int32 factionRestriction, std::vector<uint32> const& componentIds);
|
||||
|
||||
/// Despawn a single room's entities (MeshObjects + HousingRoomEntity).
|
||||
void DespawnRoomEntities(ObjectGuid roomGuid);
|
||||
|
||||
/// After a layout edit: rebuild the door slots whose look changed and re-point every room's door list.
|
||||
void RefreshRoomDoors(std::vector<Housing::Room const*> const& rooms, int32 factionRestriction);
|
||||
|
||||
/// Move/turn a spawned room entity to its stored placement (its meshes and decor follow on the client).
|
||||
void UpdateRoomPlacement(Housing::Room const& room);
|
||||
|
||||
/// Re-point a decor's attachment to its current parent (parent decor object, or the room once the
|
||||
/// stack link is gone); call before UpdateDecorPosition so the local transform uses the fresh anchor.
|
||||
void UpdateDecorAttachment(Housing::PlacedDecor const& decor);
|
||||
|
||||
/// Despawn and re-spawn one room's entity, meshes and decor. The client bakes each mesh's world
|
||||
/// transform from the room entity at mesh-create time and never re-reads it, so a turned or moved
|
||||
/// room must arrive whole again — and the fresh entity create is what rebuilds the editor's layout
|
||||
/// entry and door pins. Decor objects bake the same way and must re-arrive with the room.
|
||||
/// Destroys go out as immediate direct packets (the door-slot churn ordering) so they cannot
|
||||
/// overlap the creates issued in the same call.
|
||||
void RespawnRoomVisuals(Housing::Room const& room, int32 factionRestriction, ObjectGuid houseGuid,
|
||||
Housing* housing);
|
||||
|
||||
/// True when the position lies inside one of the rooms (its RoomWmoData box, on its floor).
|
||||
bool IsInsideAnyRoom(Position const& pos, std::vector<Housing::Room const*> const& rooms) const;
|
||||
|
||||
/// Where the house puts people: the entry hall at the interior origin (SMSG_NEW_WORLD on entry).
|
||||
/// Where entering players appear: the placed starter door decor's position when it exists,
|
||||
/// else the entry hall origin.
|
||||
Position GetEntryPosition() const;
|
||||
|
||||
/// Spawn all placed decor for the owner's house on the interior map.
|
||||
void SpawnInteriorDecor(Housing* housing);
|
||||
|
||||
/// Overload for visits to offline owners (raw decor from Neighborhood::PlotInfo.Decor).
|
||||
void SpawnInteriorDecorFromList(std::vector<Housing::PlacedDecor> const& decor, ObjectGuid houseGuid);
|
||||
|
||||
/// Spawn a single placed decor item immediately (called from PLACE handler).
|
||||
void SpawnSingleInteriorDecor(Housing::PlacedDecor const& decor, ObjectGuid houseGuid);
|
||||
|
||||
/// Update position/rotation of a single interior decor item.
|
||||
void UpdateDecorPosition(ObjectGuid decorGuid, Position const& pos, QuaternionData const& rot, float scale = 1.0f);
|
||||
void UpdateDecorDyes(ObjectGuid decorGuid, std::array<uint32, MAX_HOUSING_DYE_SLOTS> const& dyeSlots);
|
||||
void UpdateDecorPet(ObjectGuid decorGuid, ObjectGuid battlePetGuid, uint32 creatureId, std::string const& petName, uint8 petBehavior);
|
||||
// Restores FHousingDecor_C.PetInfo from the owner's battle pet journal and spawns the companion creature.
|
||||
void ApplyDecorPetBinding(WorldObject* obj, ObjectGuid decorGuid, ObjectGuid battlePetGuid,
|
||||
uint32 creatureId, std::string const& petName, uint8 petBehavior);
|
||||
|
||||
/// Despawn a single decor item by its Housing decor GUID.
|
||||
void DespawnDecorItem(ObjectGuid decorGuid);
|
||||
|
||||
/// Get the interior decor GUID → MeshObject GUID map (for edit mode CREATEs).
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> const& GetDecorGuidMap() const { return _decorGuidToObjGuid; }
|
||||
|
||||
/// Get the room GUID → room MeshObject GUID vectors (for entity set synchronization).
|
||||
std::unordered_map<ObjectGuid, std::vector<ObjectGuid>> const& GetRoomMeshObjects() const { return _roomMeshObjects; }
|
||||
|
||||
/// Get HousingRoomEntity instances for inclusion in initial UPDATE_OBJECT
|
||||
std::vector<HousingRoomEntity*> const& GetRoomEntities() const { return _roomEntities; }
|
||||
|
||||
// Puts QUEST_HOUSING_TUTORIAL_COMPLETE in the log and credits the house-entered kill credit (AUTO_ACCEPT|AUTO_COMPLETE, no quest-giver NPC).
|
||||
void GrantHousingTutorialProgress(Player* player);
|
||||
|
||||
private:
|
||||
void RestoreDecorPetBinding(WorldObject* obj, ObjectGuid decorGuid, ObjectGuid petGuid, uint8 petBehavior);
|
||||
|
||||
/// A door slot that meets another room.
|
||||
struct DoorwayState
|
||||
{
|
||||
ObjectGuid AttachedRoom;
|
||||
bool Owner = false; ///< this side builds the doorway (Housing::OwnsDoorway)
|
||||
uint8 Variant = 0; ///< RoomComponentOption.RoomComponentID of the doorway pieces
|
||||
};
|
||||
|
||||
Position GetRoomWorldPosition(Housing::Room const& room) const;
|
||||
HousingRoomEntity* FindRoomEntity(ObjectGuid roomGuid) const;
|
||||
/// The HouseTheme a slot is built with: its own, else the room's per-surface/legacy theme, else the faction's.
|
||||
static int32 GetComponentThemeID(Housing::Room const& room, RoomComponentData const& comp, int32 factionThemeID);
|
||||
/// FHousingRoomComponentMesh HouseThemeID of a piece: the slot's sub-theme when it belongs to the option's theme.
|
||||
static int32 GetComponentHouseThemeID(Housing::Room const& room, RoomComponentData const& comp, RoomComponentOptionEntry const* option);
|
||||
/// Stairwell halves leave out the shaft between them (lower: ceiling, upper: floor and stairs).
|
||||
static bool IsComponentHidden(std::vector<Housing::Room const*> const& rooms, Housing::Room const& room, RoomComponentData const& comp);
|
||||
void ReplaceSlotMeshes(Housing::Room const& room, RoomComponentData const& comp,
|
||||
std::vector<RoomComponentOptionEntry const*> const& wanted, Position const& roomPos, std::vector<ObjectGuid>& roomMeshes);
|
||||
static std::unordered_map<uint32 /*componentId*/, DoorwayState> GetDoorwayStates(std::vector<Housing::Room const*> const& rooms, Housing::Room const& room);
|
||||
/// The RoomComponentOption rows one component slot is built from.
|
||||
static std::vector<RoomComponentOptionEntry const*> SelectComponentOptions(Housing::Room const& room, RoomComponentData const& comp,
|
||||
int32 factionThemeID, DoorwayState const* doorway);
|
||||
MeshObject* CreateRoomComponentMesh(Housing::Room const& room, RoomComponentData const& comp,
|
||||
RoomComponentOptionEntry const* option, Position const& roomPos);
|
||||
|
||||
ObjectGuid _owner;
|
||||
Player* _loadingPlayer; ///< @workaround Player not in ObjectAccessor during login
|
||||
uint32 _sourceNeighborhoodMapId;
|
||||
uint8 _sourcePlotIndex;
|
||||
bool _roomsSpawned = false;
|
||||
|
||||
// Interior map origin from NeighborhoodMap DB2 (fallback: -1000, -1000, 0.1)
|
||||
float _originX = -1000.0f;
|
||||
float _originY = -1000.0f;
|
||||
float _originZ = 0.1f;
|
||||
|
||||
/// GUIDs of all spawned room MeshObjects, indexed by room GUID
|
||||
std::unordered_map<ObjectGuid /*roomGuid*/, std::vector<ObjectGuid>> _roomMeshObjects;
|
||||
|
||||
/// Decor GUID → visual object GUID (for despawning individual decor items)
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> _decorGuidToObjGuid;
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> _decorAttachParentObj; // decorGuid -> parent decor's client object (snapped stacks)
|
||||
|
||||
/// Decor GUID → companion battle pet creature GUID (CAN_ATTACH_PET decor)
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> _decorGuidToPetSummon;
|
||||
|
||||
/// Map-owned interior exit door GO (persists across owner leave/re-entry)
|
||||
|
||||
/// HousingRoomEntity instances (objectType=18, Housing/2 GUIDs) for the layout editor
|
||||
std::vector<HousingRoomEntity*> _roomEntities;
|
||||
};
|
||||
|
||||
#endif // HouseInteriorMap_h__
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef Housing_h__
|
||||
#define Housing_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include "DatabaseEnvFwd.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "Position.h"
|
||||
#include "QuaternionData.h"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Map;
|
||||
class Player;
|
||||
struct ExteriorComponentEntry;
|
||||
|
||||
// Placed decor is sent to the client relative to its room (FMirroredPositionData_C): subtract the room origin and yaw.
|
||||
inline Position HousingWorldToRoomLocal(Position const& roomWorldPos, Position const& worldPos)
|
||||
{
|
||||
float dx = worldPos.GetPositionX() - roomWorldPos.GetPositionX();
|
||||
float dy = worldPos.GetPositionY() - roomWorldPos.GetPositionY();
|
||||
float cosF = std::cos(roomWorldPos.GetOrientation());
|
||||
float sinF = std::sin(roomWorldPos.GetOrientation());
|
||||
return Position(cosF * dx + sinF * dy, -sinF * dx + cosF * dy, worldPos.GetPositionZ() - roomWorldPos.GetPositionZ());
|
||||
}
|
||||
|
||||
// Rotation counterpart of HousingWorldToRoomLocal: the client composes worldRot = roomRot ⊗ localRot.
|
||||
inline QuaternionData HousingWorldRotationToRoomLocal(float roomWorldYaw, QuaternionData const& worldRot)
|
||||
{
|
||||
float z, y, x;
|
||||
worldRot.toEulerAnglesZYX(z, y, x);
|
||||
return QuaternionData::fromEulerAnglesZYX(z - roomWorldYaw, y, x);
|
||||
}
|
||||
|
||||
class TC_GAME_API Housing
|
||||
{
|
||||
public:
|
||||
struct PlacedDecor
|
||||
{
|
||||
ObjectGuid Guid;
|
||||
uint32 DecorEntryId = 0;
|
||||
float PosX = 0.0f;
|
||||
float PosY = 0.0f;
|
||||
float PosZ = 0.0f;
|
||||
float RotationX = 0.0f;
|
||||
float RotationY = 0.0f;
|
||||
float RotationZ = 0.0f;
|
||||
float RotationW = 1.0f;
|
||||
float Scale = 1.0f;
|
||||
std::array<uint32, MAX_HOUSING_DYE_SLOTS> DyeSlots = {};
|
||||
ObjectGuid RoomGuid;
|
||||
bool Locked = false;
|
||||
time_t PlacementTime = 0;
|
||||
uint8 SourceType = DECOR_SOURCE_STANDARD;
|
||||
std::string SourceValue;
|
||||
ObjectGuid PetGuid; // battle-pet bound to this decor slot (empty = none)
|
||||
uint8 PetFlag = 0; // client-sent flag accompanying the pet binding
|
||||
ObjectGuid ParentDecorGuid; // decor this item is stacked on (empty = none; client groups moves by it)
|
||||
};
|
||||
|
||||
struct Room
|
||||
{
|
||||
ObjectGuid Guid;
|
||||
uint32 RoomEntryId = 0;
|
||||
uint32 SlotIndex = 0;
|
||||
int32 GridX = 0; // 2D grid position (yard offsets from origin)
|
||||
int32 GridY = 0;
|
||||
int32 FloorIndex = 0; // 0=ground, 1+=upper floors
|
||||
uint32 Orientation = 0;
|
||||
bool Mirrored = false;
|
||||
uint32 ThemeId = 0; // Legacy single-theme; kept for back-compat
|
||||
uint32 WallThemeId = 0; // Per-surface theme (HouseTheme ID)
|
||||
uint32 FloorThemeId = 0;
|
||||
uint32 CeilingThemeId = 0;
|
||||
uint32 WallTextureId = 0; // RoomComponentTexture ID for walls
|
||||
uint32 FloorTextureId = 0; // RoomComponentTexture ID for floors
|
||||
uint32 CeilingTextureId = 0; // RoomComponentTexture ID for ceilings
|
||||
int32 ColorOverride = -1; // Shared color override (-1 = default)
|
||||
uint32 DoorTypeId = 0; // last SET_DOOR_TYPE (component, variant); blueprints carry this pair
|
||||
uint8 DoorSlot = 0;
|
||||
std::map<uint32 /*componentId*/, uint8 /*variant*/> DoorTypes; // doorway style of each door
|
||||
// Look of each component slot; falls back to the per-surface fields above.
|
||||
std::map<uint32 /*componentId*/, uint32 /*HouseThemeID*/> ComponentThemes;
|
||||
std::map<uint32 /*componentId*/, uint32 /*RoomComponentTextureID*/> ComponentTextures;
|
||||
uint32 CeilingTypeId = 0;
|
||||
uint8 CeilingSlot = 0;
|
||||
};
|
||||
|
||||
struct Fixture
|
||||
{
|
||||
uint32 FixturePointId = 0;
|
||||
uint32 OptionId = 0;
|
||||
};
|
||||
|
||||
struct CatalogEntry
|
||||
{
|
||||
uint32 DecorEntryId = 0;
|
||||
uint32 Count = 0;
|
||||
uint8 SourceType = DECOR_SOURCE_STANDARD;
|
||||
std::string SourceValue;
|
||||
};
|
||||
|
||||
// ownerGuid: the character that bought the house and keys its rows (retail CosmeticOwner); defaults to the player.
|
||||
explicit Housing(Player* owner, ObjectGuid ownerGuid = ObjectGuid::Empty);
|
||||
|
||||
// Must run once at startup, before any Housing objects are loaded, to prevent cross-player ID collisions.
|
||||
static void InitializeDbIdGenerators();
|
||||
|
||||
bool LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
|
||||
PreparedQueryResult rooms, PreparedQueryResult fixtures, PreparedQueryResult catalog);
|
||||
void SaveToDB(CharacterDatabaseTransaction trans);
|
||||
static void DeleteFromDB(ObjectGuid::LowType ownerGuid, CharacterDatabaseTransaction trans);
|
||||
|
||||
HousingResult Create(ObjectGuid neighborhoodGuid, uint8 plotIndex);
|
||||
void Delete();
|
||||
|
||||
Player* GetOwner() const { return _owner; }
|
||||
// Character that bought the house (row key, retail CosmeticOwner).
|
||||
ObjectGuid GetOwnerGuid() const { return _ownerGuid; }
|
||||
// House settings owner change: the house, its rows and its plot move to another character of the account.
|
||||
HousingResult ChangeOwner(ObjectGuid newOwnerGuid);
|
||||
ObjectGuid GetHouseGuid() const { return _houseGuid; }
|
||||
ObjectGuid GetNeighborhoodGuid() const { return _neighborhoodGuid; }
|
||||
// NEIGHBORHOOD_FACTION_* of the neighborhood the house stands in; the owner's team only when unknown.
|
||||
int32 GetNeighborhoodFaction() const;
|
||||
void SetNeighborhoodGuid(ObjectGuid guid) { _neighborhoodGuid = guid; }
|
||||
ObjectGuid GetPlotGuid() const;
|
||||
uint8 GetPlotIndex() const { return _plotIndex; }
|
||||
void SetPlotIndex(uint8 plotIndex) { _plotIndex = plotIndex; }
|
||||
uint32 GetCreateTime() const { return _createTime; }
|
||||
uint32 GetLevel() const { return _level; }
|
||||
uint32 GetFavor() const { return _favor; }
|
||||
uint32 GetSettingsFlags() const { return _settingsFlags; }
|
||||
ObjectGuid GetCosmeticOwnerGuid() const { return _cosmeticOwnerGuid; }
|
||||
void SetCosmeticOwnerGuid(ObjectGuid guid) { _cosmeticOwnerGuid = guid; }
|
||||
|
||||
// Editor mode
|
||||
void SetEditorMode(HousingEditorMode mode);
|
||||
HousingEditorMode GetEditorMode() const { return _editorMode; }
|
||||
// SMSG_HOUSING_HOUSE_STATUS_RESPONSE trailing bits: 0x80 decor, 0x40 layout, 0x20 fixture edit mode active.
|
||||
uint8 GetEditModeStatusFlags() const
|
||||
{
|
||||
switch (_editorMode)
|
||||
{
|
||||
case HOUSING_EDITOR_MODE_BASIC_DECOR:
|
||||
case HOUSING_EDITOR_MODE_EXPERT_DECOR:
|
||||
return 0x80;
|
||||
case HOUSING_EDITOR_MODE_LAYOUT:
|
||||
return 0x40;
|
||||
case HOUSING_EDITOR_MODE_EXTERIOR_CUSTOMIZATION:
|
||||
return 0x20;
|
||||
default:
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
// Interior state tracking (set by door script, cleared on leave)
|
||||
void SetInInterior(bool interior) { _isInInterior = interior; }
|
||||
bool IsInInterior() const { return _isInInterior; }
|
||||
|
||||
// Decor operations - StartPlacingNewDecor creates a pending placement, PlaceDecorWithGuid commits it.
|
||||
// Mint decor GUIDs from the global generator so ids stay unique across players and reloads.
|
||||
ObjectGuid GenerateDecorGuid(uint32 decorEntryId);
|
||||
ObjectGuid StartPlacingNewDecor(uint32 catalogEntryId, HousingResult& result);
|
||||
uint32 GetPendingPlacementEntryId(ObjectGuid decorGuid) const;
|
||||
void CancelPendingPlacement(ObjectGuid decorGuid);
|
||||
// scale: what the client placed with (HouseDecor InitialScale unless the player resized it); <= 0 means InitialScale.
|
||||
HousingResult PlaceDecorWithGuid(ObjectGuid decorGuid, uint32 decorEntryId, float x, float y, float z,
|
||||
float rotX, float rotY, float rotZ, float rotW, ObjectGuid roomGuid, float scale,
|
||||
ObjectGuid parentDecorGuid = ObjectGuid::Empty);
|
||||
HousingResult PlaceDecor(uint32 decorEntryId, float x, float y, float z,
|
||||
float rotX, float rotY, float rotZ, float rotW, ObjectGuid roomGuid);
|
||||
// movedChildren (optional) receives the stacked decor transformed along with the moved item.
|
||||
HousingResult MoveDecor(ObjectGuid decorGuid, float x, float y, float z,
|
||||
float rotX, float rotY, float rotZ, float rotW, float scale = 1.0f,
|
||||
ObjectGuid parentDecorGuid = ObjectGuid::Empty,
|
||||
std::vector<ObjectGuid>* movedChildren = nullptr);
|
||||
// removedChildren (optional) receives the stacked decor returned to storage along with the item
|
||||
// (guid + acquisition source for its storage entry).
|
||||
HousingResult RemoveDecor(ObjectGuid decorGuid,
|
||||
std::vector<std::pair<ObjectGuid, std::pair<uint8, std::string>>>* removedChildren = nullptr);
|
||||
// Exterior (yard-budget) placements: empty RoomGuid or the plot's base/exterior room identity; every budget path classifies through this.
|
||||
static bool IsExteriorDecorPlacement(ObjectGuid roomGuid);
|
||||
// Interior origin for interior decor; the owner's position (must stand on the plot) for plot decor.
|
||||
Position GetDecorPlacementAnchor(ObjectGuid roomGuid) const;
|
||||
// Interior decor must end up inside one of the house's rooms (RoomWmoData bounding box).
|
||||
HousingResult CheckInteriorDecorBounds(ObjectGuid roomGuid, float x, float y, float z) const;
|
||||
|
||||
// The room whose footprint contains the position (room GUIDs arrive empty from the client).
|
||||
ObjectGuid FindRoomGuidAtPosition(float x, float y, float z) const;
|
||||
// consumeDyes: take one DyeColor.ItemID per newly dyed slot (player dyeing; blueprint imports pass false).
|
||||
HousingResult CommitDecorDyes(ObjectGuid decorGuid, std::array<uint32, MAX_HOUSING_DYE_SLOTS> const& dyeSlots, bool consumeDyes = true);
|
||||
HousingResult SetDecorLocked(ObjectGuid decorGuid, bool locked);
|
||||
// Bind (or, with an empty petGuid, clear) a battle pet on a placed decor slot.
|
||||
HousingResult SetDecorPet(ObjectGuid decorGuid, ObjectGuid petGuid, uint8 petFlag);
|
||||
// Wipe placed decor for a HousingHouseScope (1=Interior, 2=Exterior). Returns the count removed.
|
||||
HousingResult ResetDecor(uint8 scope, uint32* outRemoved = nullptr);
|
||||
PlacedDecor const* GetPlacedDecor(ObjectGuid decorGuid) const;
|
||||
std::vector<PlacedDecor const*> GetAllPlacedDecor() const;
|
||||
|
||||
// Room operations
|
||||
HousingResult PlaceRoom(uint32 roomEntryId, uint32 slotIndex, uint32 orientation, bool mirrored, ObjectGuid* outRoomGuid = nullptr, int32 gridX = 0, int32 gridY = 0, int32 floorIndex = 0);
|
||||
HousingResult RemoveRoom(ObjectGuid roomGuid);
|
||||
HousingResult RotateRoom(ObjectGuid roomGuid, bool clockwise);
|
||||
HousingResult MoveRoom(ObjectGuid roomGuid, ObjectGuid targetRoomGuid, uint32 sourceDoorComponentId, uint32 targetDoorComponentId);
|
||||
HousingResult ApplyRoomTheme(ObjectGuid roomGuid, uint32 themeSetId, std::vector<uint32> const& optionIds);
|
||||
HousingResult ApplyRoomMaterial(ObjectGuid roomGuid, uint32 textureId, int32 colorOverride, std::vector<uint32> const& optionIds);
|
||||
HousingResult SetDoorType(ObjectGuid roomGuid, uint32 doorTypeId, uint8 doorSlot);
|
||||
HousingResult SetCeilingType(ObjectGuid roomGuid, uint32 ceilingTypeId, uint8 ceilingSlot);
|
||||
std::vector<Room const*> GetRooms() const;
|
||||
Room const* GetRoom(ObjectGuid roomGuid) const;
|
||||
ObjectGuid GetBaseRoomGuid() const { return FindBaseRoomGuid(); }
|
||||
uint32 GetNextRoomSlotIndex() const;
|
||||
// Blueprint imports: drop every room but the base room (their decor must already be removed).
|
||||
void RemoveAllNonBaseRooms();
|
||||
void SetRoomAppearance(ObjectGuid roomGuid, Room const& appearance);
|
||||
void ReplaceFixtures(std::vector<Fixture> const& fixtures);
|
||||
std::unordered_map<ObjectGuid, Room> const& GetRoomsMap() const { return _rooms; }
|
||||
|
||||
// Interior layout grid: rooms sit at (GridX, GridY) yards, Orientation in quarter turns; two rooms connect where a door of each meets.
|
||||
struct RoomDoor
|
||||
{
|
||||
uint32 ComponentId = 0;
|
||||
uint8 ComponentType = 0; // HousingRoomComponentType (FHousingDoorData.RoomComponentType: 1 wall, 2 floor, 3 ceiling)
|
||||
Position Local; // RoomComponent.OffsetPos (FHousingDoorData.RoomComponentOffset)
|
||||
float X = 0.0f; // door point, grid space
|
||||
float Y = 0.0f;
|
||||
int32 DirX = 0; // outward direction, grid space
|
||||
int32 DirY = 0;
|
||||
int32 DirZ = 0; // -1 floor / +1 ceiling of a stairwell, 0 for walls
|
||||
|
||||
bool IsVertical() const { return DirZ != 0; }
|
||||
};
|
||||
|
||||
// character_housing_rooms.doorTypes: "componentId:variant,componentId:variant"
|
||||
static std::string SerializeDoorTypes(Room const& room);
|
||||
static void LoadDoorTypes(Room& room, std::string const& doorTypes);
|
||||
/// Housing/3 house GUID: arg1 = the neighborhood's NeighborhoodMapID, arg2 = 7, low = the Battle.net account.
|
||||
static ObjectGuid MakeHouseGuid(uint32 neighborhoodMapId, uint32 bnetAccountId);
|
||||
static std::string SerializeComponentStyles(Room const& room);
|
||||
static void LoadComponentStyles(Room& room, std::string const& componentStyles);
|
||||
|
||||
static void RotateRoomOffset(float x, float y, uint32 orientation, float& outX, float& outY);
|
||||
static std::vector<RoomDoor> GetRoomDoors(uint32 roomEntryId, float gridX, float gridY, uint32 orientation);
|
||||
static std::vector<RoomDoor> GetRoomDoors(Room const& room) { return GetRoomDoors(room.RoomEntryId, float(room.GridX), float(room.GridY), room.Orientation); }
|
||||
// The room whose door meets `door` of `room` (and that door's component), or nullptr.
|
||||
static Room const* FindRoomAtDoor(std::vector<Room const*> const& rooms, Room const& room, RoomDoor const& door, uint32* outComponentId = nullptr);
|
||||
// Which side of a connection builds the doorway: never the base room, otherwise the lower slot.
|
||||
static bool OwnsDoorway(Room const& room, Room const& other);
|
||||
// Door variant of a connection (RoomComponentOption.RoomComponentID): the one picked for either side, else 2.
|
||||
static uint8 GetDoorwayVariant(Room const& room, uint32 componentId, Room const& other, uint32 otherComponentId);
|
||||
// True when a room of this kind at this placement does not overlap any other room on its floor.
|
||||
static bool RoomFits(std::vector<Room const*> const& rooms, uint32 roomEntryId, int32 gridX, int32 gridY, int32 floorIndex,
|
||||
uint32 orientation, ObjectGuid ignoreRoom = ObjectGuid::Empty);
|
||||
// Places a room of this kind, turned to `orientation`, so that one of its doors faces `target`. Fills the grid position.
|
||||
static bool FitRoomToDoor(std::vector<Room const*> const& rooms, uint32 roomEntryId, int32 floorIndex, RoomDoor const& target,
|
||||
uint32 orientation, ObjectGuid ignoreRoom, int32& gridX, int32& gridY);
|
||||
// Stairwells are two stacked rooms at one XY; returns the other half, or nullptr.
|
||||
Room const* FindStairwellPartner(Room const& room) const;
|
||||
// The whole stack of stairwell halves in one column (same cell, contiguous floors), bottom first.
|
||||
std::vector<Room const*> GetStairwellColumn(Room const& room) const;
|
||||
|
||||
// Fixture operations
|
||||
HousingResult SelectFixtureOption(uint32 fixturePointId, uint32 optionId, std::vector<uint32>* removedHookIDs = nullptr);
|
||||
// Re-keys the fixtures on oldCompId's hooks to the equivalent hooks (same fixture type and rank) of newCompId.
|
||||
void MoveHookFixtures(uint32 oldCompId, uint32 newCompId);
|
||||
// Re-resolves Base/Roof root fixtures stored at the old size to the same style at newSize; call after _houseSize changes.
|
||||
void RemapFixturesForHouseSize(uint8 newSize);
|
||||
HousingResult RemoveFixture(uint32 componentID, uint32* outHookID = nullptr);
|
||||
std::vector<Fixture const*> GetFixtures() const;
|
||||
std::unordered_map<uint32, uint32> GetFixtureOverrideMap() const;
|
||||
uint32 GetCoreExteriorComponentID() const;
|
||||
std::unordered_map<uint8, uint32> GetRootComponentOverrides() const;
|
||||
|
||||
// Catalog operations
|
||||
HousingResult AddToCatalog(uint32 decorEntryId, uint8 sourceType = DECOR_SOURCE_STANDARD, std::string sourceValue = {});
|
||||
HousingResult RemoveFromCatalog(uint32 decorEntryId);
|
||||
HousingResult DestroyAllCopies(uint32 decorEntryId);
|
||||
// Resolves an unplaced catalog copy to its synthetic storage GUID (the band PopulateOwnStorageEntries emits); grants no new copies.
|
||||
ObjectGuid MintStorageDecorInstance(uint32 decorEntryId, HousingResult& result);
|
||||
std::vector<CatalogEntry const*> GetCatalogEntries() const;
|
||||
|
||||
// House level and favor
|
||||
void AddLevel(uint32 amount);
|
||||
void SetLevel(uint32 level);
|
||||
void AddFavor(uint64 amount, HousingFavorUpdateSource source = HOUSING_FAVOR_SOURCE_UNKNOWN, bool emitUpdate = true);
|
||||
uint64 GetFavor64() const { return _favor64; }
|
||||
|
||||
// Budget tracking (WeightCost-based)
|
||||
uint32 GetInteriorDecorWeightUsed() const { return _interiorDecorWeightUsed; }
|
||||
uint32 GetExteriorDecorWeightUsed() const { return _exteriorDecorWeightUsed; }
|
||||
uint32 GetRoomWeightUsed() const { return _roomWeightUsed; }
|
||||
uint32 GetFixtureWeightUsed() const { return _fixtureWeightUsed; }
|
||||
uint32 GetMaxInteriorDecorBudget() const;
|
||||
uint32 GetMaxExteriorDecorBudget() const;
|
||||
uint32 GetMaxRoomBudget() const;
|
||||
uint32 GetMaxFixtureBudget() const;
|
||||
void RecalculateBudgets();
|
||||
|
||||
// Rewards of HouseLevelData levels fromLevel..toLevel not granted yet; force bypasses the one-per-account marker.
|
||||
void GrantLevelAwards(uint32 fromLevel, uint32 toLevel, bool force = false);
|
||||
// SMSG_HOUSING_SVCS_UPDATE_HOUSES_LEVEL_FAVOR for this house; -1 = unchanged.
|
||||
void SendLevelFavorUpdate(int32 level, int32 favor) const;
|
||||
|
||||
// UpdateField synchronization
|
||||
void SyncUpdateFields();
|
||||
|
||||
// Settings
|
||||
void SaveSettings(uint32 settingsFlags);
|
||||
|
||||
// House name and description
|
||||
std::string const& GetHouseName() const { return _houseName; }
|
||||
std::string const& GetHouseDescription() const { return _houseDescription; }
|
||||
void SetHouseNameDescription(std::string const& name, std::string const& desc);
|
||||
|
||||
// Exterior lock state
|
||||
void SetExteriorLocked(bool locked);
|
||||
bool IsExteriorLocked() const { return _exteriorLocked; }
|
||||
|
||||
// Photo sharing authorization (per-session, volatile)
|
||||
void SetPhotoSharingAuthorized(bool authorized) { _photoSharingAuthorized = authorized; }
|
||||
bool IsPhotoSharingAuthorized() const { return _photoSharingAuthorized; }
|
||||
|
||||
// House size (HousingFixtureSize enum)
|
||||
void SetHouseSize(uint8 size);
|
||||
uint8 GetHouseSize() const { return _houseSize; }
|
||||
// Size the player picked in the exterior editor; not stored, after a relog it is the current size.
|
||||
uint8 GetPreferredHouseSize() const { return _preferredHouseSize ? _preferredHouseSize : _houseSize; }
|
||||
void SetPreferredHouseSize(uint8 size) { _preferredHouseSize = size; }
|
||||
|
||||
// House type (HouseExteriorWmoData ID)
|
||||
void SetHouseType(uint32 typeId);
|
||||
uint32 GetHouseType() const { return _houseType; }
|
||||
|
||||
// House position persistence (player can reposition house on plot)
|
||||
bool HasCustomPosition() const { return _hasCustomPosition; }
|
||||
Position GetHousePosition() const { return Position(_housePosX, _housePosY, _housePosZ, _houseFacing); }
|
||||
void SetHousePosition(float x, float y, float z, float facing);
|
||||
// Back to the plot's DB2 house position (e.g. after moving to another plot).
|
||||
void ResetHousePosition();
|
||||
// Carry every yard piece over when the house moves to another plot, keeping its place relative to the plot room.
|
||||
void RelocateExteriorDecor(Position const& fromFrame, Position const& toFrame);
|
||||
|
||||
// Direct access to placed decor map (for GO spawning)
|
||||
std::unordered_map<ObjectGuid, PlacedDecor> const& GetPlacedDecorMap() const { return _placedDecor; }
|
||||
bool IsStoragePopulated() const { return _storagePopulated; }
|
||||
void ResetStoragePopulated() { _storagePopulated = false; }
|
||||
|
||||
// Houses from before the door grant get the front door placed into the entry hall once;
|
||||
// a door already placed or withdrawn to storage is never re-placed. Returns the placed row.
|
||||
PlacedDecor const* EnsureStarterDoorPlaced();
|
||||
// Interior arrival point: anchored to the placed front door, entry hall origin without one.
|
||||
Position GetInteriorEntryPosition() const;
|
||||
|
||||
// Fills the account's FHousingStorage_C with the decor of every house of the account, on demand (REQUEST_STORAGE / edit mode).
|
||||
void PopulateCatalogStorageEntries();
|
||||
void PopulateOwnStorageEntries();
|
||||
|
||||
private:
|
||||
uint64 GenerateDecorDbId();
|
||||
uint64 GenerateRoomDbId();
|
||||
|
||||
// Room connectivity helpers
|
||||
ObjectGuid FindBaseRoomGuid() const;
|
||||
bool IsRoomGraphConnectedWithout(ObjectGuid excludeRoomGuid) const;
|
||||
bool IsRoomGraphConnectedWithout(std::unordered_set<ObjectGuid> const& excludeRoomGuids) const;
|
||||
// Room budget cost at this spot: the upper half of a stairwell is free, the stairwell was paid for once.
|
||||
uint32 GetRoomWeightCost(uint32 roomEntryId, int32 gridX, int32 gridY, int32 floorIndex, ObjectGuid self) const;
|
||||
// Re-places a room (grid position + orientation) and carries its placed decor along. Persists both.
|
||||
void SetRoomPlacement(Room& room, int32 gridX, int32 gridY, uint32 orientation, int32 floorIndex = INT32_MIN);
|
||||
|
||||
// Immediate DB persistence helpers
|
||||
void PersistRoomToDB(ObjectGuid roomGuid, Room const& room);
|
||||
void PersistFixtureToDB(uint32 fixturePointId, uint32 optionId);
|
||||
|
||||
// Populate starter fixtures (Base + Roof) on house creation
|
||||
void PopulateStarterFixtures();
|
||||
|
||||
// Fixtures of every house type stay stored (switching back restores them); these pick out the current one's.
|
||||
bool IsCurrentTypeComponent(ExteriorComponentEntry const* comp) const;
|
||||
bool HasCurrentTypeDoor() const;
|
||||
|
||||
// Rejects exterior Lighting decor within HOUSING_LIGHT_OVERLAP_RADIUS of another exterior light; excludeGuid skips the decor being moved.
|
||||
HousingResult CheckLightOverlap(uint32 decorEntryId, float x, float y, float z,
|
||||
bool isExterior, ObjectGuid excludeGuid = ObjectGuid::Empty) const;
|
||||
|
||||
Player* _owner;
|
||||
ObjectGuid _ownerGuid;
|
||||
ObjectGuid _houseGuid;
|
||||
ObjectGuid _neighborhoodGuid;
|
||||
uint8 _plotIndex;
|
||||
uint32 _level;
|
||||
uint32 _favor;
|
||||
uint64 _favor64 = 0;
|
||||
uint32 _settingsFlags;
|
||||
HousingEditorMode _editorMode;
|
||||
bool _exteriorLocked = false;
|
||||
bool _isInInterior = false;
|
||||
uint8 _houseSize = HOUSING_FIXTURE_SIZE_SMALL;
|
||||
uint8 _preferredHouseSize = HOUSING_FIXTURE_SIZE_NONE;
|
||||
uint32 _houseType = 0;
|
||||
uint32 _createTime = 0;
|
||||
std::string _houseName;
|
||||
std::string _houseDescription;
|
||||
|
||||
// Persisted house position on plot
|
||||
float _housePosX = 0.0f;
|
||||
float _housePosY = 0.0f;
|
||||
float _housePosZ = 0.0f;
|
||||
float _houseFacing = 0.0f;
|
||||
ObjectGuid _cosmeticOwnerGuid; // Display owner for guild housing
|
||||
bool _hasCustomPosition = false;
|
||||
bool _storagePopulated = false; // True after PopulateCatalogStorageEntries() — gates Account entity updates
|
||||
bool _photoSharingAuthorized = false; // Per-session photo sharing authorization state
|
||||
|
||||
// WeightCost-based budget tracking
|
||||
uint32 _interiorDecorWeightUsed = 0;
|
||||
uint32 _exteriorDecorWeightUsed = 0;
|
||||
uint32 _roomWeightUsed = 0;
|
||||
uint32 _fixtureWeightUsed = 0;
|
||||
|
||||
std::unordered_map<ObjectGuid, PlacedDecor> _placedDecor;
|
||||
std::unordered_map<ObjectGuid, uint32 /*decorEntryId*/> _pendingPlacements;
|
||||
std::unordered_map<ObjectGuid, Room> _rooms;
|
||||
std::unordered_map<uint32 /*fixturePointId*/, Fixture> _fixtures;
|
||||
std::unordered_map<uint32 /*decorEntryId*/, CatalogEntry> _catalog;
|
||||
|
||||
// Global DB ID generators (atomic, shared across all Housing instances)
|
||||
static std::atomic<uint64> s_nextDecorDbId;
|
||||
static std::atomic<uint64> s_nextRoomDbId;
|
||||
};
|
||||
|
||||
#endif // Housing_h__
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_BLUEPRINT_MGR_H
|
||||
#define TRINITYCORE_HOUSING_BLUEPRINT_MGR_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "HousingBlueprintPackets.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Housing;
|
||||
class Player;
|
||||
|
||||
// A saved layout; decor positions are relative to their room (interior) or plot (exterior).
|
||||
struct HousingBlueprintRoom
|
||||
{
|
||||
uint32 RoomEntryId = 0;
|
||||
int32 GridX = 0;
|
||||
int32 GridY = 0;
|
||||
int32 FloorIndex = 0;
|
||||
uint32 Orientation = 0;
|
||||
bool Mirrored = false;
|
||||
uint32 ThemeId = 0;
|
||||
uint32 WallThemeId = 0;
|
||||
uint32 FloorThemeId = 0;
|
||||
uint32 CeilingThemeId = 0;
|
||||
uint32 WallTextureId = 0;
|
||||
uint32 FloorTextureId = 0;
|
||||
uint32 CeilingTextureId = 0;
|
||||
int32 ColorOverride = -1;
|
||||
uint32 DoorTypeId = 0;
|
||||
uint8 DoorSlot = 0;
|
||||
uint32 CeilingTypeId = 0;
|
||||
uint8 CeilingSlot = 0;
|
||||
};
|
||||
|
||||
struct HousingBlueprintDecor
|
||||
{
|
||||
static constexpr int32 EXTERIOR = -1;
|
||||
|
||||
uint32 DecorEntryId = 0;
|
||||
int32 RoomIndex = EXTERIOR; // index into HousingBlueprintContent::Rooms
|
||||
float PosX = 0.0f; // local to the room or the plot
|
||||
float PosY = 0.0f;
|
||||
float PosZ = 0.0f;
|
||||
float RotX = 0.0f;
|
||||
float RotY = 0.0f;
|
||||
float RotZ = 0.0f;
|
||||
float RotW = 1.0f;
|
||||
float Scale = 1.0f;
|
||||
std::array<uint32, MAX_HOUSING_DYE_SLOTS> DyeSlots = {};
|
||||
};
|
||||
|
||||
struct HousingBlueprintFixture
|
||||
{
|
||||
uint32 FixturePointId = 0;
|
||||
uint32 OptionId = 0;
|
||||
};
|
||||
|
||||
struct HousingBlueprintContent
|
||||
{
|
||||
uint32 HouseType = 0;
|
||||
uint8 HouseSize = 0;
|
||||
bool HasHousePosition = false;
|
||||
float HousePosX = 0.0f; // local to the plot
|
||||
float HousePosY = 0.0f;
|
||||
float HousePosZ = 0.0f;
|
||||
float HouseFacing = 0.0f;
|
||||
std::vector<HousingBlueprintFixture> Fixtures;
|
||||
std::vector<HousingBlueprintRoom> Rooms;
|
||||
std::vector<HousingBlueprintDecor> Decor;
|
||||
};
|
||||
|
||||
struct HousingBlueprint
|
||||
{
|
||||
uint64 Id = 0;
|
||||
std::string Uuid;
|
||||
uint32 BnetAccountId = 0;
|
||||
ObjectGuid::LowType ExporterGuid = 0;
|
||||
std::string Name;
|
||||
HousingBlueprintType Type = HousingBlueprintType::None;
|
||||
uint8 Flags = HOUSING_BLUEPRINT_FLAG_NONE;
|
||||
int64 CreateTime = 0;
|
||||
HousingBlueprintContent Content;
|
||||
|
||||
bool IsAutomaticBackup() const { return (Flags & HOUSING_BLUEPRINT_FLAG_AUTOMATIC_BACKUP) != 0; }
|
||||
};
|
||||
|
||||
// What importing a blueprint into a house needs and lacks - SMSG_HOUSING_BLUEPRINT_CONTENTS.
|
||||
struct HousingBlueprintEvaluation
|
||||
{
|
||||
uint32 UnmetRequirementFlags = 0;
|
||||
WorldPackets::Housing::JamBlueprintContentLists Totals;
|
||||
WorldPackets::Housing::JamBlueprintContentLists Missing;
|
||||
WorldPackets::Housing::JamBlueprintContentLists Invalid;
|
||||
std::vector<WorldPackets::Housing::JamBlueprintBudgetEntry> InteriorBudgets;
|
||||
std::vector<WorldPackets::Housing::JamBlueprintBudgetEntry> ExteriorBudgets;
|
||||
|
||||
bool IsBlocked() const { return (UnmetRequirementFlags & HOUSING_BLUEPRINT_UNMET_BLOCKING_MASK) != 0; }
|
||||
};
|
||||
|
||||
// Result of applying a blueprint to a house's model: what the maps have to despawn and rebuild.
|
||||
struct HousingBlueprintApplyResult
|
||||
{
|
||||
std::vector<ObjectGuid> RemovedDecor;
|
||||
bool InteriorChanged = false;
|
||||
bool ExteriorChanged = false;
|
||||
uint32 PlacedDecor = 0;
|
||||
uint32 SkippedDecor = 0;
|
||||
};
|
||||
|
||||
class TC_GAME_API HousingBlueprintMgr
|
||||
{
|
||||
public:
|
||||
HousingBlueprintMgr(HousingBlueprintMgr const&) = delete;
|
||||
HousingBlueprintMgr& operator=(HousingBlueprintMgr const&) = delete;
|
||||
|
||||
static HousingBlueprintMgr& Instance();
|
||||
|
||||
void LoadFromDB();
|
||||
|
||||
HousingBlueprint const* GetByUuid(std::string_view uuid) const;
|
||||
HousingBlueprint const* Get(uint64 id) const;
|
||||
std::vector<HousingBlueprint const*> GetCollection(uint32 bnetAccountId) const;
|
||||
uint32 GetPlayerMadeCount(uint32 bnetAccountId) const;
|
||||
|
||||
HousingBlueprint const* Create(uint32 bnetAccountId, ObjectGuid::LowType exporterGuid, std::string name, HousingBlueprintType type,
|
||||
uint8 flags, HousingBlueprintContent content);
|
||||
HousingResult Rename(uint32 bnetAccountId, uint64 blueprintId, std::string const& name);
|
||||
HousingResult Delete(uint32 bnetAccountId, uint64 blueprintId);
|
||||
|
||||
static bool IsValidName(std::string const& name);
|
||||
static std::string NormalizeUuid(std::string_view uuid);
|
||||
|
||||
// Exports what the blueprint type covers from a house: House = everything, Interior = rooms and interior decor,
|
||||
// Exterior = house type, size, position, fixtures and yard decor, Room = one room and its decor.
|
||||
static HousingResult Snapshot(Housing const& housing, HousingBlueprintType type, ObjectGuid roomGuid, HousingBlueprintContent& content);
|
||||
|
||||
// Contents, costs and unmet requirements of importing into target (nullptr: no house context; ownership is then taken
|
||||
// from storageSource).
|
||||
static void Evaluate(HousingBlueprint const& blueprint, Housing const* target, Housing const* storageSource,
|
||||
HousingBlueprintEvaluation& evaluation);
|
||||
|
||||
// Imports a house, interior or exterior blueprint into the model of housing. Saves the replaced part as an automatic
|
||||
// backup first.
|
||||
HousingResult ApplyLayout(Player* player, Housing* housing, HousingBlueprint const& blueprint, HousingBlueprintApplyResult& result);
|
||||
// Places a room blueprint's decor into a room that was just added for it.
|
||||
static void ApplyRoomDecor(Player* player, Housing* housing, HousingBlueprint const& blueprint, ObjectGuid roomGuid,
|
||||
HousingBlueprintApplyResult& result);
|
||||
|
||||
private:
|
||||
HousingBlueprintMgr() = default;
|
||||
|
||||
static std::vector<uint8> Serialize(HousingBlueprintContent const& content);
|
||||
static bool Deserialize(std::vector<uint8> const& data, HousingBlueprintContent& content);
|
||||
static std::string GenerateUuid();
|
||||
void PruneBackups(uint32 bnetAccountId);
|
||||
|
||||
std::unordered_map<uint64, HousingBlueprint> _blueprints;
|
||||
std::unordered_map<std::string, uint64> _byUuid;
|
||||
uint64 _nextId = 1;
|
||||
};
|
||||
|
||||
#define sHousingBlueprintMgr HousingBlueprintMgr::Instance()
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_BLUEPRINT_MGR_H
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingClientData.h"
|
||||
#include "DB2DatabaseLoader.h"
|
||||
#include "DB2Metadata.h"
|
||||
#include "DB2Store.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "HotfixDatabase.h"
|
||||
#include "Log.h"
|
||||
#include "World.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Housing::ClientData
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr uint32 HouseRoomFileDataId69587 = 5163003;
|
||||
constexpr uint32 HouseRoomLayoutHash69587 = 0xF04DC279;
|
||||
constexpr uint32 HouseLevelDataFileDataId69587 = 7149539;
|
||||
constexpr uint32 HouseLevelDataLayoutHash69587 = 0xF8146F9D;
|
||||
constexpr uint32 RoomComponentFileDataId69587 = 5503432;
|
||||
constexpr uint32 RoomComponentLayoutHash69587 = 0xA5C69E52;
|
||||
constexpr uint32 RoomWmoDataFileDataId69587 = 5503435;
|
||||
constexpr uint32 RoomWmoDataLayoutHash69587 = 0x5EEBE354;
|
||||
|
||||
// Defensive topology cap only. A room exceeding this is treated as unusable
|
||||
// rather than returning a truncated component graph that could produce an
|
||||
// incorrect door/wall placement.
|
||||
constexpr uint32 MaxRoomComponentsPerTemplate = 512;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// Exact HouseRoom.db2 record order for WoW 12.1.0.69587.
|
||||
// Keep this local until the generated DB2Structure/DB2LoadInfo files are
|
||||
// regenerated globally; this avoids changing unrelated DB2 stores.
|
||||
struct HouseRoomRecord69587
|
||||
{
|
||||
LocalizedString Name;
|
||||
uint32 ID;
|
||||
int8 Size;
|
||||
int32 Flags;
|
||||
int32 Field_002;
|
||||
int32 RoomWmoDataID;
|
||||
int32 UiTextureAtlasElementID;
|
||||
int32 WeightCost;
|
||||
int32 ItemID;
|
||||
int32 SortPriority;
|
||||
};
|
||||
|
||||
struct HouseLevelDataRecord69587
|
||||
{
|
||||
uint32 ID;
|
||||
int32 Level;
|
||||
int32 QuestID;
|
||||
int32 Field_12_0_7_67808_003;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(std::is_standard_layout_v<HouseRoomRecord69587> && std::is_trivially_copyable_v<HouseRoomRecord69587>);
|
||||
static_assert(std::is_standard_layout_v<HouseLevelDataRecord69587> && std::is_trivially_copyable_v<HouseLevelDataRecord69587>);
|
||||
|
||||
constexpr DB2FieldMeta HouseRoomFields69587[10] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Name" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = true, .Type = FT_BYTE, .Name = "Size" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Flags" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_002" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "RoomWmoDataID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "UiTextureAtlasElementID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "WeightCost" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "ItemID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "SortPriority" },
|
||||
};
|
||||
|
||||
constexpr DB2FieldMeta HouseLevelDataFields69587[4] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Level" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "QuestID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Field_12_0_7_67808_003" },
|
||||
};
|
||||
|
||||
constexpr DB2LoadInfo HouseRoomLoadInfo69587
|
||||
{
|
||||
HouseRoomFields69587,
|
||||
10,
|
||||
&HouseRoomMeta::Instance,
|
||||
HOTFIX_SEL_HOUSE_ROOM
|
||||
};
|
||||
|
||||
constexpr DB2LoadInfo HouseLevelDataLoadInfo69587
|
||||
{
|
||||
HouseLevelDataFields69587,
|
||||
4,
|
||||
&HouseLevelDataMeta::Instance,
|
||||
HOTFIX_SEL_HOUSE_LEVEL_DATA
|
||||
};
|
||||
|
||||
struct HousingClientDataStores
|
||||
{
|
||||
DB2Storage<HouseRoomRecord69587> RoomStore{ "HouseRoom.db2", &HouseRoomLoadInfo69587 };
|
||||
DB2Storage<HouseLevelDataRecord69587> LevelStore{ "HouseLevelData.db2", &HouseLevelDataLoadInfo69587 };
|
||||
std::mutex LoadMutex;
|
||||
bool LoadAttempted = false;
|
||||
bool RoomReady = false;
|
||||
bool LevelReady = false;
|
||||
};
|
||||
|
||||
HousingClientDataStores& GetStores()
|
||||
{
|
||||
static HousingClientDataStores stores;
|
||||
return stores;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool LoadVerifiedStore(DB2Storage<T>& store, std::string const& path, LocaleConstant locale, uint32 expectedLayoutHash)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Deliberately load the extracted client DB2 only. Do not call
|
||||
// LoadFromDB()/LoadStringsFromDB() here: the global generated hotfix
|
||||
// query shape is still stale and must not be allowed to corrupt the
|
||||
// verified 69587 binary contract.
|
||||
store.Load(path, locale);
|
||||
}
|
||||
catch (std::system_error const& e)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Housing client-data failed to load {}: {}", store.GetFileName(), e.what());
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Housing client-data rejected {}: {}", store.GetFileName(), e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (store.GetTableHash() == 0 || store.GetLayoutHash() != expectedLayoutHash || store.GetNumRows() == 0)
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Housing client-data rejected {}: tableHash=0x{:08X}, layout=0x{:08X}, expectedLayout=0x{:08X}, indexSize={}",
|
||||
store.GetFileName(), store.GetTableHash(), store.GetLayoutHash(), expectedLayoutHash, store.GetNumRows());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasExpectedGeneratedMetadata()
|
||||
{
|
||||
bool const roomMetaValid =
|
||||
HouseRoomMeta::Instance.FileDataId == HouseRoomFileDataId69587 &&
|
||||
HouseRoomMeta::Instance.LayoutHash == HouseRoomLayoutHash69587 &&
|
||||
HouseRoomMeta::Instance.FieldCount == 10 &&
|
||||
HouseRoomMeta::Instance.FileFieldCount == 10 &&
|
||||
HouseRoomMeta::Instance.GetRecordSize() == sizeof(HouseRoomRecord69587);
|
||||
|
||||
bool const levelMetaValid =
|
||||
HouseLevelDataMeta::Instance.FileDataId == HouseLevelDataFileDataId69587 &&
|
||||
HouseLevelDataMeta::Instance.LayoutHash == HouseLevelDataLayoutHash69587 &&
|
||||
HouseLevelDataMeta::Instance.FieldCount == 4 &&
|
||||
HouseLevelDataMeta::Instance.FileFieldCount == 4 &&
|
||||
HouseLevelDataMeta::Instance.GetRecordSize() == sizeof(HouseLevelDataRecord69587);
|
||||
|
||||
if (!roomMetaValid || !levelMetaValid)
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Housing 69587 client-data metadata guard failed (HouseRoom={}, HouseLevelData={}); room templates remain disabled",
|
||||
roomMetaValid, levelMetaValid);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasExpectedRoomTopologyMetadata()
|
||||
{
|
||||
return RoomComponentMeta::Instance.FileDataId == RoomComponentFileDataId69587 &&
|
||||
RoomComponentMeta::Instance.LayoutHash == RoomComponentLayoutHash69587 &&
|
||||
RoomComponentMeta::Instance.FieldCount == 9 &&
|
||||
RoomComponentMeta::Instance.FileFieldCount == 9 &&
|
||||
RoomComponentMeta::Instance.GetRecordSize() == sizeof(RoomComponentEntry) &&
|
||||
RoomWmoDataMeta::Instance.FileDataId == RoomWmoDataFileDataId69587 &&
|
||||
RoomWmoDataMeta::Instance.LayoutHash == RoomWmoDataLayoutHash69587 &&
|
||||
RoomWmoDataMeta::Instance.FieldCount == 2 &&
|
||||
RoomWmoDataMeta::Instance.FileFieldCount == 2 &&
|
||||
RoomWmoDataMeta::Instance.GetRecordSize() == sizeof(RoomWmoDataEntry);
|
||||
}
|
||||
|
||||
bool IsSafeRoomWmoData(RoomWmoDataEntry const& entry)
|
||||
{
|
||||
return std::isfinite(entry.BoundingBoxMinX) && std::isfinite(entry.BoundingBoxMinY) &&
|
||||
std::isfinite(entry.BoundingBoxMinZ) && std::isfinite(entry.BoundingBoxMaxX) &&
|
||||
std::isfinite(entry.BoundingBoxMaxY) && std::isfinite(entry.BoundingBoxMaxZ) &&
|
||||
std::isfinite(entry.Height);
|
||||
}
|
||||
|
||||
bool IsSafeRoomComponent(RoomComponentEntry const& entry, int32 roomWmoDataId)
|
||||
{
|
||||
return entry.ID != 0 && entry.RoomWmoDataID == roomWmoDataId &&
|
||||
std::isfinite(entry.OffsetPos.X) && std::isfinite(entry.OffsetPos.Y) && std::isfinite(entry.OffsetPos.Z) &&
|
||||
std::isfinite(entry.OffsetRot.X) && std::isfinite(entry.OffsetRot.Y) && std::isfinite(entry.OffsetRot.Z);
|
||||
}
|
||||
|
||||
void EnsureLoaded()
|
||||
{
|
||||
HousingClientDataStores& stores = GetStores();
|
||||
std::scoped_lock lock(stores.LoadMutex);
|
||||
if (stores.LoadAttempted)
|
||||
return;
|
||||
|
||||
stores.LoadAttempted = true;
|
||||
|
||||
if (!HasExpectedGeneratedMetadata())
|
||||
return;
|
||||
|
||||
LocaleConstant const locale = sWorld->GetDefaultDbcLocale();
|
||||
if (!IsValidLocale(locale))
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Housing client-data cannot load with invalid DBC locale {}", uint32(locale));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string const path = sWorld->GetDataPath() + "dbc/" + localeNames[locale] + '/';
|
||||
|
||||
stores.RoomReady = LoadVerifiedStore(stores.RoomStore, path, locale, HouseRoomLayoutHash69587);
|
||||
stores.LevelReady = LoadVerifiedStore(stores.LevelStore, path, locale, HouseLevelDataLayoutHash69587);
|
||||
|
||||
if (stores.RoomReady)
|
||||
TC_LOG_INFO("housing", "Housing client-data loaded verified HouseRoom.db2 layout 0x{:08X}", HouseRoomLayoutHash69587);
|
||||
|
||||
if (stores.LevelReady)
|
||||
TC_LOG_INFO("housing", "Housing client-data loaded verified HouseLevelData.db2 layout 0x{:08X}", HouseLevelDataLayoutHash69587);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRoomTemplateStoreReady()
|
||||
{
|
||||
EnsureLoaded();
|
||||
return GetStores().RoomReady;
|
||||
}
|
||||
|
||||
bool IsHouseLevelDataStoreReady()
|
||||
{
|
||||
EnsureLoaded();
|
||||
return GetStores().LevelReady;
|
||||
}
|
||||
|
||||
bool IsKnownRoomTemplate(uint32 houseRoomId)
|
||||
{
|
||||
if (!houseRoomId || !IsRoomTemplateStoreReady())
|
||||
return false;
|
||||
|
||||
return GetStores().RoomStore.LookupEntry(houseRoomId) != nullptr;
|
||||
}
|
||||
|
||||
bool GetRoomTemplateFlags(uint32 houseRoomId, int32& flags)
|
||||
{
|
||||
flags = 0;
|
||||
if (!houseRoomId || !IsRoomTemplateStoreReady())
|
||||
return false;
|
||||
|
||||
HouseRoomRecord69587 const* room = GetStores().RoomStore.LookupEntry(houseRoomId);
|
||||
if (!room)
|
||||
return false;
|
||||
|
||||
flags = room->Flags;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsRoomTopologyStoreReady()
|
||||
{
|
||||
if (!HasExpectedRoomTopologyMetadata())
|
||||
return false;
|
||||
|
||||
return sRoomComponentStore.GetTableHash() != 0 &&
|
||||
sRoomComponentStore.GetLayoutHash() == RoomComponentLayoutHash69587 &&
|
||||
sRoomComponentStore.GetFieldCount() == 13 &&
|
||||
sRoomComponentStore.GetNumRows() != 0 &&
|
||||
sRoomWmoDataStore.GetTableHash() != 0 &&
|
||||
sRoomWmoDataStore.GetLayoutHash() == RoomWmoDataLayoutHash69587 &&
|
||||
sRoomWmoDataStore.GetFieldCount() == 8 &&
|
||||
sRoomWmoDataStore.GetNumRows() != 0;
|
||||
}
|
||||
|
||||
bool GetRoomTemplateWmoDataID(uint32 houseRoomId, int32& roomWmoDataId)
|
||||
{
|
||||
roomWmoDataId = 0;
|
||||
if (!houseRoomId || !IsRoomTemplateStoreReady() || !IsRoomTopologyStoreReady())
|
||||
return false;
|
||||
|
||||
HouseRoomRecord69587 const* room = GetStores().RoomStore.LookupEntry(houseRoomId);
|
||||
if (!room || room->RoomWmoDataID <= 0)
|
||||
return false;
|
||||
|
||||
RoomWmoDataEntry const* roomWmo = sRoomWmoDataStore.LookupEntry(uint32(room->RoomWmoDataID));
|
||||
if (!roomWmo || !IsSafeRoomWmoData(*roomWmo))
|
||||
return false;
|
||||
|
||||
roomWmoDataId = room->RoomWmoDataID;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetRoomComponentsForWmo(int32 roomWmoDataId, std::vector<RoomComponentTopology>& components)
|
||||
{
|
||||
components.clear();
|
||||
if (roomWmoDataId <= 0 || !IsRoomTopologyStoreReady())
|
||||
return false;
|
||||
|
||||
RoomWmoDataEntry const* roomWmo = sRoomWmoDataStore.LookupEntry(uint32(roomWmoDataId));
|
||||
if (!roomWmo || !IsSafeRoomWmoData(*roomWmo))
|
||||
return false;
|
||||
|
||||
components.reserve(32);
|
||||
for (RoomComponentEntry const* entry : sRoomComponentStore)
|
||||
{
|
||||
if (!entry || entry->RoomWmoDataID != roomWmoDataId)
|
||||
continue;
|
||||
|
||||
if (!IsSafeRoomComponent(*entry, roomWmoDataId))
|
||||
{
|
||||
TC_LOG_WARN("housing", "Rejecting malformed RoomComponent {} for RoomWmoDataID {}",
|
||||
entry->ID, roomWmoDataId);
|
||||
components.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (components.size() >= MaxRoomComponentsPerTemplate)
|
||||
{
|
||||
TC_LOG_WARN("housing", "RoomWmoDataID {} exceeds the {} component topology safety cap",
|
||||
roomWmoDataId, MaxRoomComponentsPerTemplate);
|
||||
components.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
RoomComponentTopology& component = components.emplace_back();
|
||||
component.OffsetPosition = { entry->OffsetPos.X, entry->OffsetPos.Y, entry->OffsetPos.Z };
|
||||
component.OffsetRotation = { entry->OffsetRot.X, entry->OffsetRot.Y, entry->OffsetRot.Z };
|
||||
component.ID = entry->ID;
|
||||
component.RoomWmoDataID = entry->RoomWmoDataID;
|
||||
component.ModelFileDataID = entry->ModelFileDataID;
|
||||
component.Type = entry->Type;
|
||||
component.MeshStyleFilterID = entry->MeshStyleFilterID;
|
||||
component.ConnectionType = entry->ConnectionType;
|
||||
component.Flags = entry->Flags;
|
||||
}
|
||||
|
||||
return !components.empty();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_CLIENT_DATA_H
|
||||
#define TRINITYCORE_HOUSING_CLIENT_DATA_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace Housing::ClientData
|
||||
{
|
||||
struct RoomComponentTopology
|
||||
{
|
||||
std::array<float, 3> OffsetPosition{};
|
||||
std::array<float, 3> OffsetRotation{};
|
||||
uint32 ID = 0;
|
||||
int32 RoomWmoDataID = 0;
|
||||
int32 ModelFileDataID = 0;
|
||||
uint8 Type = 0;
|
||||
int32 MeshStyleFilterID = 0;
|
||||
uint8 ConnectionType = 0;
|
||||
int32 Flags = 0;
|
||||
};
|
||||
|
||||
// Housing client-data is deliberately isolated from the global DB2 stores while
|
||||
// HouseRoom/HouseLevelData generated structures in this branch are being brought
|
||||
// to the 12.1.0.69587 contract. The loader is fail-closed: a missing/mismatched
|
||||
// DB2 never makes a client-controlled template id trusted.
|
||||
bool IsRoomTemplateStoreReady();
|
||||
bool IsHouseLevelDataStoreReady();
|
||||
bool IsKnownRoomTemplate(uint32 houseRoomId);
|
||||
bool GetRoomTemplateFlags(uint32 houseRoomId, int32& flags);
|
||||
|
||||
// RoomComponent/RoomWmoData are already globally loaded in this core. Housing
|
||||
// consumes them only through these verified, bounded helpers so Room placement
|
||||
// never trusts stale topology or malformed offsets.
|
||||
bool IsRoomTopologyStoreReady();
|
||||
bool GetRoomTemplateWmoDataID(uint32 houseRoomId, int32& roomWmoDataId);
|
||||
bool GetRoomComponentsForWmo(int32 roomWmoDataId, std::vector<RoomComponentTopology>& components);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_CLIENT_DATA_H
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingDecorQuantitySession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingPackets.h"
|
||||
#include "Log.h"
|
||||
#include "RealmList.h"
|
||||
#include "WorldSession.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr uint32 MaxDecorInstancesPerAccount = 10000;
|
||||
|
||||
struct LicensedDecorQuantity
|
||||
{
|
||||
uint32 DecorId = 0;
|
||||
uint32 Quantity = 0;
|
||||
};
|
||||
|
||||
uint32 GetDecorGuidNamespace()
|
||||
{
|
||||
uint32 const realmId = sRealmList->GetCurrentRealmId().Realm & 0xFFFFu;
|
||||
return realmId ? realmId : 1u;
|
||||
}
|
||||
|
||||
ObjectGuid MakeDecorGuid(uint32 decorId, uint32 battlenetAccountId, uint32 serial)
|
||||
{
|
||||
if (!decorId || !battlenetAccountId || !serial)
|
||||
return ObjectGuid::Empty;
|
||||
|
||||
uint32 const token = 0x80000000u | serial;
|
||||
uint64 const instanceId = (uint64(battlenetAccountId) << 32) | uint64(token);
|
||||
return ObjectGuid::Create<HighGuid::Housing>(1, GetDecorGuidNamespace(), decorId, instanceId);
|
||||
}
|
||||
|
||||
bool LoadLicensedDecor(uint32 battlenetAccountId, std::vector<LicensedDecorQuantity>& licensed)
|
||||
{
|
||||
licensed.clear();
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT DecorId, Quantity FROM account_housing_decor "
|
||||
"WHERE BattlenetAccountId = {} AND Quantity > 0 ORDER BY DecorId LIMIT {}",
|
||||
battlenetAccountId, MaxDecorInstancesPerAccount + 1);
|
||||
if (!result)
|
||||
return true;
|
||||
|
||||
uint64 totalQuantity = 0;
|
||||
do
|
||||
{
|
||||
if (licensed.size() >= MaxDecorInstancesPerAccount)
|
||||
return false;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
LicensedDecorQuantity entry;
|
||||
entry.DecorId = fields[0].GetUInt32();
|
||||
entry.Quantity = fields[1].GetUInt32();
|
||||
if (!entry.DecorId || !entry.Quantity)
|
||||
return false;
|
||||
|
||||
totalQuantity += entry.Quantity;
|
||||
if (totalQuantity > MaxDecorInstancesPerAccount)
|
||||
return false;
|
||||
|
||||
licensed.push_back(entry);
|
||||
} while (result->NextRow());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EnsureLicensedDecorInstances(uint32 battlenetAccountId)
|
||||
{
|
||||
std::vector<LicensedDecorQuantity> licensed;
|
||||
if (!LoadLicensedDecor(battlenetAccountId, licensed))
|
||||
return false;
|
||||
|
||||
if (licensed.empty())
|
||||
return true;
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
uint32 inserted = 0;
|
||||
uint32 serial = 1;
|
||||
|
||||
for (LicensedDecorQuantity const& entry : licensed)
|
||||
{
|
||||
QueryResult countResult = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*) FROM housing_decor_instance "
|
||||
"WHERE BattlenetAccountId = {} AND DecorID = {}",
|
||||
battlenetAccountId, entry.DecorId);
|
||||
if (!countResult)
|
||||
return false;
|
||||
|
||||
uint32 const existingCount = countResult->Fetch()[0].GetUInt32();
|
||||
if (existingCount >= entry.Quantity)
|
||||
continue;
|
||||
|
||||
uint32 const missing = entry.Quantity - existingCount;
|
||||
for (uint32 i = 0; i < missing; ++i)
|
||||
{
|
||||
ObjectGuid decorGuid;
|
||||
std::string decorGuidString;
|
||||
do
|
||||
{
|
||||
if (serial == 0x7FFFFFFFu)
|
||||
return false;
|
||||
|
||||
decorGuid = MakeDecorGuid(entry.DecorId, battlenetAccountId, serial++);
|
||||
if (!IsDecorGuid(decorGuid) || GetDecorId(decorGuid) != entry.DecorId)
|
||||
return false;
|
||||
|
||||
decorGuidString = decorGuid.ToString();
|
||||
} while (CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_decor_instance WHERE DecorGuid = '{}' LIMIT 1",
|
||||
decorGuidString));
|
||||
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_decor_instance "
|
||||
"(BattlenetAccountId, DecorGuid, DecorID, HouseGuid, PlacementStatus, SourceType, SourceValue) "
|
||||
"VALUES ({}, '{}', {}, '', 0, 0, '')",
|
||||
battlenetAccountId, decorGuidString, entry.DecorId);
|
||||
++inserted;
|
||||
}
|
||||
}
|
||||
|
||||
if (inserted)
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
for (LicensedDecorQuantity const& entry : licensed)
|
||||
{
|
||||
QueryResult verification = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*) FROM housing_decor_instance "
|
||||
"WHERE BattlenetAccountId = {} AND DecorID = {}",
|
||||
battlenetAccountId, entry.DecorId);
|
||||
if (!verification || verification->Fetch()[0].GetUInt32() < entry.Quantity)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inserted)
|
||||
TC_LOG_INFO("housing", "Materialized {} decor instances before licensed-quantity publication for Battle.net account {}",
|
||||
inserted, battlenetAccountId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleGetAllLicensedDecorQuantities(WorldSession* session,
|
||||
WorldPackets::Housing::GetAllLicensedDecorQuantities& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
if (!EnsureLicensedDecorInstances(battlenetAccountId))
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Decor quantity synchronization failed for Battle.net account {}",
|
||||
battlenetAccountId);
|
||||
|
||||
WorldPackets::Housing::GetAllLicensedDecorQuantitiesResponse response;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
session->SendLicensedDecorQuantities(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_DECOR_QUANTITY_SESSION_H
|
||||
#define TRINITYCORE_HOUSING_DECOR_QUANTITY_SESSION_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
class WorldSession;
|
||||
|
||||
namespace WorldPackets::Housing
|
||||
{
|
||||
class GetAllLicensedDecorQuantities;
|
||||
}
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
TC_GAME_API void HandleGetAllLicensedDecorQuantities(WorldSession* session,
|
||||
WorldPackets::Housing::GetAllLicensedDecorQuantities& packet);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_DECOR_QUANTITY_SESSION_H
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingDecorRedemptionSession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingPackets.h"
|
||||
#include "Log.h"
|
||||
#include "RealmList.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
uint32 GetDecorGuidNamespace()
|
||||
{
|
||||
uint32 const realmId = sRealmList->GetCurrentRealmId().Realm & 0xFFFFu;
|
||||
return realmId ? realmId : 1u;
|
||||
}
|
||||
|
||||
uint64 MakeDecorInstanceId(uint32 battlenetAccountId, uint32 transactionId)
|
||||
{
|
||||
return (uint64(battlenetAccountId) << 32) | uint64(transactionId);
|
||||
}
|
||||
|
||||
bool NormalizePersistedRedemptionGuid(uint32 battlenetAccountId, uint32 transactionId,
|
||||
uint32 decorId, ObjectGuid& decorGuid)
|
||||
{
|
||||
if (IsDecorGuid(decorGuid) && GetDecorId(decorGuid) == decorId)
|
||||
return true;
|
||||
|
||||
ObjectGuid const replacement = CreateDecorGuid(GetDecorGuidNamespace(), decorId,
|
||||
MakeDecorInstanceId(battlenetAccountId, transactionId));
|
||||
if (!IsDecorGuid(replacement) || GetDecorId(replacement) != decorId)
|
||||
return false;
|
||||
|
||||
std::string const oldGuid = decorGuid.ToString();
|
||||
std::string const newGuid = replacement.ToString();
|
||||
QueryResult collision = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_decor_instance WHERE DecorGuid = '{}' AND DecorGuid <> '{}' LIMIT 1",
|
||||
newGuid, oldGuid);
|
||||
if (collision)
|
||||
return false;
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_placed_decor SET DecorGuid = '{}' WHERE DecorGuid = '{}'",
|
||||
newGuid, oldGuid);
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_decor_instance SET DecorGuid = '{}' "
|
||||
"WHERE BattlenetAccountId = {} AND DecorGuid = '{}' AND DecorID = {}",
|
||||
newGuid, battlenetAccountId, oldGuid, decorId);
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_deferred_decor_redemption SET DecorGuid = '{}' "
|
||||
"WHERE BattlenetAccountId = {} AND TransactionID = {} AND DecorID = {}",
|
||||
newGuid, battlenetAccountId, transactionId, decorId);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
QueryResult verified = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_deferred_decor_redemption r "
|
||||
"LEFT JOIN housing_decor_instance i ON i.BattlenetAccountId = r.BattlenetAccountId "
|
||||
"AND i.DecorGuid = r.DecorGuid AND i.DecorID = r.DecorID "
|
||||
"WHERE r.BattlenetAccountId = {} AND r.TransactionID = {} AND r.DecorID = {} "
|
||||
"AND r.DecorGuid = '{}' LIMIT 1",
|
||||
battlenetAccountId, transactionId, decorId, newGuid);
|
||||
if (!verified)
|
||||
return false;
|
||||
|
||||
decorGuid = replacement;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleDecorRedeemDeferred(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorRedeemDeferredDecor& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingRedeemDeferredDecorResponse response;
|
||||
response.TransactionID = packet.TransactionID;
|
||||
response.Result = static_cast<uint8>(Result::InvalidDecorItem);
|
||||
|
||||
if (!packet.DecorID || !packet.TransactionID)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
QueryResult existing = CharacterDatabase.PQuery(
|
||||
"SELECT DecorID, DecorGuid FROM housing_deferred_decor_redemption "
|
||||
"WHERE BattlenetAccountId = {} AND TransactionID = {} LIMIT 1",
|
||||
battlenetAccountId, packet.TransactionID);
|
||||
|
||||
if (existing)
|
||||
{
|
||||
Field* fields = existing->Fetch();
|
||||
uint32 const persistedDecorId = fields[0].GetUInt32();
|
||||
ObjectGuid persistedDecorGuid = ObjectGuid::FromString(fields[1].GetString());
|
||||
if (persistedDecorId != packet.DecorID || persistedDecorGuid == ObjectGuid::FromStringFailed ||
|
||||
!NormalizePersistedRedemptionGuid(battlenetAccountId, packet.TransactionID,
|
||||
packet.DecorID, persistedDecorGuid))
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
response.DecorGUID = persistedDecorGuid;
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"INSERT INTO account_housing_decor (BattlenetAccountId, DecorId, Quantity) VALUES ({}, {}, 1) "
|
||||
"ON DUPLICATE KEY UPDATE Quantity = Quantity",
|
||||
battlenetAccountId, packet.DecorID);
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_decor_instance "
|
||||
"(BattlenetAccountId, DecorGuid, DecorID, HouseGuid, PlacementStatus, SourceType, SourceValue) "
|
||||
"VALUES ({}, '{}', {}, '', 0, 0, '') "
|
||||
"ON DUPLICATE KEY UPDATE DecorID = VALUES(DecorID)",
|
||||
battlenetAccountId, response.DecorGUID.ToString(), packet.DecorID);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The client controls both request fields. An unknown transaction is
|
||||
// not proof of entitlement and must never mint decor. Authorized
|
||||
// producers create the deferred-redemption row before notifying the
|
||||
// client; retries then take the idempotent recovery path above.
|
||||
TC_LOG_WARN("housing", "Rejected unauthorized deferred decor redemption for Battle.net account {} transaction {} decor {}",
|
||||
battlenetAccountId, packet.TransactionID, packet.DecorID);
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_deferred_decor_redemption r "
|
||||
"INNER JOIN housing_decor_instance i ON i.BattlenetAccountId = r.BattlenetAccountId "
|
||||
"AND i.DecorGuid = r.DecorGuid AND i.DecorID = r.DecorID "
|
||||
"INNER JOIN account_housing_decor a ON a.BattlenetAccountId = r.BattlenetAccountId "
|
||||
"AND a.DecorId = r.DecorID AND a.Quantity > 0 "
|
||||
"WHERE r.BattlenetAccountId = {} AND r.TransactionID = {} "
|
||||
"AND r.DecorID = {} AND r.DecorGuid = '{}' LIMIT 1",
|
||||
battlenetAccountId, packet.TransactionID, packet.DecorID, response.DecorGUID.ToString());
|
||||
if (committed)
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
else
|
||||
TC_LOG_ERROR("housing", "Deferred decor redemption verification failed for Battle.net account {} transaction {}",
|
||||
battlenetAccountId, packet.TransactionID);
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_DECOR_REDEMPTION_SESSION_H
|
||||
#define TRINITYCORE_HOUSING_DECOR_REDEMPTION_SESSION_H
|
||||
|
||||
class WorldSession;
|
||||
|
||||
namespace WorldPackets::Housing
|
||||
{
|
||||
class HousingDecorRedeemDeferredDecor;
|
||||
}
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
void HandleDecorRedeemDeferred(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorRedeemDeferredDecor& packet);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_DECOR_REDEMPTION_SESSION_H
|
||||
@@ -1,610 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingDecorSession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "HousingMapContext.h"
|
||||
#include "HousingPackets.h"
|
||||
#include "Log.h"
|
||||
#include "Map.h"
|
||||
#include "Player.h"
|
||||
#include "WorldSession.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
Result VerifyAuthoritativeHouse(WorldSession* session, HouseState& house)
|
||||
{
|
||||
house = {};
|
||||
if (!session)
|
||||
return Result::HouseNotFound;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
return Result::HouseNotFound;
|
||||
|
||||
Result const result = sHousingMgr.LoadOwnedHouse(player, ObjectGuid::Empty, house);
|
||||
if (result != Result::Success)
|
||||
return result;
|
||||
return player->IsInWorld() && IsHouseInMap(player->GetMap(), house.HouseGuid)
|
||||
? Result::Success : Result::PermissionDenied;
|
||||
}
|
||||
|
||||
uint8 ToDecorPacketResult(Result result)
|
||||
{
|
||||
return static_cast<uint8>(result);
|
||||
}
|
||||
|
||||
ObjectGuid ParsePersistedGuid(std::string const& guidString)
|
||||
{
|
||||
if (guidString.empty())
|
||||
return ObjectGuid::Empty;
|
||||
|
||||
ObjectGuid const guid = ObjectGuid::FromString(guidString);
|
||||
return guid == ObjectGuid::FromStringFailed ? ObjectGuid::Empty : guid;
|
||||
}
|
||||
|
||||
struct DecorEditSessionState
|
||||
{
|
||||
WorldSession const* Session = nullptr;
|
||||
Player const* PlayerIdentity = nullptr;
|
||||
ObjectGuid PlayerGuid;
|
||||
ObjectGuid HouseGuid;
|
||||
};
|
||||
|
||||
std::mutex DecorEditSessionStateMutex;
|
||||
std::mutex DecorPersistenceMutationMutex;
|
||||
std::unordered_map<uint32, DecorEditSessionState> DecorEditSessionStates;
|
||||
|
||||
void ClearDecorEditSessionState(WorldSession* session)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(DecorEditSessionStateMutex);
|
||||
auto itr = DecorEditSessionStates.find(session->GetBattlenetAccountId());
|
||||
if (itr != DecorEditSessionStates.end() && itr->second.Session == session)
|
||||
DecorEditSessionStates.erase(itr);
|
||||
}
|
||||
|
||||
void SetDecorEditSessionState(WorldSession* session, HouseState const& house, bool enabled)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!enabled || !player)
|
||||
{
|
||||
ClearDecorEditSessionState(session);
|
||||
return;
|
||||
}
|
||||
|
||||
DecorEditSessionState state;
|
||||
state.Session = session;
|
||||
state.PlayerIdentity = player;
|
||||
state.PlayerGuid = player->GetGUID();
|
||||
state.HouseGuid = house.HouseGuid;
|
||||
|
||||
std::lock_guard<std::mutex> lock(DecorEditSessionStateMutex);
|
||||
DecorEditSessionStates[session->GetBattlenetAccountId()] = state;
|
||||
}
|
||||
|
||||
bool IsDecorEditSessionActive(WorldSession* session, HouseState const& house)
|
||||
{
|
||||
if (!session)
|
||||
return false;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(DecorEditSessionStateMutex);
|
||||
auto itr = DecorEditSessionStates.find(session->GetBattlenetAccountId());
|
||||
if (itr == DecorEditSessionStates.end())
|
||||
return false;
|
||||
|
||||
DecorEditSessionState const& state = itr->second;
|
||||
if (state.Session != session)
|
||||
return false;
|
||||
|
||||
if (state.PlayerIdentity != player || state.PlayerGuid != player->GetGUID() || state.HouseGuid != house.HouseGuid)
|
||||
{
|
||||
DecorEditSessionStates.erase(itr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendDecorPlaceResult(WorldSession* session, ObjectGuid const& decorGuid, Result result)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingDecorPlaceResponse response;
|
||||
if (Player* player = session->GetPlayer())
|
||||
response.PlayerGUID = player->GetGUID();
|
||||
response.DecorGUID = decorGuid;
|
||||
response.Result = ToDecorPacketResult(result);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void SendDecorRemoveResult(WorldSession* session, ObjectGuid const& decorGuid, Result result)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingDecorRemoveResponse response;
|
||||
response.DecorGUID = decorGuid;
|
||||
if (Player* player = session->GetPlayer())
|
||||
response.OwnerGUID = player->GetGUID();
|
||||
response.Result = ToDecorPacketResult(result);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
}
|
||||
|
||||
void HandleDecorMove(WorldSession* session, WorldPackets::Housing::HousingDecorMove& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
HouseState house;
|
||||
Result const ownership = VerifyAuthoritativeHouse(session, house);
|
||||
if (ownership != Result::Success)
|
||||
{
|
||||
WorldPackets::Housing::HousingDecorMoveResponse response;
|
||||
if (Player* player = session->GetPlayer())
|
||||
response.PlayerGUID = player->GetGUID();
|
||||
response.DecorGUID = packet.DecorGUID;
|
||||
response.IncludeChildren = packet.IncludeChildren;
|
||||
response.Result = ToDecorPacketResult(ownership);
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
session->ApplyHousingDecorMove(packet);
|
||||
}
|
||||
|
||||
void HandleDecorLock(WorldSession* session, WorldPackets::Housing::HousingDecorLock& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
HouseState house;
|
||||
Result const ownership = VerifyAuthoritativeHouse(session, house);
|
||||
if (ownership != Result::Success)
|
||||
{
|
||||
WorldPackets::Housing::HousingDecorLockResponse response;
|
||||
response.DecorGUID = packet.DecorGUID;
|
||||
if (Player* player = session->GetPlayer())
|
||||
response.PlayerGUID = player->GetGUID();
|
||||
response.Result = ToDecorPacketResult(ownership);
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
session->ApplyHousingDecorLock(packet);
|
||||
}
|
||||
|
||||
void HandleDecorSetDyeSlots(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorSetDyeSlots& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
HouseState house;
|
||||
Result const ownership = VerifyAuthoritativeHouse(session, house);
|
||||
if (ownership != Result::Success)
|
||||
{
|
||||
WorldPackets::Housing::HousingDecorSetDyeSlotsResponse response;
|
||||
response.DecorGUID = packet.DecorGUID;
|
||||
response.Result = ToDecorPacketResult(ownership);
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
session->ApplyHousingDecorDyeSlots(packet);
|
||||
}
|
||||
|
||||
void HandleDecorDeleteFromStorage(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorDeleteFromStorage& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
std::scoped_lock mutationLock(DecorPersistenceMutationMutex);
|
||||
|
||||
WorldPackets::Housing::HousingDecorDeleteFromStorageResponse response;
|
||||
response.Result = ToDecorPacketResult(Result::Success);
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
std::vector<std::pair<ObjectGuid, uint32>> validatedDecor;
|
||||
validatedDecor.reserve(packet.DecorGUIDs.size());
|
||||
|
||||
// Validate the complete batch before beginning the transaction. Duplicate
|
||||
// GUIDs are rejected so one storage instance cannot decrement its license twice.
|
||||
for (ObjectGuid const& decorGuid : packet.DecorGUIDs)
|
||||
{
|
||||
if (!IsDecorGuid(decorGuid) || !decorGuid.GetEntry() ||
|
||||
std::find_if(validatedDecor.begin(), validatedDecor.end(),
|
||||
[&decorGuid](auto const& entry) { return entry.first == decorGuid; }) != validatedDecor.end())
|
||||
{
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
break;
|
||||
}
|
||||
|
||||
QueryResult storedDecor = CharacterDatabase.PQuery(
|
||||
"SELECT i.DecorID FROM housing_decor_instance i "
|
||||
"INNER JOIN account_housing_decor a ON a.BattlenetAccountId = i.BattlenetAccountId "
|
||||
"AND a.DecorId = i.DecorID "
|
||||
"WHERE i.BattlenetAccountId = {} AND i.DecorGuid = '{}' "
|
||||
"AND i.PlacementStatus = 0 AND i.HouseGuid = '' AND a.Quantity > 0 LIMIT 1",
|
||||
battlenetAccountId, decorGuid.ToString());
|
||||
if (!storedDecor)
|
||||
{
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32 const decorId = storedDecor->Fetch()[0].GetUInt32();
|
||||
if (!decorId || decorId != decorGuid.GetEntry())
|
||||
{
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
break;
|
||||
}
|
||||
|
||||
validatedDecor.emplace_back(decorGuid, decorId);
|
||||
}
|
||||
|
||||
if (response.Result == ToDecorPacketResult(Result::Success) && !validatedDecor.empty())
|
||||
{
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
for (auto const& [decorGuid, decorId] : validatedDecor)
|
||||
{
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_decor_instance WHERE BattlenetAccountId = {} "
|
||||
"AND DecorGuid = '{}' AND DecorID = {} AND PlacementStatus = 0 AND HouseGuid = ''",
|
||||
battlenetAccountId, decorGuid.ToString(), decorId);
|
||||
transaction->PAppend(
|
||||
"UPDATE account_housing_decor SET Quantity = Quantity - 1 "
|
||||
"WHERE BattlenetAccountId = {} AND DecorId = {} AND Quantity > 0",
|
||||
battlenetAccountId, decorId);
|
||||
}
|
||||
|
||||
transaction->PAppend(
|
||||
"DELETE FROM account_housing_decor WHERE BattlenetAccountId = {} AND Quantity = 0",
|
||||
battlenetAccountId);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
}
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleDecorRedeemDeferredDecor(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorRedeemDeferredDecor& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingRedeemDeferredDecorResponse response;
|
||||
response.TransactionID = packet.TransactionID;
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player || !packet.DecorID || !packet.TransactionID)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
QueryResult existing = CharacterDatabase.PQuery(
|
||||
"SELECT DecorID, DecorGuid FROM housing_deferred_decor_redemption "
|
||||
"WHERE BattlenetAccountId = {} AND TransactionID = {} LIMIT 1",
|
||||
battlenetAccountId, packet.TransactionID);
|
||||
|
||||
if (existing)
|
||||
{
|
||||
Field* fields = existing->Fetch();
|
||||
uint32 const persistedDecorId = fields[0].GetUInt32();
|
||||
ObjectGuid const persistedDecorGuid = ObjectGuid::FromString(fields[1].GetString());
|
||||
if (persistedDecorId != packet.DecorID || !IsDecorGuid(persistedDecorGuid) ||
|
||||
persistedDecorGuid.GetEntry() != packet.DecorID)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
response.DecorGUID = persistedDecorGuid;
|
||||
|
||||
// Restore only rows that are unambiguously implied by the persisted
|
||||
// redemption. Existing licensed quantities remain unchanged.
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"INSERT INTO account_housing_decor (BattlenetAccountId, DecorId, Quantity) VALUES ({}, {}, 1) "
|
||||
"ON DUPLICATE KEY UPDATE Quantity = Quantity",
|
||||
battlenetAccountId, packet.DecorID);
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_decor_instance "
|
||||
"(BattlenetAccountId, DecorGuid, DecorID, HouseGuid, PlacementStatus, SourceType, SourceValue) "
|
||||
"VALUES ({}, '{}', {}, '', 0, 0, '') "
|
||||
"ON DUPLICATE KEY UPDATE DecorID = VALUES(DecorID)",
|
||||
battlenetAccountId, response.DecorGUID.ToString(), packet.DecorID);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep this legacy entry point fail-closed as well. Client-provided
|
||||
// identifiers cannot create an entitlement; an authorized producer
|
||||
// must persist the deferred-redemption row first.
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_deferred_decor_redemption r "
|
||||
"INNER JOIN housing_decor_instance i ON i.BattlenetAccountId = r.BattlenetAccountId "
|
||||
"AND i.DecorGuid = r.DecorGuid AND i.DecorID = r.DecorID "
|
||||
"INNER JOIN account_housing_decor a ON a.BattlenetAccountId = r.BattlenetAccountId "
|
||||
"AND a.DecorId = r.DecorID AND a.Quantity > 0 "
|
||||
"WHERE r.BattlenetAccountId = {} AND r.TransactionID = {} "
|
||||
"AND r.DecorID = {} AND r.DecorGuid = '{}' LIMIT 1",
|
||||
battlenetAccountId, packet.TransactionID, packet.DecorID, response.DecorGUID.ToString());
|
||||
if (committed)
|
||||
response.Result = ToDecorPacketResult(Result::Success);
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleDecorSetEditMode(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorSetEditMode& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
HouseState house;
|
||||
Result const ownership = VerifyAuthoritativeHouse(session, house);
|
||||
if (ownership != Result::Success)
|
||||
{
|
||||
ClearDecorEditSessionState(session);
|
||||
|
||||
WorldPackets::Housing::HousingDecorSetEditModeResponse response;
|
||||
response.HouseOwnerAccountGUID = session->GetBattlenetAccountGUID();
|
||||
response.Result = ToDecorPacketResult(ownership);
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// WorldSession keeps the canonical private edit-mode flag. The local session
|
||||
// guard mirrors it only after the established handler accepts the owned house.
|
||||
session->ApplyHousingDecorEditMode(packet);
|
||||
SetDecorEditSessionState(session, house, packet.Enabled);
|
||||
}
|
||||
|
||||
void HandleDecorPlace(WorldSession* session, WorldPackets::Housing::HousingDecorPlace& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
std::scoped_lock mutationLock(DecorPersistenceMutationMutex);
|
||||
|
||||
HouseState house;
|
||||
Result const ownership = VerifyAuthoritativeHouse(session, house);
|
||||
if (ownership != Result::Success)
|
||||
{
|
||||
ClearDecorEditSessionState(session);
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, ownership);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsDecorEditSessionActive(session, house))
|
||||
{
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, Result::NotInDecorEditMode);
|
||||
return;
|
||||
}
|
||||
|
||||
float const normalizedScale = packet.Scale == 0.0f ? 1.0f : packet.Scale;
|
||||
bool const validTransform = std::isfinite(packet.PositionX) && std::isfinite(packet.PositionY) &&
|
||||
std::isfinite(packet.PositionZ) && std::isfinite(packet.RotationX) &&
|
||||
std::isfinite(packet.RotationY) && std::isfinite(packet.RotationZ) &&
|
||||
std::isfinite(normalizedScale) && normalizedScale >= 0.2f && normalizedScale <= 2.0f;
|
||||
if (!IsDecorGuid(packet.DecorGUID) || !packet.DecorGUID.GetEntry() || !validTransform)
|
||||
{
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, Result::InvalidDecorItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
{
|
||||
ClearDecorEditSessionState(session);
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, Result::HouseNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
std::string const decorGuidString = packet.DecorGUID.ToString();
|
||||
std::string const houseGuidString = house.HouseGuid.ToString();
|
||||
|
||||
QueryResult licensedInstance = CharacterDatabase.PQuery(
|
||||
"SELECT i.HouseGuid, i.PlacementStatus, COALESCE(p.HouseGuid, ''), COALESCE(p.EntityGuid, '') "
|
||||
"FROM housing_decor_instance i "
|
||||
"INNER JOIN account_housing_decor a ON a.BattlenetAccountId = i.BattlenetAccountId "
|
||||
"AND a.DecorId = i.DecorID AND a.Quantity > 0 "
|
||||
"LEFT JOIN housing_placed_decor p ON p.DecorGuid = i.DecorGuid "
|
||||
"WHERE i.BattlenetAccountId = {} AND i.DecorGuid = '{}' AND i.DecorID = {} LIMIT 1",
|
||||
battlenetAccountId, decorGuidString, packet.DecorGUID.GetEntry());
|
||||
if (!licensedInstance)
|
||||
{
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, Result::InvalidDecorItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Field* instanceFields = licensedInstance->Fetch();
|
||||
std::string const instanceHouseGuid = instanceFields[0].GetString();
|
||||
uint8 const placementStatus = instanceFields[1].GetUInt8();
|
||||
std::string const placedHouseGuid = instanceFields[2].GetString();
|
||||
ObjectGuid staleEntityGuid = ParsePersistedGuid(instanceFields[3].GetString());
|
||||
|
||||
if ((!instanceHouseGuid.empty() && ObjectGuid::FromString(instanceHouseGuid) != house.HouseGuid) ||
|
||||
(!placedHouseGuid.empty() && ObjectGuid::FromString(placedHouseGuid) != house.HouseGuid) ||
|
||||
(placementStatus != 0 && instanceHouseGuid.empty()))
|
||||
{
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, Result::PermissionDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_placed_decor "
|
||||
"(DecorGuid, HouseGuid, OwnerGuid, PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, "
|
||||
"Scale, AttachParentGuid, RoomGuid, ParentHouseFixtureGuid, PlacedComponentID) "
|
||||
"VALUES ('{}', '{}', {}, {}, {}, {}, {}, {}, {}, {}, '{}', '{}', '{}', {}) "
|
||||
"ON DUPLICATE KEY UPDATE HouseGuid = VALUES(HouseGuid), OwnerGuid = VALUES(OwnerGuid), "
|
||||
"PositionX = VALUES(PositionX), PositionY = VALUES(PositionY), PositionZ = VALUES(PositionZ), "
|
||||
"RotationX = VALUES(RotationX), RotationY = VALUES(RotationY), RotationZ = VALUES(RotationZ), "
|
||||
"Scale = VALUES(Scale), AttachParentGuid = VALUES(AttachParentGuid), RoomGuid = VALUES(RoomGuid), "
|
||||
"ParentHouseFixtureGuid = VALUES(ParentHouseFixtureGuid), PlacedComponentID = VALUES(PlacedComponentID)",
|
||||
decorGuidString, houseGuidString, player->GetGUID().GetCounter(),
|
||||
packet.PositionX, packet.PositionY, packet.PositionZ, packet.RotationX, packet.RotationY,
|
||||
packet.RotationZ, normalizedScale, packet.AttachParentGUID.ToString(), packet.RoomGUID.ToString(),
|
||||
packet.ParentHouseFixtureGUID.ToString(), packet.PlacedComponentID);
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_decor_instance SET HouseGuid = '{}', PlacementStatus = 1 "
|
||||
"WHERE BattlenetAccountId = {} AND DecorGuid = '{}' AND DecorID = {}",
|
||||
houseGuidString, battlenetAccountId, decorGuidString, packet.DecorGUID.GetEntry());
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT p.EntityGuid FROM housing_placed_decor p "
|
||||
"INNER JOIN housing_decor_instance i ON i.DecorGuid = p.DecorGuid "
|
||||
"WHERE p.DecorGuid = '{}' AND p.HouseGuid = '{}' AND p.OwnerGuid = {} "
|
||||
"AND i.BattlenetAccountId = {} AND i.DecorID = {} "
|
||||
"AND i.HouseGuid = p.HouseGuid AND i.PlacementStatus = 1 LIMIT 1",
|
||||
decorGuidString, houseGuidString, player->GetGUID().GetCounter(),
|
||||
battlenetAccountId, packet.DecorGUID.GetEntry());
|
||||
if (!committed)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Decor placement commit verification failed for {} on Battle.net account {}",
|
||||
decorGuidString, battlenetAccountId);
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, Result::InvalidDecorItem);
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectGuid const committedEntityGuid = ParsePersistedGuid(committed->Fetch()[0].GetString());
|
||||
if (!committedEntityGuid.IsEmpty())
|
||||
staleEntityGuid = committedEntityGuid;
|
||||
|
||||
// The client owns the newly confirmed visible transform. Remove an older map
|
||||
// materialization only after persistence succeeds; Map owns the entity lifetime.
|
||||
if (!staleEntityGuid.IsEmpty())
|
||||
if (Map* map = player->GetMap())
|
||||
map->RemoveHousingDynamicEntity(staleEntityGuid);
|
||||
|
||||
SendDecorPlaceResult(session, packet.DecorGUID, Result::Success);
|
||||
}
|
||||
|
||||
void HandleDecorRemove(WorldSession* session, WorldPackets::Housing::HousingDecorRemove& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
std::scoped_lock mutationLock(DecorPersistenceMutationMutex);
|
||||
|
||||
HouseState house;
|
||||
Result const ownership = VerifyAuthoritativeHouse(session, house);
|
||||
if (ownership != Result::Success)
|
||||
{
|
||||
ClearDecorEditSessionState(session);
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, ownership);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsDecorEditSessionActive(session, house))
|
||||
{
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, Result::NotInDecorEditMode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsDecorGuid(packet.DecorGUID) || !packet.DecorGUID.GetEntry())
|
||||
{
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, Result::InvalidDecorItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
{
|
||||
ClearDecorEditSessionState(session);
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, Result::HouseNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
std::string const decorGuidString = packet.DecorGUID.ToString();
|
||||
std::string const houseGuidString = house.HouseGuid.ToString();
|
||||
|
||||
QueryResult placedDecor = CharacterDatabase.PQuery(
|
||||
"SELECT p.EntityGuid, i.HouseGuid, i.PlacementStatus "
|
||||
"FROM housing_placed_decor p "
|
||||
"INNER JOIN housing_decor_instance i ON i.DecorGuid = p.DecorGuid "
|
||||
"WHERE p.DecorGuid = '{}' AND p.HouseGuid = '{}' "
|
||||
"AND i.BattlenetAccountId = {} AND i.DecorID = {} LIMIT 1",
|
||||
decorGuidString, houseGuidString, battlenetAccountId, packet.DecorGUID.GetEntry());
|
||||
if (!placedDecor)
|
||||
{
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, Result::InvalidDecorItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Field* placedFields = placedDecor->Fetch();
|
||||
ObjectGuid const entityGuid = ParsePersistedGuid(placedFields[0].GetString());
|
||||
std::string const instanceHouseGuid = placedFields[1].GetString();
|
||||
if (!instanceHouseGuid.empty() && ObjectGuid::FromString(instanceHouseGuid) != house.HouseGuid)
|
||||
{
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, Result::PermissionDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_placed_decor WHERE DecorGuid = '{}' AND HouseGuid = '{}'",
|
||||
decorGuidString, houseGuidString);
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_decor_instance SET HouseGuid = '', PlacementStatus = 0 "
|
||||
"WHERE BattlenetAccountId = {} AND DecorGuid = '{}' AND DecorID = {}",
|
||||
battlenetAccountId, decorGuidString, packet.DecorGUID.GetEntry());
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_decor_instance i "
|
||||
"LEFT JOIN housing_placed_decor p ON p.DecorGuid = i.DecorGuid "
|
||||
"WHERE i.BattlenetAccountId = {} AND i.DecorGuid = '{}' AND i.DecorID = {} "
|
||||
"AND i.HouseGuid = '' AND i.PlacementStatus = 0 AND p.DecorGuid IS NULL LIMIT 1",
|
||||
battlenetAccountId, decorGuidString, packet.DecorGUID.GetEntry());
|
||||
if (!committed)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Decor storage-return commit verification failed for {} on Battle.net account {}",
|
||||
decorGuidString, battlenetAccountId);
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, Result::InvalidDecorItem);
|
||||
return;
|
||||
}
|
||||
|
||||
// Database state is authoritative first; only then remove the live object.
|
||||
// Map owns its lifetime, avoiding raw-pointer deletion and use-after-free.
|
||||
if (!entityGuid.IsEmpty())
|
||||
if (Map* map = player->GetMap())
|
||||
map->RemoveHousingDynamicEntity(entityGuid);
|
||||
|
||||
SendDecorRemoveResult(session, packet.DecorGUID, Result::Success);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_DECOR_SESSION_H
|
||||
#define TRINITYCORE_HOUSING_DECOR_SESSION_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
class WorldSession;
|
||||
|
||||
namespace WorldPackets::Housing
|
||||
{
|
||||
class HousingDecorDeleteFromStorage;
|
||||
class HousingDecorLock;
|
||||
class HousingDecorMove;
|
||||
class HousingDecorPlace;
|
||||
class HousingDecorRedeemDeferredDecor;
|
||||
class HousingDecorRemove;
|
||||
class HousingDecorSetDyeSlots;
|
||||
class HousingDecorSetEditMode;
|
||||
}
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
// Decoration mutations enter through the authoritative Housing::Manager ownership
|
||||
// gate. Handlers that still delegate to WorldSession retain the captured 12.1 wire
|
||||
// behavior while manager-owned persistence paths keep related database rows atomic.
|
||||
TC_GAME_API void HandleDecorSetEditMode(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorSetEditMode& packet);
|
||||
TC_GAME_API void HandleDecorPlace(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorPlace& packet);
|
||||
TC_GAME_API void HandleDecorMove(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorMove& packet);
|
||||
TC_GAME_API void HandleDecorRemove(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorRemove& packet);
|
||||
TC_GAME_API void HandleDecorLock(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorLock& packet);
|
||||
TC_GAME_API void HandleDecorSetDyeSlots(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorSetDyeSlots& packet);
|
||||
|
||||
// Account storage operations are committed transactionally so concrete instances,
|
||||
// licensed quantities and deferred-redemption records cannot drift apart.
|
||||
TC_GAME_API void HandleDecorDeleteFromStorage(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorDeleteFromStorage& packet);
|
||||
TC_GAME_API void HandleDecorRedeemDeferredDecor(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorRedeemDeferredDecor& packet);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_DECOR_SESSION_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,713 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingFixtureSession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "DB2Structure.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingDynamicEntity.h"
|
||||
#include "HousingMapContext.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "HousingPackets.h"
|
||||
#include "Map.h"
|
||||
#include "Player.h"
|
||||
#include "WorldSession.h"
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr uint8 FixtureCannotDeleteDoor = 16;
|
||||
constexpr uint8 FixtureHookOccupied = 18;
|
||||
constexpr uint8 FixtureHouseTypeMismatch = 19;
|
||||
constexpr uint8 FixtureTypeMismatch = 22;
|
||||
constexpr uint8 FixtureNotFound = 20;
|
||||
constexpr uint8 DatabaseError = 9;
|
||||
constexpr uint8 HouseAlreadyThatSize = 28;
|
||||
constexpr uint8 HouseAlreadyThatType = 29;
|
||||
constexpr uint8 HouseSizeNotAvailable = 34;
|
||||
constexpr uint8 HouseTypeNotFound = 32;
|
||||
constexpr uint8 NotOnOwnedPlot = 65;
|
||||
constexpr uint8 NotInFixtureEditMode = 62;
|
||||
std::mutex FixtureMutationMutex;
|
||||
|
||||
struct FixtureEditState
|
||||
{
|
||||
WorldSession const* Session = nullptr;
|
||||
ObjectGuid PlayerGuid;
|
||||
ObjectGuid HouseGuid;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32, FixtureEditState> FixtureEditStates;
|
||||
|
||||
bool LoadEditableHouse(Player* player, HouseState& house)
|
||||
{
|
||||
return player && player->IsInWorld() &&
|
||||
sHousingMgr.LoadOwnedHouse(player, ObjectGuid::Empty, house) == Result::Success &&
|
||||
IsHouseInMap(player->GetMap(), house.HouseGuid);
|
||||
}
|
||||
|
||||
bool HasFixtureEditLock(WorldSession* session, Player* player, HouseState const& house)
|
||||
{
|
||||
auto const itr = FixtureEditStates.find(session->GetBattlenetAccountId());
|
||||
return itr != FixtureEditStates.end() && itr->second.Session == session &&
|
||||
itr->second.PlayerGuid == player->GetGUID() && itr->second.HouseGuid == house.HouseGuid;
|
||||
}
|
||||
|
||||
bool GenerateFixtureGuid(Map* map, ObjectGuid& guid)
|
||||
{
|
||||
guid.Clear();
|
||||
if (!map || map->GetId() > 0x1FFFu)
|
||||
return false;
|
||||
|
||||
for (uint32 attempt = 0; attempt < 64; ++attempt)
|
||||
{
|
||||
ObjectGuid const candidate = ObjectGuid::Create<HighGuid::MeshObject>(
|
||||
uint16(map->GetId()), 0, map->GenerateLowGuid<HighGuid::MeshObject>());
|
||||
if (candidate.IsEmpty())
|
||||
continue;
|
||||
QueryResult duplicate = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*) FROM housing_fixture_entity WHERE EntityGuid = '{}'", candidate.ToString());
|
||||
if (duplicate && duplicate->Fetch()[0].GetUInt64() == 0)
|
||||
{
|
||||
guid = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleHouseExteriorLock(WorldSession* session,
|
||||
WorldPackets::Housing::HouseExteriorLock& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HouseExteriorLockResponse response;
|
||||
response.FixtureEntityGuid = packet.HouseGuid;
|
||||
response.Active = packet.Locked;
|
||||
response.Result = NotOnOwnedPlot;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlocking is always safe and clears only the lock owned by this session.
|
||||
if (!packet.Locked)
|
||||
{
|
||||
std::scoped_lock stateLock(FixtureMutationMutex);
|
||||
auto itr = FixtureEditStates.find(session->GetBattlenetAccountId());
|
||||
if (itr != FixtureEditStates.end() && itr->second.Session == session)
|
||||
FixtureEditStates.erase(itr);
|
||||
response.EditorPlayerGuid = player->GetGUID();
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
HouseState house;
|
||||
if (!LoadEditableHouse(player, house) || packet.HouseGuid != house.HouseGuid ||
|
||||
packet.NeighborhoodGuid != house.NeighborhoodGuid || packet.PlotGuid.IsEmpty() ||
|
||||
packet.PlotGuid.GetHigh() != HighGuid::Housing)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock stateLock(FixtureMutationMutex);
|
||||
FixtureEditStates[session->GetBattlenetAccountId()] =
|
||||
{ session, player->GetGUID(), house.HouseGuid };
|
||||
response.EditorPlayerGuid = player->GetGUID();
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleFixtureSetEditMode(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetEditMode& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingFixtureSetEditModeResponse response;
|
||||
response.Result = static_cast<uint8>(Result::HouseNotFound);
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Leaving editor mode is always safe. Entering requires the authoritative
|
||||
// owned house to be the house represented by the current map context.
|
||||
if (!packet.Active)
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
else
|
||||
{
|
||||
HouseState house;
|
||||
Result const ownership = sHousingMgr.LoadOwnedHouse(player, ObjectGuid::Empty, house);
|
||||
response.Result = static_cast<uint8>(ownership);
|
||||
if (ownership == Result::Success && player->IsInWorld() && IsHouseInMap(player->GetMap(), house.HouseGuid))
|
||||
response.EditorPlayerGuid = player->GetGUID();
|
||||
else if (ownership == Result::Success)
|
||||
response.Result = static_cast<uint8>(Result::PermissionDenied);
|
||||
|
||||
if (response.Result == static_cast<uint8>(Result::Success))
|
||||
{
|
||||
std::scoped_lock stateLock(FixtureMutationMutex);
|
||||
FixtureEditStates[session->GetBattlenetAccountId()] =
|
||||
{ session, player->GetGUID(), house.HouseGuid };
|
||||
}
|
||||
}
|
||||
|
||||
if (!packet.Active)
|
||||
{
|
||||
std::scoped_lock stateLock(FixtureMutationMutex);
|
||||
auto itr = FixtureEditStates.find(session->GetBattlenetAccountId());
|
||||
if (itr != FixtureEditStates.end() && itr->second.Session == session)
|
||||
FixtureEditStates.erase(itr);
|
||||
}
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleFixtureDelete(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureDeleteFixture& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingFixtureDeleteFixtureResponse response;
|
||||
response.FixtureGuid = packet.FixtureGuid;
|
||||
response.Result = FixtureNotFound;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
HouseState house;
|
||||
if (!LoadEditableHouse(player, house))
|
||||
{
|
||||
response.Result = NotOnOwnedPlot;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock mutationLock(FixtureMutationMutex);
|
||||
auto const editState = FixtureEditStates.find(session->GetBattlenetAccountId());
|
||||
if (editState == FixtureEditStates.end() || editState->second.Session != session ||
|
||||
editState->second.PlayerGuid != player->GetGUID() || editState->second.HouseGuid != house.HouseGuid)
|
||||
{
|
||||
response.Result = NotInFixtureEditMode;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.FixtureGuid.GetHigh() != HighGuid::MeshObject || !packet.ExteriorComponentID)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult fixture = CharacterDatabase.PQuery(
|
||||
"SELECT ExteriorComponentID, ExteriorComponentType FROM housing_fixture_entity "
|
||||
"WHERE EntityGuid = '{}' AND HouseGuid = '{}' LIMIT 1",
|
||||
packet.FixtureGuid.ToString(), house.HouseGuid.ToString());
|
||||
if (!fixture || fixture->Fetch()[0].GetUInt32() != packet.ExteriorComponentID)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// The entrance door is part of the playable house shell. Retail does not
|
||||
// allow deleting it through the generic fixture removal operation.
|
||||
if (fixture->Fetch()[1].GetUInt8() == 11)
|
||||
{
|
||||
response.Result = FixtureCannotDeleteDoor;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"DELETE FROM housing_fixture_entity WHERE EntityGuid = '{}' AND HouseGuid = '{}' "
|
||||
"AND ExteriorComponentID = {}",
|
||||
packet.FixtureGuid.ToString(), house.HouseGuid.ToString(), packet.ExteriorComponentID);
|
||||
|
||||
if (CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_fixture_entity WHERE EntityGuid = '{}' AND HouseGuid = '{}' LIMIT 1",
|
||||
packet.FixtureGuid.ToString(), house.HouseGuid.ToString()))
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (player->GetMap())
|
||||
player->GetMap()->RemoveHousingDynamicEntity(packet.FixtureGuid);
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleFixtureCreate(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureCreateFixture& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingFixtureCreateFixtureResponse response;
|
||||
response.Result = FixtureNotFound;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
HouseState house;
|
||||
if (!LoadEditableHouse(player, house))
|
||||
{
|
||||
response.Result = NotOnOwnedPlot;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
ExteriorComponentHookEntry const* hook =
|
||||
sExteriorComponentHookStore.LookupEntry(packet.ExteriorComponentHookID);
|
||||
ExteriorComponentEntry const* component =
|
||||
sExteriorComponentStore.LookupEntry(packet.ExteriorComponentID);
|
||||
if (!hook || !component || component->ModelFileDataID <= 0)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (hook->ExteriorComponentTypeID != component->Type)
|
||||
{
|
||||
response.Result = FixtureTypeMismatch;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsHousingAttachmentGuid(packet.AttachParentGuid) || packet.AttachParentGuid.IsEmpty() ||
|
||||
(!packet.HookEntityGuid.IsEmpty() && !IsHousingAttachmentGuid(packet.HookEntityGuid)))
|
||||
{
|
||||
response.Result = FixtureTypeMismatch;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock mutationLock(FixtureMutationMutex);
|
||||
if (!HasFixtureEditLock(session, player, house))
|
||||
{
|
||||
response.Result = NotInFixtureEditMode;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult exterior = CharacterDatabase.PQuery(
|
||||
"SELECT HouseExteriorWmoDataID FROM character_housing "
|
||||
"WHERE OwnerGuid = {} AND HouseGuid = '{}' LIMIT 1",
|
||||
player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
if (!exterior)
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
uint32 const activeExterior = exterior->Fetch()[0].GetUInt32();
|
||||
if (activeExterior && component->HouseExteriorWmoDataID != activeExterior)
|
||||
{
|
||||
response.Result = FixtureHouseTypeMismatch;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult count = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*) FROM housing_fixture_entity WHERE HouseGuid = '{}'", house.HouseGuid.ToString());
|
||||
if (!count || count->Fetch()[0].GetUInt64() >= SafetyMaxFixturesPerHouse)
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_fixture_entity WHERE HouseGuid = '{}' AND ExteriorComponentHookID = {} LIMIT 1",
|
||||
house.HouseGuid.ToString(), packet.ExteriorComponentHookID))
|
||||
{
|
||||
response.Result = FixtureHookOccupied;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectGuid entityGuid;
|
||||
if (!GenerateFixtureGuid(player->GetMap(), entityGuid))
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr float DegreesToRadians = 3.14159265358979323846f / 180.0f;
|
||||
float const rx = hook->Rotation[0] * DegreesToRadians;
|
||||
float const ry = hook->Rotation[1] * DegreesToRadians;
|
||||
float const rz = hook->Rotation[2] * DegreesToRadians;
|
||||
float const cx = std::cos(rx * 0.5f);
|
||||
float const sx = std::sin(rx * 0.5f);
|
||||
float const cy = std::cos(ry * 0.5f);
|
||||
float const sy = std::sin(ry * 0.5f);
|
||||
float const cz = std::cos(rz * 0.5f);
|
||||
float const sz = std::sin(rz * 0.5f);
|
||||
QuaternionData rotation;
|
||||
rotation.x = sx * cy * cz - cx * sy * sz;
|
||||
rotation.y = cx * sy * cz + sx * cy * sz;
|
||||
rotation.z = cx * cy * sz - sx * sy * cz;
|
||||
rotation.w = cx * cy * cz + sx * sy * sz;
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_fixture_entity "
|
||||
"(EntityGuid, HouseGuid, FileDataID, IsWMO, IsRoom, AttachParentGuid, PositionX, PositionY, PositionZ, "
|
||||
"RotationX, RotationY, RotationZ, RotationW, Scale, AttachmentFlags, ExteriorComponentID, "
|
||||
"HouseExteriorWmoDataID, ExteriorComponentHookID, ExteriorComponentType, Size) "
|
||||
"VALUES ('{}', '{}', {}, 1, 0, '{}', {}, {}, {}, {}, {}, {}, {}, 1, 3, {}, {}, {}, {}, {})",
|
||||
entityGuid.ToString(), house.HouseGuid.ToString(), uint32(component->ModelFileDataID),
|
||||
packet.AttachParentGuid.ToString(), hook->Position[0], hook->Position[1], hook->Position[2],
|
||||
rotation.x, rotation.y, rotation.z, rotation.w, component->ID, component->HouseExteriorWmoDataID,
|
||||
hook->ID, uint32(component->Type), uint32(component->Size));
|
||||
|
||||
if (!CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_fixture_entity WHERE EntityGuid = '{}' AND HouseGuid = '{}' LIMIT 1",
|
||||
entityGuid.ToString(), house.HouseGuid.ToString()))
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicWorldEntity* entity = new DynamicWorldEntity(DynamicWorldEntity::Kind::Fixture, entityGuid);
|
||||
entity->InitializeMesh(uint32(component->ModelFileDataID), true, false, packet.AttachParentGuid,
|
||||
{ hook->Position[0], hook->Position[1], hook->Position[2] }, rotation, 1.0f, 3);
|
||||
entity->InitializeFixture(house.HouseGuid, component->ID, component->HouseExteriorWmoDataID,
|
||||
hook->ID, component->Type, component->Size);
|
||||
if (!entity->AttachToMap(player->GetMap(), *player) || !player->GetMap()->AddToMap(entity))
|
||||
{
|
||||
delete entity;
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"DELETE FROM housing_fixture_entity WHERE EntityGuid = '{}' AND HouseGuid = '{}'",
|
||||
entityGuid.ToString(), house.HouseGuid.ToString());
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
response.FixtureGuid = entityGuid;
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleFixtureSetHouseSize(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetHouseSize& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingFixtureSetHouseSizeResponse response;
|
||||
response.Size = packet.Size;
|
||||
response.Result = HouseSizeNotAvailable;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
HouseState house;
|
||||
if (!LoadEditableHouse(player, house) || packet.HouseGuid != house.HouseGuid)
|
||||
{
|
||||
response.Result = NotOnOwnedPlot;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Retail HousingFixtureSize: 1=Any, 2=Small, 3=Medium, 4=Large.
|
||||
if (packet.Size < 1 || packet.Size > 4)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock mutationLock(FixtureMutationMutex);
|
||||
if (!HasFixtureEditLock(session, player, house))
|
||||
{
|
||||
response.Result = NotInFixtureEditMode;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult current = CharacterDatabase.PQuery(
|
||||
"SELECT HouseSize FROM character_housing WHERE OwnerGuid = {} AND HouseGuid = '{}' LIMIT 1",
|
||||
player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
if (!current)
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
if (current->Fetch()[0].GetUInt8() == packet.Size)
|
||||
{
|
||||
response.Result = HouseAlreadyThatSize;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ObjectGuid> fixtureGuids;
|
||||
QueryResult fixtures = CharacterDatabase.PQuery(
|
||||
"SELECT EntityGuid FROM housing_fixture_entity WHERE HouseGuid = '{}' ORDER BY EntityGuid LIMIT {}",
|
||||
house.HouseGuid.ToString(), SafetyMaxFixturesPerHouse + 1);
|
||||
if (fixtures)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (fixtureGuids.size() >= SafetyMaxFixturesPerHouse)
|
||||
break;
|
||||
ObjectGuid const guid = ObjectGuid::FromString(fixtures->Fetch()[0].GetString());
|
||||
if (guid.GetHigh() == HighGuid::MeshObject)
|
||||
fixtureGuids.push_back(guid);
|
||||
} while (fixtures->NextRow());
|
||||
}
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"UPDATE character_housing SET HouseSize = {} WHERE OwnerGuid = {} AND HouseGuid = '{}'",
|
||||
uint32(packet.Size), player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_fixture_entity SET Size = {} WHERE HouseGuid = '{}'",
|
||||
uint32(packet.Size), house.HouseGuid.ToString());
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT HouseSize FROM character_housing WHERE OwnerGuid = {} AND HouseGuid = '{}' LIMIT 1",
|
||||
player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
QueryResult staleFixture = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_fixture_entity WHERE HouseGuid = '{}' AND Size <> {} LIMIT 1",
|
||||
house.HouseGuid.ToString(), uint32(packet.Size));
|
||||
if (!committed || committed->Fetch()[0].GetUInt8() != packet.Size || staleFixture)
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
for (ObjectGuid const& guid : fixtureGuids)
|
||||
player->GetMap()->RemoveHousingDynamicEntity(guid);
|
||||
SendHouseDynamicEntities(player, house.HouseGuid, house.PlotId);
|
||||
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleFixtureSetCore(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetCoreFixture& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingFixtureSetCoreFixtureResponse response;
|
||||
response.Result = FixtureNotFound;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
HouseState house;
|
||||
if (!LoadEditableHouse(player, house))
|
||||
{
|
||||
response.Result = NotOnOwnedPlot;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
ExteriorComponentEntry const* component =
|
||||
sExteriorComponentStore.LookupEntry(packet.ExteriorComponentID);
|
||||
if (packet.FixtureGuid.GetHigh() != HighGuid::MeshObject || !component || component->ModelFileDataID <= 0)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock mutationLock(FixtureMutationMutex);
|
||||
if (!HasFixtureEditLock(session, player, house))
|
||||
{
|
||||
response.Result = NotInFixtureEditMode;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult fixture = CharacterDatabase.PQuery(
|
||||
"SELECT ExteriorComponentHookID, ExteriorComponentType, ExteriorComponentID "
|
||||
"FROM housing_fixture_entity WHERE EntityGuid = '{}' AND HouseGuid = '{}' LIMIT 1",
|
||||
packet.FixtureGuid.ToString(), house.HouseGuid.ToString());
|
||||
if (!fixture)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
Field* fixtureFields = fixture->Fetch();
|
||||
uint32 const hookID = fixtureFields[0].GetUInt32();
|
||||
uint8 const fixtureType = fixtureFields[1].GetUInt8();
|
||||
if (fixtureFields[2].GetUInt32() == packet.ExteriorComponentID)
|
||||
{
|
||||
// Treat retries as an idempotent success. The core fixture already
|
||||
// represents the requested retail component and no DB write is needed.
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
ExteriorComponentHookEntry const* hook = sExteriorComponentHookStore.LookupEntry(hookID);
|
||||
if (!hook || hook->ExteriorComponentTypeID != component->Type || fixtureType != component->Type)
|
||||
{
|
||||
response.Result = FixtureTypeMismatch;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult exterior = CharacterDatabase.PQuery(
|
||||
"SELECT HouseExteriorWmoDataID FROM character_housing "
|
||||
"WHERE OwnerGuid = {} AND HouseGuid = '{}' LIMIT 1",
|
||||
player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
if (!exterior)
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
uint32 const activeExterior = exterior->Fetch()[0].GetUInt32();
|
||||
if (activeExterior && component->HouseExteriorWmoDataID != activeExterior)
|
||||
{
|
||||
response.Result = FixtureHouseTypeMismatch;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"UPDATE housing_fixture_entity SET FileDataID = {}, ExteriorComponentID = {}, "
|
||||
"HouseExteriorWmoDataID = {}, ExteriorComponentType = {}, Size = {} "
|
||||
"WHERE EntityGuid = '{}' AND HouseGuid = '{}' AND ExteriorComponentHookID = {}",
|
||||
uint32(component->ModelFileDataID), component->ID, component->HouseExteriorWmoDataID,
|
||||
uint32(component->Type), uint32(component->Size), packet.FixtureGuid.ToString(),
|
||||
house.HouseGuid.ToString(), hookID);
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT ExteriorComponentID, FileDataID FROM housing_fixture_entity "
|
||||
"WHERE EntityGuid = '{}' AND HouseGuid = '{}' LIMIT 1",
|
||||
packet.FixtureGuid.ToString(), house.HouseGuid.ToString());
|
||||
if (!committed || committed->Fetch()[0].GetUInt32() != component->ID ||
|
||||
committed->Fetch()[1].GetUInt32() != uint32(component->ModelFileDataID))
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
player->GetMap()->RemoveHousingDynamicEntity(packet.FixtureGuid);
|
||||
SendHouseDynamicEntities(player, house.HouseGuid, house.PlotId);
|
||||
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleFixtureSetHouseType(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetHouseType& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingFixtureSetHouseTypeResponse response;
|
||||
response.HouseExteriorTypeID = packet.HouseExteriorWmoDataID;
|
||||
response.Result = HouseTypeNotFound;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
HouseState house;
|
||||
if (!LoadEditableHouse(player, house) || packet.HouseGuid != house.HouseGuid)
|
||||
{
|
||||
response.Result = NotOnOwnedPlot;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packet.HouseExteriorWmoDataID ||
|
||||
!sHouseExteriorWmoDataStore.LookupEntry(packet.HouseExteriorWmoDataID))
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock mutationLock(FixtureMutationMutex);
|
||||
if (!HasFixtureEditLock(session, player, house))
|
||||
{
|
||||
response.Result = NotInFixtureEditMode;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult current = CharacterDatabase.PQuery(
|
||||
"SELECT HouseExteriorWmoDataID FROM character_housing "
|
||||
"WHERE OwnerGuid = {} AND HouseGuid = '{}' LIMIT 1",
|
||||
player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
if (!current)
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
if (current->Fetch()[0].GetUInt32() == packet.HouseExteriorWmoDataID)
|
||||
{
|
||||
response.Result = HouseAlreadyThatType;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ObjectGuid> liveFixtureGuids;
|
||||
QueryResult fixtures = CharacterDatabase.PQuery(
|
||||
"SELECT EntityGuid FROM housing_fixture_entity WHERE HouseGuid = '{}' ORDER BY EntityGuid LIMIT {}",
|
||||
house.HouseGuid.ToString(), SafetyMaxFixturesPerHouse + 1);
|
||||
if (fixtures)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (liveFixtureGuids.size() >= SafetyMaxFixturesPerHouse)
|
||||
break;
|
||||
ObjectGuid const guid = ObjectGuid::FromString(fixtures->Fetch()[0].GetString());
|
||||
if (guid.GetHigh() == HighGuid::MeshObject)
|
||||
liveFixtureGuids.push_back(guid);
|
||||
} while (fixtures->NextRow());
|
||||
}
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"UPDATE character_housing SET HouseExteriorWmoDataID = {} "
|
||||
"WHERE OwnerGuid = {} AND HouseGuid = '{}'",
|
||||
packet.HouseExteriorWmoDataID, player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_fixture_entity WHERE HouseGuid = '{}' AND HouseExteriorWmoDataID <> {}",
|
||||
house.HouseGuid.ToString(), packet.HouseExteriorWmoDataID);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT HouseExteriorWmoDataID FROM character_housing "
|
||||
"WHERE OwnerGuid = {} AND HouseGuid = '{}' LIMIT 1",
|
||||
player->GetGUID().GetCounter(), house.HouseGuid.ToString());
|
||||
QueryResult incompatible = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_fixture_entity WHERE HouseGuid = '{}' "
|
||||
"AND HouseExteriorWmoDataID <> {} LIMIT 1",
|
||||
house.HouseGuid.ToString(), packet.HouseExteriorWmoDataID);
|
||||
if (!committed || committed->Fetch()[0].GetUInt32() != packet.HouseExteriorWmoDataID || incompatible)
|
||||
{
|
||||
response.Result = DatabaseError;
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
for (ObjectGuid const& guid : liveFixtureGuids)
|
||||
player->GetMap()->RemoveHousingDynamicEntity(guid);
|
||||
SendHouseDynamicEntities(player, house.HouseGuid, house.PlotId);
|
||||
|
||||
response.Result = static_cast<uint8>(Result::Success);
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_FIXTURE_SESSION_H
|
||||
#define TRINITYCORE_HOUSING_FIXTURE_SESSION_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
class WorldSession;
|
||||
|
||||
namespace WorldPackets::Housing
|
||||
{
|
||||
class HouseExteriorLock;
|
||||
class HousingFixtureSetEditMode;
|
||||
class HousingFixtureDeleteFixture;
|
||||
class HousingFixtureCreateFixture;
|
||||
class HousingFixtureSetCoreFixture;
|
||||
class HousingFixtureSetHouseSize;
|
||||
class HousingFixtureSetHouseType;
|
||||
}
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
TC_GAME_API void HandleHouseExteriorLock(WorldSession* session,
|
||||
WorldPackets::Housing::HouseExteriorLock& packet);
|
||||
TC_GAME_API void HandleFixtureSetEditMode(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetEditMode& packet);
|
||||
TC_GAME_API void HandleFixtureDelete(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureDeleteFixture& packet);
|
||||
TC_GAME_API void HandleFixtureCreate(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureCreateFixture& packet);
|
||||
TC_GAME_API void HandleFixtureSetCore(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetCoreFixture& packet);
|
||||
TC_GAME_API void HandleFixtureSetHouseSize(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetHouseSize& packet);
|
||||
TC_GAME_API void HandleFixtureSetHouseType(WorldSession* session,
|
||||
WorldPackets::Housing::HousingFixtureSetHouseType& packet);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,338 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingInitiativeCriteria.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingInitiativeProgress.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::size_t MaxPendingCriteriaUpdates = 16384;
|
||||
constexpr std::size_t MaxCriteriaUpdatesPerWorldTick = 256;
|
||||
|
||||
struct PendingCriteriaUpdate
|
||||
{
|
||||
ObjectGuid PlayerGuid;
|
||||
uint32 CriteriaId = 0;
|
||||
uint64 Counter = 0;
|
||||
};
|
||||
|
||||
struct CriteriaCheckpoint
|
||||
{
|
||||
uint32 ActivityId = 0;
|
||||
uint64 LastCounter = 0;
|
||||
uint64 LastMemberProgress = 0;
|
||||
};
|
||||
|
||||
std::mutex PendingCriteriaMutex;
|
||||
std::deque<PendingCriteriaUpdate> PendingCriteriaUpdates;
|
||||
|
||||
uint64 GetMemberProgress(InitiativeProgressSnapshot const& snapshot, uint32 activityId)
|
||||
{
|
||||
for (InitiativeMemberProgressState const& progress : snapshot.PlayerActivities)
|
||||
if (progress.ActivityId == activityId)
|
||||
return progress.Progress;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool LoadActiveNeighborhood(Player const* player, ObjectGuid& neighborhoodGuid)
|
||||
{
|
||||
neighborhoodGuid.Clear();
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT ActiveNeighborhoodGuid FROM housing_initiative_player_state "
|
||||
"WHERE CharacterGuid = {} LIMIT 1",
|
||||
player->GetGUID().GetCounter());
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
neighborhoodGuid = ObjectGuid::FromString(result->Fetch()[0].GetString());
|
||||
return IsNeighborhoodGuid(neighborhoodGuid);
|
||||
}
|
||||
|
||||
bool LoadCriteriaActivity(uint32 initiativeId, uint32 criteriaId, uint32& activityId)
|
||||
{
|
||||
activityId = 0;
|
||||
if (!initiativeId || !criteriaId)
|
||||
return false;
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT ActivityID FROM housing_initiative_activity_definition "
|
||||
"WHERE InitiativeID = {} AND CriteriaID = {} AND `Enabled` = 1 "
|
||||
"ORDER BY ActivityID LIMIT 2",
|
||||
initiativeId, criteriaId);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
activityId = result->Fetch()[0].GetUInt32();
|
||||
if (!activityId)
|
||||
return false;
|
||||
|
||||
if (result->NextRow())
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Initiative {} has multiple enabled activities mapped to CriteriaID {}; criteria progress was ignored",
|
||||
initiativeId, criteriaId);
|
||||
activityId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadCheckpoint(ObjectGuid const& neighborhoodGuid, uint32 initiativeId, uint64 characterGuid,
|
||||
uint32 criteriaId, CriteriaCheckpoint& checkpoint)
|
||||
{
|
||||
checkpoint = {};
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT ActivityID, LastCounter, LastMemberProgress "
|
||||
"FROM housing_initiative_criteria_checkpoint "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} AND CharacterGuid = {} AND CriteriaID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId, characterGuid, criteriaId);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
checkpoint.ActivityId = fields[0].GetUInt32();
|
||||
checkpoint.LastCounter = fields[1].GetUInt64();
|
||||
checkpoint.LastMemberProgress = fields[2].GetUInt64();
|
||||
return checkpoint.ActivityId != 0;
|
||||
}
|
||||
|
||||
bool PersistCheckpoint(ObjectGuid const& neighborhoodGuid, uint32 initiativeId, uint64 characterGuid,
|
||||
uint32 criteriaId, uint32 activityId, uint64 counter, uint64 memberProgress)
|
||||
{
|
||||
if (!IsNeighborhoodGuid(neighborhoodGuid) || !initiativeId || !characterGuid || !criteriaId || !activityId)
|
||||
return false;
|
||||
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_initiative_criteria_checkpoint "
|
||||
"(NeighborhoodGuid, InitiativeID, CharacterGuid, CriteriaID, ActivityID, LastCounter, LastMemberProgress) "
|
||||
"VALUES ('{}', {}, {}, {}, {}, {}, {}) "
|
||||
"ON DUPLICATE KEY UPDATE ActivityID = VALUES(ActivityID), LastCounter = VALUES(LastCounter), "
|
||||
"LastMemberProgress = VALUES(LastMemberProgress)",
|
||||
neighborhoodGuid.ToString(), initiativeId, characterGuid, criteriaId, activityId, counter, memberProgress);
|
||||
|
||||
QueryResult verification = CharacterDatabase.PQuery(
|
||||
"SELECT ActivityID, LastCounter, LastMemberProgress "
|
||||
"FROM housing_initiative_criteria_checkpoint "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} AND CharacterGuid = {} AND CriteriaID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId, characterGuid, criteriaId);
|
||||
if (!verification)
|
||||
return false;
|
||||
|
||||
Field* fields = verification->Fetch();
|
||||
return fields[0].GetUInt32() == activityId && fields[1].GetUInt64() == counter &&
|
||||
fields[2].GetUInt64() == memberProgress;
|
||||
}
|
||||
|
||||
void ProcessCriteriaUpdate(PendingCriteriaUpdate const& update)
|
||||
{
|
||||
if (update.PlayerGuid.IsEmpty() || !update.CriteriaId)
|
||||
return;
|
||||
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(update.PlayerGuid);
|
||||
if (!player || !player->IsInWorld())
|
||||
return;
|
||||
|
||||
ObjectGuid neighborhoodGuid;
|
||||
if (!LoadActiveNeighborhood(player, neighborhoodGuid))
|
||||
return;
|
||||
|
||||
InitiativeProgressSnapshot snapshot;
|
||||
InitiativeProgressResult const loadResult = LoadInitiativeProgress(player, neighborhoodGuid, snapshot);
|
||||
if (loadResult != InitiativeProgressResult::Success || !snapshot.InitiativeId || snapshot.Completed || snapshot.Expired)
|
||||
return;
|
||||
|
||||
uint32 activityId = 0;
|
||||
if (!LoadCriteriaActivity(snapshot.InitiativeId, update.CriteriaId, activityId))
|
||||
return;
|
||||
|
||||
uint64 const characterGuid = player->GetGUID().GetCounter();
|
||||
uint64 const currentMemberProgress = GetMemberProgress(snapshot, activityId);
|
||||
|
||||
CriteriaCheckpoint checkpoint;
|
||||
if (!LoadCheckpoint(neighborhoodGuid, snapshot.InitiativeId, characterGuid, update.CriteriaId, checkpoint))
|
||||
{
|
||||
// The first observed counter is a baseline, not contribution. Achievement
|
||||
// criteria can predate the current Initiative, so counting the full value
|
||||
// here would grant historical progress a second time.
|
||||
if (!PersistCheckpoint(neighborhoodGuid, snapshot.InitiativeId, characterGuid,
|
||||
update.CriteriaId, activityId, update.Counter, currentMemberProgress))
|
||||
TC_LOG_ERROR("housing", "Failed to seed Initiative criteria checkpoint for player {} criteria {}",
|
||||
player->GetGUID().ToString(), update.CriteriaId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkpoint.ActivityId != activityId || update.Counter < checkpoint.LastCounter ||
|
||||
currentMemberProgress < checkpoint.LastMemberProgress)
|
||||
{
|
||||
// A criteria reset, Initiative remap, or persistence generation change starts
|
||||
// a new baseline. Never turn a backwards counter into unsigned progress.
|
||||
if (!PersistCheckpoint(neighborhoodGuid, snapshot.InitiativeId, characterGuid,
|
||||
update.CriteriaId, activityId, update.Counter, currentMemberProgress))
|
||||
TC_LOG_ERROR("housing", "Failed to reset Initiative criteria checkpoint for player {} criteria {}",
|
||||
player->GetGUID().ToString(), update.CriteriaId);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64 effectiveLastCounter = checkpoint.LastCounter;
|
||||
uint64 effectiveMemberProgress = checkpoint.LastMemberProgress;
|
||||
|
||||
// Recover a successful Housing progress commit whose following checkpoint
|
||||
// write was interrupted. The member contribution provides a monotonic witness
|
||||
// for how much of the criteria delta has already been consumed.
|
||||
if (currentMemberProgress > checkpoint.LastMemberProgress)
|
||||
{
|
||||
uint64 const recoveredProgress = currentMemberProgress - checkpoint.LastMemberProgress;
|
||||
if (effectiveLastCounter > std::numeric_limits<uint64>::max() - recoveredProgress)
|
||||
effectiveLastCounter = std::numeric_limits<uint64>::max();
|
||||
else
|
||||
effectiveLastCounter += recoveredProgress;
|
||||
|
||||
effectiveLastCounter = std::min(effectiveLastCounter, update.Counter);
|
||||
effectiveMemberProgress = currentMemberProgress;
|
||||
|
||||
if (!PersistCheckpoint(neighborhoodGuid, snapshot.InitiativeId, characterGuid,
|
||||
update.CriteriaId, activityId, effectiveLastCounter, effectiveMemberProgress))
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Failed to reconcile Initiative criteria checkpoint for player {} criteria {}",
|
||||
player->GetGUID().ToString(), update.CriteriaId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (update.Counter <= effectiveLastCounter)
|
||||
{
|
||||
if (effectiveMemberProgress != currentMemberProgress &&
|
||||
!PersistCheckpoint(neighborhoodGuid, snapshot.InitiativeId, characterGuid,
|
||||
update.CriteriaId, activityId, effectiveLastCounter, currentMemberProgress))
|
||||
TC_LOG_ERROR("housing", "Failed to refresh Initiative criteria checkpoint for player {} criteria {}",
|
||||
player->GetGUID().ToString(), update.CriteriaId);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64 const delta = update.Counter - effectiveLastCounter;
|
||||
InitiativeProgressSnapshot updatedSnapshot;
|
||||
InitiativeProgressTransition transition;
|
||||
InitiativeProgressResult const applyResult = ApplyInitiativeActivityProgress(
|
||||
player, neighborhoodGuid, activityId, delta, &updatedSnapshot, &transition);
|
||||
if (applyResult != InitiativeProgressResult::Success)
|
||||
{
|
||||
TC_LOG_DEBUG("housing",
|
||||
"Initiative criteria progress was not applied for player {} criteria {} activity {} result {}",
|
||||
player->GetGUID().ToString(), update.CriteriaId, activityId, uint32(applyResult));
|
||||
return;
|
||||
}
|
||||
|
||||
uint64 const updatedMemberProgress = GetMemberProgress(updatedSnapshot, activityId);
|
||||
if (!PersistCheckpoint(neighborhoodGuid, snapshot.InitiativeId, characterGuid,
|
||||
update.CriteriaId, activityId, update.Counter, updatedMemberProgress))
|
||||
TC_LOG_ERROR("housing", "Initiative criteria progress applied but checkpoint verification failed for player {} criteria {}",
|
||||
player->GetGUID().ToString(), update.CriteriaId);
|
||||
}
|
||||
|
||||
void QueueCriteriaUpdate(ObjectGuid const& playerGuid, uint32 criteriaId, uint64 counter)
|
||||
{
|
||||
if (playerGuid.IsEmpty() || !criteriaId)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(PendingCriteriaMutex);
|
||||
if (PendingCriteriaUpdates.size() >= MaxPendingCriteriaUpdates)
|
||||
{
|
||||
// Prefer replacing an older update for the same criterion. Its absolute
|
||||
// counter contains the missed delta and is therefore safe to coalesce.
|
||||
for (auto itr = PendingCriteriaUpdates.rbegin(); itr != PendingCriteriaUpdates.rend(); ++itr)
|
||||
{
|
||||
if (itr->PlayerGuid == playerGuid && itr->CriteriaId == criteriaId)
|
||||
{
|
||||
itr->Counter = counter;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PendingCriteriaUpdates.pop_front();
|
||||
TC_LOG_WARN("housing", "Initiative criteria queue reached its safety cap; oldest update was coalesced away");
|
||||
}
|
||||
|
||||
PendingCriteriaUpdates.push_back({ playerGuid, criteriaId, counter });
|
||||
}
|
||||
|
||||
void ProcessPendingCriteriaUpdates()
|
||||
{
|
||||
std::vector<PendingCriteriaUpdate> batch;
|
||||
batch.reserve(MaxCriteriaUpdatesPerWorldTick);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(PendingCriteriaMutex);
|
||||
while (!PendingCriteriaUpdates.empty() && batch.size() < MaxCriteriaUpdatesPerWorldTick)
|
||||
{
|
||||
batch.push_back(PendingCriteriaUpdates.front());
|
||||
PendingCriteriaUpdates.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
for (PendingCriteriaUpdate const& update : batch)
|
||||
ProcessCriteriaUpdate(update);
|
||||
}
|
||||
|
||||
class InitiativeCriteriaPacketScript final : public ServerScript
|
||||
{
|
||||
public:
|
||||
InitiativeCriteriaPacketScript() : ServerScript("InitiativeCriteriaPacketScript") { }
|
||||
|
||||
void OnPacketSend(WorldSession* session, WorldPacket& packet) override
|
||||
{
|
||||
if (!session || packet.GetOpcode() != SMSG_CRITERIA_UPDATE || packet.size() < sizeof(uint32) + sizeof(uint64))
|
||||
return;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
packet.rpos(0);
|
||||
uint32 criteriaId = 0;
|
||||
uint64 counter = 0;
|
||||
packet >> criteriaId;
|
||||
packet >> counter;
|
||||
QueueCriteriaUpdate(player->GetGUID(), criteriaId, counter);
|
||||
}
|
||||
};
|
||||
|
||||
class InitiativeCriteriaWorldScript final : public WorldScript
|
||||
{
|
||||
public:
|
||||
InitiativeCriteriaWorldScript() : WorldScript("InitiativeCriteriaWorldScript") { }
|
||||
|
||||
void OnUpdate(uint32 /*diff*/) override
|
||||
{
|
||||
ProcessPendingCriteriaUpdates();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void RegisterInitiativeCriteriaBridge()
|
||||
{
|
||||
new InitiativeCriteriaPacketScript();
|
||||
new InitiativeCriteriaWorldScript();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_INITIATIVE_CRITERIA_H
|
||||
#define TRINITYCORE_HOUSING_INITIATIVE_CRITERIA_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
TC_GAME_API void RegisterInitiativeCriteriaBridge();
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_INITIATIVE_CRITERIA_H
|
||||
@@ -1,564 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingInitiativeProgress.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "GameTime.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingInitiativePackets.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "WorldSession.h"
|
||||
#include <limits>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr uint32 MaxInitiativeActivitiesPerNeighborhood = 1024;
|
||||
constexpr uint32 MaxInitiativeLogEntriesPerNeighborhood = 10000;
|
||||
|
||||
struct InitiativeActivityDefinition
|
||||
{
|
||||
uint32 ActivityId = 0;
|
||||
uint64 Target = 0;
|
||||
};
|
||||
|
||||
bool PlayerOwnsNeighborhood(Player const* player, ObjectGuid const& neighborhoodGuid)
|
||||
{
|
||||
if (!player || !IsNeighborhoodGuid(neighborhoodGuid))
|
||||
return false;
|
||||
|
||||
for (HouseState const& house : sHousingMgr.LoadOwnedHouses(player))
|
||||
if (house.NeighborhoodGuid == neighborhoodGuid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EnsureRootState(ObjectGuid const& neighborhoodGuid)
|
||||
{
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_neighborhood_initiative "
|
||||
"(NeighborhoodGuid, InitiativeID, State, Progress, RewardTier, StartedAt, EndsAt) "
|
||||
"VALUES ('{}', 0, 0, 0, 0, NULL, NULL) "
|
||||
"ON DUPLICATE KEY UPDATE NeighborhoodGuid = VALUES(NeighborhoodGuid)",
|
||||
neighborhoodGuid.ToString());
|
||||
|
||||
QueryResult root = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_neighborhood_initiative WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
return bool(root);
|
||||
}
|
||||
|
||||
bool RefreshExpirationState(ObjectGuid const& neighborhoodGuid, uint32 initiativeId, uint64 endsAt, bool& expired)
|
||||
{
|
||||
expired = false;
|
||||
if (!initiativeId)
|
||||
return true;
|
||||
|
||||
QueryResult persisted = CharacterDatabase.PQuery(
|
||||
"SELECT ExpiredAt FROM housing_initiative_expiration "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
|
||||
uint64 const now = uint64(GameTime::GetGameTime());
|
||||
if (!persisted && (!endsAt || endsAt > now))
|
||||
return true;
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_expiration (NeighborhoodGuid, InitiativeID, ExpiredAt) "
|
||||
"VALUES ('{}', {}, {}) "
|
||||
"ON DUPLICATE KEY UPDATE ExpiredAt = IF(ExpiredAt = 0, VALUES(ExpiredAt), ExpiredAt)",
|
||||
neighborhoodGuid.ToString(), initiativeId, now);
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_player_state WHERE ActiveNeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_criteria_checkpoint "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {}",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
QueryResult verification = CharacterDatabase.PQuery(
|
||||
"SELECT ExpiredAt FROM housing_initiative_expiration "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
QueryResult staleActiveSelection = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_player_state WHERE ActiveNeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
QueryResult staleCheckpoint = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_criteria_checkpoint "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
|
||||
if (!verification || !verification->Fetch()[0].GetUInt64() || staleActiveSelection || staleCheckpoint)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Failed to finalize Initiative expiration for neighborhood {} initiative {}",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
return false;
|
||||
}
|
||||
|
||||
expired = true;
|
||||
if (!persisted)
|
||||
TC_LOG_DEBUG("housing", "Initiative {} expired for neighborhood {}; active selection and criteria checkpoints were cleared",
|
||||
initiativeId, neighborhoodGuid.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
InitiativeActivityProgressState const* FindActivity(InitiativeProgressSnapshot const& snapshot, uint32 activityId)
|
||||
{
|
||||
for (InitiativeActivityProgressState const& activity : snapshot.Activities)
|
||||
if (activity.ActivityId == activityId)
|
||||
return &activity;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
InitiativeMemberProgressState const* FindPlayerActivity(InitiativeProgressSnapshot const& snapshot, uint32 activityId)
|
||||
{
|
||||
for (InitiativeMemberProgressState const& activity : snapshot.PlayerActivities)
|
||||
if (activity.ActivityId == activityId)
|
||||
return &activity;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool LoadActivityDefinitions(uint32 initiativeId, std::vector<InitiativeActivityDefinition>& definitions)
|
||||
{
|
||||
definitions.clear();
|
||||
if (!initiativeId)
|
||||
return false;
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT ActivityID, Target FROM housing_initiative_activity_definition "
|
||||
"WHERE InitiativeID = {} AND `Enabled` = 1 ORDER BY ActivityID LIMIT {}",
|
||||
initiativeId, MaxInitiativeActivitiesPerNeighborhood + 1);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
do
|
||||
{
|
||||
if (definitions.size() >= MaxInitiativeActivitiesPerNeighborhood)
|
||||
return false;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
InitiativeActivityDefinition definition;
|
||||
definition.ActivityId = fields[0].GetUInt32();
|
||||
definition.Target = fields[1].GetUInt64();
|
||||
if (!definition.ActivityId || !definition.Target)
|
||||
return false;
|
||||
|
||||
if (!definitions.empty() && definitions.back().ActivityId == definition.ActivityId)
|
||||
return false;
|
||||
|
||||
definitions.push_back(definition);
|
||||
} while (result->NextRow());
|
||||
|
||||
return !definitions.empty();
|
||||
}
|
||||
|
||||
InitiativeActivityDefinition const* FindActivityDefinition(
|
||||
std::vector<InitiativeActivityDefinition> const& definitions, uint32 activityId)
|
||||
{
|
||||
for (InitiativeActivityDefinition const& definition : definitions)
|
||||
if (definition.ActivityId == activityId)
|
||||
return &definition;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EnsureProgressGeneration(ObjectGuid const& neighborhoodGuid, uint32 initiativeId)
|
||||
{
|
||||
QueryResult generation = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID FROM housing_initiative_progress_generation "
|
||||
"WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
|
||||
if (!generation)
|
||||
{
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_initiative_progress_generation (NeighborhoodGuid, InitiativeID) "
|
||||
"VALUES ('{}', {}) ON DUPLICATE KEY UPDATE InitiativeID = VALUES(InitiativeID)",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
}
|
||||
else if (generation->Fetch()[0].GetUInt32() != initiativeId)
|
||||
{
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_activity_progress WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_member_progress WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_activity_log WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_progress_generation (NeighborhoodGuid, InitiativeID) "
|
||||
"VALUES ('{}', {}) ON DUPLICATE KEY UPDATE InitiativeID = VALUES(InitiativeID)",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
}
|
||||
|
||||
QueryResult verification = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID FROM housing_initiative_progress_generation "
|
||||
"WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
return verification && verification->Fetch()[0].GetUInt32() == initiativeId;
|
||||
}
|
||||
|
||||
bool LoadCompletionState(ObjectGuid const& neighborhoodGuid, uint32 initiativeId,
|
||||
bool& completed, bool& rewardAvailable)
|
||||
{
|
||||
completed = false;
|
||||
rewardAvailable = false;
|
||||
if (!initiativeId)
|
||||
return true;
|
||||
|
||||
QueryResult completion = CharacterDatabase.PQuery(
|
||||
"SELECT RewardAvailable, RewardClaimed FROM housing_initiative_completion "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
if (!completion)
|
||||
return true;
|
||||
|
||||
Field* fields = completion->Fetch();
|
||||
uint8 const available = fields[0].GetUInt8();
|
||||
uint8 const claimed = fields[1].GetUInt8();
|
||||
if (available > 1 || claimed > 1)
|
||||
return false;
|
||||
|
||||
completed = true;
|
||||
rewardAvailable = available != 0 && claimed == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AllDefinedActivitiesComplete(InitiativeProgressSnapshot const& snapshot,
|
||||
std::vector<InitiativeActivityDefinition> const& definitions, uint32 updatedActivityId,
|
||||
bool updatedActivityCompleted)
|
||||
{
|
||||
if (definitions.empty())
|
||||
return false;
|
||||
|
||||
for (InitiativeActivityDefinition const& definition : definitions)
|
||||
{
|
||||
if (definition.ActivityId == updatedActivityId)
|
||||
{
|
||||
if (!updatedActivityCompleted)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
InitiativeActivityProgressState const* progress = FindActivity(snapshot, definition.ActivityId);
|
||||
if (!progress || !progress->Completed || progress->Target != definition.Target)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendCompletionNotifications(Player const* player, InitiativeProgressTransition const& transition)
|
||||
{
|
||||
if (!player || !player->GetSession() || !transition.InitiativeId)
|
||||
return;
|
||||
|
||||
if (transition.ActivityCompleted)
|
||||
{
|
||||
WorldPackets::Housing::InitiativeTaskComplete packet;
|
||||
packet.InitiativeID = transition.InitiativeId;
|
||||
packet.TaskID = transition.ActivityId;
|
||||
player->GetSession()->SendPacket(packet.Write());
|
||||
}
|
||||
|
||||
if (transition.InitiativeCompleted)
|
||||
{
|
||||
WorldPackets::Housing::InitiativeComplete packet;
|
||||
packet.InitiativeID = transition.InitiativeId;
|
||||
player->GetSession()->SendPacket(packet.Write());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InitiativeProgressResult LoadInitiativeProgress(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, InitiativeProgressSnapshot& snapshot)
|
||||
{
|
||||
snapshot = {};
|
||||
if (!player || !IsNeighborhoodGuid(neighborhoodGuid))
|
||||
return InitiativeProgressResult::InvalidRequest;
|
||||
|
||||
if (!PlayerOwnsNeighborhood(player, neighborhoodGuid))
|
||||
return InitiativeProgressResult::PermissionDenied;
|
||||
|
||||
if (!EnsureRootState(neighborhoodGuid))
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
QueryResult root = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID, State, Progress, RewardTier, COALESCE(StartedAt, 0), COALESCE(EndsAt, 0) "
|
||||
"FROM housing_neighborhood_initiative WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!root)
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
Field* rootFields = root->Fetch();
|
||||
snapshot.NeighborhoodGuid = neighborhoodGuid;
|
||||
snapshot.InitiativeId = rootFields[0].GetUInt32();
|
||||
snapshot.State = rootFields[1].GetUInt8();
|
||||
snapshot.Progress = rootFields[2].GetUInt64();
|
||||
snapshot.RewardTier = rootFields[3].GetUInt16();
|
||||
snapshot.StartedAt = rootFields[4].GetUInt64();
|
||||
snapshot.EndsAt = rootFields[5].GetUInt64();
|
||||
|
||||
if (!RefreshExpirationState(neighborhoodGuid, snapshot.InitiativeId, snapshot.EndsAt, snapshot.Expired))
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
if (!EnsureProgressGeneration(neighborhoodGuid, snapshot.InitiativeId))
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
if (!LoadCompletionState(neighborhoodGuid, snapshot.InitiativeId,
|
||||
snapshot.Completed, snapshot.RewardAvailable))
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
QueryResult activities = CharacterDatabase.PQuery(
|
||||
"SELECT ActivityID, Progress, Target, Completed FROM housing_initiative_activity_progress "
|
||||
"WHERE NeighborhoodGuid = '{}' ORDER BY ActivityID LIMIT {}",
|
||||
neighborhoodGuid.ToString(), MaxInitiativeActivitiesPerNeighborhood + 1);
|
||||
if (activities)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (snapshot.Activities.size() >= MaxInitiativeActivitiesPerNeighborhood)
|
||||
return InitiativeProgressResult::SafetyLimitExceeded;
|
||||
|
||||
Field* fields = activities->Fetch();
|
||||
InitiativeActivityProgressState activity;
|
||||
activity.ActivityId = fields[0].GetUInt32();
|
||||
activity.Progress = fields[1].GetUInt64();
|
||||
activity.Target = fields[2].GetUInt64();
|
||||
uint8 const completed = fields[3].GetUInt8();
|
||||
if (!activity.ActivityId || !activity.Target || completed > 1 || activity.Progress > activity.Target)
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
activity.Completed = completed != 0;
|
||||
if (activity.Completed != (activity.Progress >= activity.Target))
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
snapshot.Activities.push_back(activity);
|
||||
} while (activities->NextRow());
|
||||
}
|
||||
|
||||
uint64 const characterGuid = player->GetGUID().GetCounter();
|
||||
QueryResult memberProgress = CharacterDatabase.PQuery(
|
||||
"SELECT ActivityID, Progress FROM housing_initiative_member_progress "
|
||||
"WHERE NeighborhoodGuid = '{}' AND CharacterGuid = {} ORDER BY ActivityID LIMIT {}",
|
||||
neighborhoodGuid.ToString(), characterGuid, MaxInitiativeActivitiesPerNeighborhood + 1);
|
||||
if (memberProgress)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (snapshot.PlayerActivities.size() >= MaxInitiativeActivitiesPerNeighborhood)
|
||||
return InitiativeProgressResult::SafetyLimitExceeded;
|
||||
|
||||
Field* fields = memberProgress->Fetch();
|
||||
InitiativeMemberProgressState activity;
|
||||
activity.ActivityId = fields[0].GetUInt32();
|
||||
activity.Progress = fields[1].GetUInt64();
|
||||
if (!activity.ActivityId)
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
snapshot.PlayerActivities.push_back(activity);
|
||||
} while (memberProgress->NextRow());
|
||||
}
|
||||
|
||||
return InitiativeProgressResult::Success;
|
||||
}
|
||||
|
||||
InitiativeProgressResult ApplyInitiativeActivityProgress(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, uint32 activityId, uint64 delta,
|
||||
InitiativeProgressSnapshot* snapshot, InitiativeProgressTransition* transition)
|
||||
{
|
||||
if (transition)
|
||||
*transition = {};
|
||||
|
||||
if (!player || !IsNeighborhoodGuid(neighborhoodGuid) || !activityId || !delta)
|
||||
return InitiativeProgressResult::InvalidRequest;
|
||||
|
||||
InitiativeProgressSnapshot current;
|
||||
InitiativeProgressResult const loadResult = LoadInitiativeProgress(player, neighborhoodGuid, current);
|
||||
if (loadResult != InitiativeProgressResult::Success)
|
||||
return loadResult;
|
||||
|
||||
if (current.Expired)
|
||||
{
|
||||
if (snapshot)
|
||||
*snapshot = current;
|
||||
return InitiativeProgressResult::Expired;
|
||||
}
|
||||
|
||||
if (!current.InitiativeId)
|
||||
return InitiativeProgressResult::InvalidRequest;
|
||||
|
||||
std::vector<InitiativeActivityDefinition> definitions;
|
||||
if (!LoadActivityDefinitions(current.InitiativeId, definitions))
|
||||
return InitiativeProgressResult::InvalidRequest;
|
||||
|
||||
InitiativeActivityDefinition const* definition = FindActivityDefinition(definitions, activityId);
|
||||
if (!definition)
|
||||
return InitiativeProgressResult::InvalidRequest;
|
||||
|
||||
InitiativeActivityProgressState const* activity = FindActivity(current, activityId);
|
||||
if (!activity && current.Activities.size() >= MaxInitiativeActivitiesPerNeighborhood)
|
||||
return InitiativeProgressResult::SafetyLimitExceeded;
|
||||
if (activity && activity->Target != definition->Target)
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
uint64 const currentActivityProgress = activity ? activity->Progress : 0;
|
||||
if (currentActivityProgress >= definition->Target)
|
||||
{
|
||||
if (snapshot)
|
||||
*snapshot = current;
|
||||
return InitiativeProgressResult::Success;
|
||||
}
|
||||
|
||||
uint64 const remaining = definition->Target - currentActivityProgress;
|
||||
uint64 const appliedDelta = delta < remaining ? delta : remaining;
|
||||
if (!appliedDelta)
|
||||
return InitiativeProgressResult::Success;
|
||||
|
||||
if (appliedDelta > static_cast<uint64>(std::numeric_limits<int64>::max()))
|
||||
return InitiativeProgressResult::SafetyLimitExceeded;
|
||||
|
||||
InitiativeMemberProgressState const* memberActivity = FindPlayerActivity(current, activityId);
|
||||
uint64 const currentMemberProgress = memberActivity ? memberActivity->Progress : 0;
|
||||
if (currentMemberProgress > std::numeric_limits<uint64>::max() - appliedDelta ||
|
||||
current.Progress > std::numeric_limits<uint64>::max() - appliedDelta)
|
||||
return InitiativeProgressResult::SafetyLimitExceeded;
|
||||
|
||||
uint64 const newActivityProgress = currentActivityProgress + appliedDelta;
|
||||
uint64 const newMemberProgress = currentMemberProgress + appliedDelta;
|
||||
uint64 const newInitiativeProgress = current.Progress + appliedDelta;
|
||||
bool const activityCompleted = newActivityProgress >= definition->Target;
|
||||
bool const activityJustCompleted = activityCompleted && (!activity || !activity->Completed);
|
||||
bool const initiativeJustCompleted = !current.Completed &&
|
||||
AllDefinedActivitiesComplete(current, definitions, activityId, activityCompleted);
|
||||
uint64 const characterGuid = player->GetGUID().GetCounter();
|
||||
|
||||
QueryResult logCountResult = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*) FROM housing_initiative_activity_log WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!logCountResult)
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
uint64 const logCount = logCountResult->Fetch()[0].GetUInt64();
|
||||
uint64 const rowsToPrune = logCount >= MaxInitiativeLogEntriesPerNeighborhood
|
||||
? logCount - MaxInitiativeLogEntriesPerNeighborhood + 1
|
||||
: 0;
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_activity_progress "
|
||||
"(NeighborhoodGuid, ActivityID, Progress, Target, Completed) "
|
||||
"VALUES ('{}', {}, {}, {}, {}) "
|
||||
"ON DUPLICATE KEY UPDATE Progress = VALUES(Progress), Target = VALUES(Target), Completed = VALUES(Completed)",
|
||||
neighborhoodGuid.ToString(), activityId, newActivityProgress, definition->Target, activityCompleted ? 1 : 0);
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_member_progress "
|
||||
"(NeighborhoodGuid, CharacterGuid, ActivityID, Progress) VALUES ('{}', {}, {}, {}) "
|
||||
"ON DUPLICATE KEY UPDATE Progress = VALUES(Progress)",
|
||||
neighborhoodGuid.ToString(), characterGuid, activityId, newMemberProgress);
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_neighborhood_initiative SET Progress = {} WHERE NeighborhoodGuid = '{}'",
|
||||
newInitiativeProgress, neighborhoodGuid.ToString());
|
||||
|
||||
if (rowsToPrune)
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_activity_log WHERE NeighborhoodGuid = '{}' "
|
||||
"ORDER BY ID ASC LIMIT {}",
|
||||
neighborhoodGuid.ToString(), rowsToPrune);
|
||||
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_activity_log "
|
||||
"(NeighborhoodGuid, CharacterGuid, ActivityID, ProgressDelta, CreatedAt) "
|
||||
"VALUES ('{}', {}, {}, {}, UNIX_TIMESTAMP())",
|
||||
neighborhoodGuid.ToString(), characterGuid, activityId, appliedDelta);
|
||||
|
||||
if (initiativeJustCompleted)
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_completion "
|
||||
"(NeighborhoodGuid, InitiativeID, CompletedAt, RewardAvailable, RewardClaimed) "
|
||||
"VALUES ('{}', {}, UNIX_TIMESTAMP(), 1, 0) "
|
||||
"ON DUPLICATE KEY UPDATE CompletedAt = IF(CompletedAt = 0, VALUES(CompletedAt), CompletedAt), "
|
||||
"RewardAvailable = IF(RewardClaimed = 0, 1, RewardAvailable)",
|
||||
neighborhoodGuid.ToString(), current.InitiativeId);
|
||||
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
QueryResult verification = CharacterDatabase.PQuery(
|
||||
"SELECT a.Progress, a.Target, a.Completed, m.Progress, n.Progress "
|
||||
"FROM housing_initiative_activity_progress a "
|
||||
"JOIN housing_initiative_member_progress m ON m.NeighborhoodGuid = a.NeighborhoodGuid "
|
||||
"AND m.ActivityID = a.ActivityID AND m.CharacterGuid = {} "
|
||||
"JOIN housing_neighborhood_initiative n ON n.NeighborhoodGuid = a.NeighborhoodGuid "
|
||||
"WHERE a.NeighborhoodGuid = '{}' AND a.ActivityID = {} LIMIT 1",
|
||||
characterGuid, neighborhoodGuid.ToString(), activityId);
|
||||
if (!verification)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Initiative progress read-back failed for neighborhood {} activity {}",
|
||||
neighborhoodGuid.ToString(), activityId);
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
}
|
||||
|
||||
Field* verified = verification->Fetch();
|
||||
if (verified[0].GetUInt64() != newActivityProgress || verified[1].GetUInt64() != definition->Target ||
|
||||
(verified[2].GetUInt8() != 0) != activityCompleted || verified[3].GetUInt64() != newMemberProgress ||
|
||||
verified[4].GetUInt64() != newInitiativeProgress)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Initiative progress verification mismatch for neighborhood {} activity {}",
|
||||
neighborhoodGuid.ToString(), activityId);
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
}
|
||||
|
||||
if (initiativeJustCompleted)
|
||||
{
|
||||
QueryResult completion = CharacterDatabase.PQuery(
|
||||
"SELECT RewardAvailable, RewardClaimed FROM housing_initiative_completion "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), current.InitiativeId);
|
||||
if (!completion)
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
|
||||
Field* completionFields = completion->Fetch();
|
||||
if (completionFields[0].GetUInt8() != 1 || completionFields[1].GetUInt8() != 0)
|
||||
return InitiativeProgressResult::PersistenceError;
|
||||
}
|
||||
|
||||
InitiativeProgressTransition appliedTransition;
|
||||
appliedTransition.InitiativeId = current.InitiativeId;
|
||||
appliedTransition.ActivityId = activityId;
|
||||
appliedTransition.ActivityCompleted = activityJustCompleted;
|
||||
appliedTransition.InitiativeCompleted = initiativeJustCompleted;
|
||||
appliedTransition.RewardBecameAvailable = initiativeJustCompleted;
|
||||
|
||||
if (snapshot)
|
||||
{
|
||||
InitiativeProgressResult const reload = LoadInitiativeProgress(player, neighborhoodGuid, *snapshot);
|
||||
if (reload != InitiativeProgressResult::Success)
|
||||
return reload;
|
||||
}
|
||||
|
||||
if (transition)
|
||||
*transition = appliedTransition;
|
||||
|
||||
SendCompletionNotifications(player, appliedTransition);
|
||||
|
||||
if (appliedTransition.RewardBecameAvailable)
|
||||
TC_LOG_DEBUG("housing",
|
||||
"Initiative {} reward became available for neighborhood {}; reward GUID publication remains deferred",
|
||||
appliedTransition.InitiativeId, neighborhoodGuid.ToString());
|
||||
|
||||
return InitiativeProgressResult::Success;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_INITIATIVE_PROGRESS_H
|
||||
#define TRINITYCORE_HOUSING_INITIATIVE_PROGRESS_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <vector>
|
||||
|
||||
class Player;
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
enum class InitiativeProgressResult : uint8
|
||||
{
|
||||
Success = 0,
|
||||
InvalidRequest,
|
||||
PermissionDenied,
|
||||
SafetyLimitExceeded,
|
||||
PersistenceError,
|
||||
Expired
|
||||
};
|
||||
|
||||
struct InitiativeActivityProgressState
|
||||
{
|
||||
uint32 ActivityId = 0;
|
||||
uint64 Progress = 0;
|
||||
uint64 Target = 0;
|
||||
bool Completed = false;
|
||||
};
|
||||
|
||||
struct InitiativeMemberProgressState
|
||||
{
|
||||
uint32 ActivityId = 0;
|
||||
uint64 Progress = 0;
|
||||
};
|
||||
|
||||
struct InitiativeProgressSnapshot
|
||||
{
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint32 InitiativeId = 0;
|
||||
uint8 State = 0;
|
||||
uint64 Progress = 0;
|
||||
uint16 RewardTier = 0;
|
||||
uint64 StartedAt = 0;
|
||||
uint64 EndsAt = 0;
|
||||
bool Completed = false;
|
||||
bool RewardAvailable = false;
|
||||
bool Expired = false;
|
||||
std::vector<InitiativeActivityProgressState> Activities;
|
||||
std::vector<InitiativeMemberProgressState> PlayerActivities;
|
||||
};
|
||||
|
||||
struct InitiativeProgressTransition
|
||||
{
|
||||
uint32 InitiativeId = 0;
|
||||
uint32 ActivityId = 0;
|
||||
bool ActivityCompleted = false;
|
||||
bool InitiativeCompleted = false;
|
||||
bool RewardBecameAvailable = false;
|
||||
};
|
||||
|
||||
TC_GAME_API InitiativeProgressResult LoadInitiativeProgress(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, InitiativeProgressSnapshot& snapshot);
|
||||
|
||||
TC_GAME_API InitiativeProgressResult ApplyInitiativeActivityProgress(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, uint32 activityId, uint64 delta,
|
||||
InitiativeProgressSnapshot* snapshot = nullptr, InitiativeProgressTransition* transition = nullptr);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_INITIATIVE_PROGRESS_H
|
||||
@@ -1,560 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingInitiativeReward.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "GameTime.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr uint32 MaxArchivedRewardsPerNeighborhood = 32;
|
||||
constexpr uint32 MaxRewardEntriesPerClaim = 64;
|
||||
constexpr uint8 RewardClaimPending = 0;
|
||||
constexpr uint8 RewardClaimCompleted = 1;
|
||||
constexpr uint8 RewardGrantPending = 0;
|
||||
constexpr uint8 RewardGrantDelivered = 1;
|
||||
|
||||
struct ArchivedRewardState
|
||||
{
|
||||
uint32 InitiativeId = 0;
|
||||
uint16 RewardTier = 0;
|
||||
uint64 CompletedAt = 0;
|
||||
uint64 ExpiredAt = 0;
|
||||
bool RewardAvailable = false;
|
||||
bool RewardClaimed = false;
|
||||
};
|
||||
|
||||
struct RewardClaimState
|
||||
{
|
||||
bool Exists = false;
|
||||
uint64 ClaimId = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint64 GenerationId = 0;
|
||||
uint32 InitiativeId = 0;
|
||||
uint64 CharacterGuid = 0;
|
||||
uint8 State = RewardClaimPending;
|
||||
uint64 CompletedAt = 0;
|
||||
};
|
||||
|
||||
struct RewardCatalogEntry
|
||||
{
|
||||
uint32 EntryId = 0;
|
||||
InitiativeRewardGrantType Type = InitiativeRewardGrantType::Item;
|
||||
uint32 AssetId = 0;
|
||||
uint32 Quantity = 0;
|
||||
};
|
||||
|
||||
std::mutex InitiativeRewardClaimMutex;
|
||||
|
||||
bool PlayerOwnsNeighborhood(Player const* player, ObjectGuid const& neighborhoodGuid)
|
||||
{
|
||||
if (!player || !IsNeighborhoodGuid(neighborhoodGuid))
|
||||
return false;
|
||||
|
||||
for (HouseState const& house : sHousingMgr.LoadOwnedHouses(player))
|
||||
if (house.NeighborhoodGuid == neighborhoodGuid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsSupportedGrantType(uint8 value)
|
||||
{
|
||||
return value == uint8(InitiativeRewardGrantType::Item) ||
|
||||
value == uint8(InitiativeRewardGrantType::Currency);
|
||||
}
|
||||
|
||||
bool LoadArchivedReward(ObjectGuid const& neighborhoodGuid, uint64 generationId, ArchivedRewardState& state)
|
||||
{
|
||||
state = {};
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID, RewardTier, CompletedAt, ExpiredAt, RewardAvailable, RewardClaimed "
|
||||
"FROM housing_initiative_history WHERE NeighborhoodGuid = '{}' AND GenerationID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), generationId);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
uint8 const rewardAvailable = fields[4].GetUInt8();
|
||||
uint8 const rewardClaimed = fields[5].GetUInt8();
|
||||
if (rewardAvailable > 1 || rewardClaimed > 1)
|
||||
return false;
|
||||
|
||||
state.InitiativeId = fields[0].GetUInt32();
|
||||
state.RewardTier = fields[1].GetUInt16();
|
||||
state.CompletedAt = fields[2].GetUInt64();
|
||||
state.ExpiredAt = fields[3].GetUInt64();
|
||||
state.RewardAvailable = rewardAvailable != 0;
|
||||
state.RewardClaimed = rewardClaimed != 0;
|
||||
return state.InitiativeId != 0;
|
||||
}
|
||||
|
||||
bool LoadClaimByGeneration(ObjectGuid const& neighborhoodGuid, uint64 generationId, RewardClaimState& state)
|
||||
{
|
||||
state = {};
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT ClaimID, InitiativeID, CharacterGuid, State, CompletedAt "
|
||||
"FROM housing_initiative_reward_claim "
|
||||
"WHERE NeighborhoodGuid = '{}' AND GenerationID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), generationId);
|
||||
if (!result)
|
||||
return true;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
state.Exists = true;
|
||||
state.ClaimId = fields[0].GetUInt64();
|
||||
state.NeighborhoodGuid = neighborhoodGuid;
|
||||
state.GenerationId = generationId;
|
||||
state.InitiativeId = fields[1].GetUInt32();
|
||||
state.CharacterGuid = fields[2].GetUInt64();
|
||||
state.State = fields[3].GetUInt8();
|
||||
state.CompletedAt = fields[4].GetUInt64();
|
||||
return state.ClaimId != 0 && state.InitiativeId != 0 &&
|
||||
(state.State == RewardClaimPending || state.State == RewardClaimCompleted);
|
||||
}
|
||||
|
||||
bool LoadClaimById(uint64 claimId, RewardClaimState& state)
|
||||
{
|
||||
state = {};
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT NeighborhoodGuid, GenerationID, InitiativeID, CharacterGuid, State, CompletedAt "
|
||||
"FROM housing_initiative_reward_claim WHERE ClaimID = {} LIMIT 1",
|
||||
claimId);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
ObjectGuid const neighborhoodGuid = ObjectGuid::FromString(fields[0].GetString());
|
||||
uint8 const claimState = fields[4].GetUInt8();
|
||||
if (!IsNeighborhoodGuid(neighborhoodGuid) ||
|
||||
(claimState != RewardClaimPending && claimState != RewardClaimCompleted))
|
||||
return false;
|
||||
|
||||
state.Exists = true;
|
||||
state.ClaimId = claimId;
|
||||
state.NeighborhoodGuid = neighborhoodGuid;
|
||||
state.GenerationId = fields[1].GetUInt64();
|
||||
state.InitiativeId = fields[2].GetUInt32();
|
||||
state.CharacterGuid = fields[3].GetUInt64();
|
||||
state.State = claimState;
|
||||
state.CompletedAt = fields[5].GetUInt64();
|
||||
return state.GenerationId != 0 && state.InitiativeId != 0 && state.CharacterGuid != 0;
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult LoadCatalogEntries(uint16 rewardTier, std::vector<RewardCatalogEntry>& entries)
|
||||
{
|
||||
entries.clear();
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT EntryID, GrantType, AssetID, Quantity FROM housing_initiative_reward_catalog "
|
||||
"WHERE RewardTier = {} AND `Enabled` = 1 ORDER BY EntryID LIMIT {}",
|
||||
uint32(rewardTier), MaxRewardEntriesPerClaim + 1);
|
||||
if (!result)
|
||||
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||
|
||||
do
|
||||
{
|
||||
if (entries.size() >= MaxRewardEntriesPerClaim)
|
||||
return InitiativeRewardClaimResult::SafetyLimitExceeded;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
uint8 const grantType = fields[1].GetUInt8();
|
||||
RewardCatalogEntry entry;
|
||||
entry.EntryId = fields[0].GetUInt32();
|
||||
entry.AssetId = fields[2].GetUInt32();
|
||||
entry.Quantity = fields[3].GetUInt32();
|
||||
|
||||
if (!entry.EntryId || !IsSupportedGrantType(grantType) || !entry.AssetId || !entry.Quantity ||
|
||||
entry.Quantity > uint32(std::numeric_limits<int32>::max()))
|
||||
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||
|
||||
entry.Type = InitiativeRewardGrantType(grantType);
|
||||
entries.push_back(entry);
|
||||
} while (result->NextRow());
|
||||
|
||||
return entries.empty() ? InitiativeRewardClaimResult::InvalidCatalog : InitiativeRewardClaimResult::Success;
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult LoadDeliverySnapshot(uint64 claimId, std::vector<InitiativeRewardGrant>& grants)
|
||||
{
|
||||
grants.clear();
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT EntryID, GrantType, AssetID, Quantity, State "
|
||||
"FROM housing_initiative_reward_delivery WHERE ClaimID = {} ORDER BY EntryID LIMIT {}",
|
||||
claimId, MaxRewardEntriesPerClaim + 1);
|
||||
if (!result)
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
|
||||
do
|
||||
{
|
||||
if (grants.size() >= MaxRewardEntriesPerClaim)
|
||||
return InitiativeRewardClaimResult::SafetyLimitExceeded;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
uint8 const grantType = fields[1].GetUInt8();
|
||||
uint8 const state = fields[4].GetUInt8();
|
||||
InitiativeRewardGrant grant;
|
||||
grant.EntryId = fields[0].GetUInt32();
|
||||
grant.AssetId = fields[2].GetUInt32();
|
||||
grant.Quantity = fields[3].GetUInt32();
|
||||
|
||||
if (!grant.EntryId || !IsSupportedGrantType(grantType) || !grant.AssetId || !grant.Quantity ||
|
||||
grant.Quantity > uint32(std::numeric_limits<int32>::max()) ||
|
||||
(state != RewardGrantPending && state != RewardGrantDelivered))
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
grant.Type = InitiativeRewardGrantType(grantType);
|
||||
grant.Delivered = state == RewardGrantDelivered;
|
||||
grants.push_back(grant);
|
||||
} while (result->NextRow());
|
||||
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult PopulateReservation(Player const* player, ArchivedRewardState const& reward,
|
||||
RewardClaimState const& claim, InitiativeRewardClaimReservation& reservation)
|
||||
{
|
||||
if (!player || !claim.Exists || claim.InitiativeId != reward.InitiativeId)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
if (claim.State == RewardClaimCompleted)
|
||||
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||
|
||||
if (claim.CharacterGuid != player->GetGUID().GetCounter())
|
||||
return InitiativeRewardClaimResult::ClaimInProgress;
|
||||
|
||||
reservation.ClaimId = claim.ClaimId;
|
||||
reservation.NeighborhoodGuid = claim.NeighborhoodGuid;
|
||||
reservation.GenerationId = claim.GenerationId;
|
||||
reservation.InitiativeId = claim.InitiativeId;
|
||||
reservation.RewardTier = reward.RewardTier;
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult LoadArchivedInitiativeRewards(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, std::vector<ArchivedInitiativeReward>& rewards)
|
||||
{
|
||||
rewards.clear();
|
||||
if (!player || !IsNeighborhoodGuid(neighborhoodGuid))
|
||||
return InitiativeRewardClaimResult::InvalidRequest;
|
||||
|
||||
if (!PlayerOwnsNeighborhood(player, neighborhoodGuid))
|
||||
return InitiativeRewardClaimResult::PermissionDenied;
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT h.GenerationID, h.InitiativeID, h.RewardTier, h.CompletedAt, h.ExpiredAt, "
|
||||
"COALESCE(c.ClaimID, 0), COALESCE(c.State, 255) "
|
||||
"FROM housing_initiative_history h "
|
||||
"LEFT JOIN housing_initiative_reward_claim c ON c.NeighborhoodGuid = h.NeighborhoodGuid "
|
||||
"AND c.GenerationID = h.GenerationID "
|
||||
"WHERE h.NeighborhoodGuid = '{}' AND h.RewardAvailable = 1 AND h.RewardClaimed = 0 "
|
||||
"ORDER BY h.GenerationID DESC LIMIT {}",
|
||||
neighborhoodGuid.ToString(), MaxArchivedRewardsPerNeighborhood + 1);
|
||||
if (!result)
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
|
||||
do
|
||||
{
|
||||
if (rewards.size() >= MaxArchivedRewardsPerNeighborhood)
|
||||
return InitiativeRewardClaimResult::SafetyLimitExceeded;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
ArchivedInitiativeReward reward;
|
||||
reward.GenerationId = fields[0].GetUInt64();
|
||||
reward.InitiativeId = fields[1].GetUInt32();
|
||||
reward.RewardTier = fields[2].GetUInt16();
|
||||
reward.CompletedAt = fields[3].GetUInt64();
|
||||
reward.ExpiredAt = fields[4].GetUInt64();
|
||||
reward.ClaimId = fields[5].GetUInt64();
|
||||
uint8 const claimState = fields[6].GetUInt8();
|
||||
|
||||
if (!reward.GenerationId || !reward.InitiativeId)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
if (reward.ClaimId)
|
||||
{
|
||||
if (claimState == RewardClaimCompleted)
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Archived Initiative reward {} generation {} has a completed claim but remains unclaimed in history",
|
||||
reward.InitiativeId, reward.GenerationId);
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
}
|
||||
|
||||
if (claimState != RewardClaimPending)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
reward.ClaimPending = true;
|
||||
}
|
||||
else if (claimState != 255)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
rewards.push_back(reward);
|
||||
} while (result->NextRow());
|
||||
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult ReserveArchivedInitiativeReward(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, uint64 generationId, InitiativeRewardClaimReservation& reservation)
|
||||
{
|
||||
reservation = {};
|
||||
if (!player || !IsNeighborhoodGuid(neighborhoodGuid) || !generationId)
|
||||
return InitiativeRewardClaimResult::InvalidRequest;
|
||||
|
||||
if (!PlayerOwnsNeighborhood(player, neighborhoodGuid))
|
||||
return InitiativeRewardClaimResult::PermissionDenied;
|
||||
|
||||
std::lock_guard<std::mutex> lock(InitiativeRewardClaimMutex);
|
||||
|
||||
ArchivedRewardState reward;
|
||||
if (!LoadArchivedReward(neighborhoodGuid, generationId, reward))
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
|
||||
if (!reward.RewardAvailable)
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
if (reward.RewardClaimed)
|
||||
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||
|
||||
RewardClaimState claim;
|
||||
if (!LoadClaimByGeneration(neighborhoodGuid, generationId, claim))
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
if (claim.Exists)
|
||||
return PopulateReservation(player, reward, claim, reservation);
|
||||
|
||||
uint64 const now = uint64(GameTime::GetGameTime());
|
||||
uint64 const characterGuid = player->GetGUID().GetCounter();
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_initiative_reward_claim "
|
||||
"(NeighborhoodGuid, GenerationID, InitiativeID, CharacterGuid, State, CreatedAt, UpdatedAt, CompletedAt) "
|
||||
"SELECT NeighborhoodGuid, GenerationID, InitiativeID, {}, {}, {}, {}, 0 "
|
||||
"FROM housing_initiative_history "
|
||||
"WHERE NeighborhoodGuid = '{}' AND GenerationID = {} AND RewardAvailable = 1 AND RewardClaimed = 0 "
|
||||
"ON DUPLICATE KEY UPDATE UpdatedAt = UpdatedAt",
|
||||
characterGuid, uint32(RewardClaimPending), now, now, neighborhoodGuid.ToString(), generationId);
|
||||
|
||||
if (!LoadClaimByGeneration(neighborhoodGuid, generationId, claim) || !claim.Exists)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
ArchivedRewardState verifiedReward;
|
||||
if (!LoadArchivedReward(neighborhoodGuid, generationId, verifiedReward))
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
if (!verifiedReward.RewardAvailable)
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
if (verifiedReward.RewardClaimed)
|
||||
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||
|
||||
return PopulateReservation(player, verifiedReward, claim, reservation);
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult PrepareArchivedInitiativeRewardDelivery(Player const* player,
|
||||
uint64 claimId, std::vector<InitiativeRewardGrant>& grants)
|
||||
{
|
||||
grants.clear();
|
||||
if (!player || !claimId)
|
||||
return InitiativeRewardClaimResult::InvalidRequest;
|
||||
|
||||
std::lock_guard<std::mutex> lock(InitiativeRewardClaimMutex);
|
||||
|
||||
RewardClaimState claim;
|
||||
if (!LoadClaimById(claimId, claim))
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
if (claim.CharacterGuid != player->GetGUID().GetCounter())
|
||||
return InitiativeRewardClaimResult::PermissionDenied;
|
||||
if (claim.State == RewardClaimCompleted)
|
||||
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||
|
||||
ArchivedRewardState reward;
|
||||
if (!LoadArchivedReward(claim.NeighborhoodGuid, claim.GenerationId, reward) ||
|
||||
reward.InitiativeId != claim.InitiativeId)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
if (!reward.RewardAvailable)
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
if (reward.RewardClaimed)
|
||||
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||
|
||||
InitiativeRewardClaimResult const existingSnapshot = LoadDeliverySnapshot(claimId, grants);
|
||||
if (existingSnapshot != InitiativeRewardClaimResult::Success)
|
||||
return existingSnapshot;
|
||||
if (!grants.empty())
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
|
||||
std::vector<RewardCatalogEntry> catalog;
|
||||
InitiativeRewardClaimResult const catalogResult = LoadCatalogEntries(reward.RewardTier, catalog);
|
||||
if (catalogResult != InitiativeRewardClaimResult::Success)
|
||||
return catalogResult;
|
||||
|
||||
uint64 const now = uint64(GameTime::GetGameTime());
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
for (RewardCatalogEntry const& entry : catalog)
|
||||
{
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_reward_delivery "
|
||||
"(ClaimID, EntryID, GrantType, AssetID, Quantity, State, CreatedAt, UpdatedAt, DeliveredAt) "
|
||||
"VALUES ({}, {}, {}, {}, {}, {}, {}, {}, 0) "
|
||||
"ON DUPLICATE KEY UPDATE ClaimID = VALUES(ClaimID)",
|
||||
claimId, entry.EntryId, uint32(entry.Type), entry.AssetId, entry.Quantity,
|
||||
uint32(RewardGrantPending), now, now);
|
||||
}
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
InitiativeRewardClaimResult const snapshotResult = LoadDeliverySnapshot(claimId, grants);
|
||||
if (snapshotResult != InitiativeRewardClaimResult::Success)
|
||||
return snapshotResult;
|
||||
if (grants.size() != catalog.size())
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
for (std::size_t i = 0; i < grants.size(); ++i)
|
||||
{
|
||||
RewardCatalogEntry const& expected = catalog[i];
|
||||
InitiativeRewardGrant const& actual = grants[i];
|
||||
if (actual.EntryId != expected.EntryId || actual.Type != expected.Type ||
|
||||
actual.AssetId != expected.AssetId || actual.Quantity != expected.Quantity || actual.Delivered)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
}
|
||||
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult MarkArchivedInitiativeRewardGrantDelivered(Player const* player,
|
||||
uint64 claimId, uint32 entryId)
|
||||
{
|
||||
if (!player || !claimId || !entryId)
|
||||
return InitiativeRewardClaimResult::InvalidRequest;
|
||||
|
||||
std::lock_guard<std::mutex> lock(InitiativeRewardClaimMutex);
|
||||
|
||||
RewardClaimState claim;
|
||||
if (!LoadClaimById(claimId, claim))
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
if (claim.CharacterGuid != player->GetGUID().GetCounter())
|
||||
return InitiativeRewardClaimResult::PermissionDenied;
|
||||
if (claim.State == RewardClaimCompleted)
|
||||
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||
|
||||
QueryResult row = CharacterDatabase.PQuery(
|
||||
"SELECT GrantType, AssetID, Quantity, State FROM housing_initiative_reward_delivery "
|
||||
"WHERE ClaimID = {} AND EntryID = {} LIMIT 1",
|
||||
claimId, entryId);
|
||||
if (!row)
|
||||
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||
|
||||
Field* fields = row->Fetch();
|
||||
uint8 const grantType = fields[0].GetUInt8();
|
||||
uint32 const assetId = fields[1].GetUInt32();
|
||||
uint32 const quantity = fields[2].GetUInt32();
|
||||
uint8 const state = fields[3].GetUInt8();
|
||||
if (!IsSupportedGrantType(grantType) || !assetId || !quantity ||
|
||||
quantity > uint32(std::numeric_limits<int32>::max()) ||
|
||||
(state != RewardGrantPending && state != RewardGrantDelivered))
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
if (state == RewardGrantDelivered)
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
|
||||
uint64 const now = uint64(GameTime::GetGameTime());
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"UPDATE housing_initiative_reward_delivery SET State = {}, UpdatedAt = {}, "
|
||||
"DeliveredAt = IF(DeliveredAt = 0, {}, DeliveredAt) "
|
||||
"WHERE ClaimID = {} AND EntryID = {} AND State = {}",
|
||||
uint32(RewardGrantDelivered), now, now, claimId, entryId, uint32(RewardGrantPending));
|
||||
|
||||
QueryResult verification = CharacterDatabase.PQuery(
|
||||
"SELECT State, DeliveredAt FROM housing_initiative_reward_delivery "
|
||||
"WHERE ClaimID = {} AND EntryID = {} LIMIT 1",
|
||||
claimId, entryId);
|
||||
if (!verification)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
Field* verified = verification->Fetch();
|
||||
if (verified[0].GetUInt8() != RewardGrantDelivered || !verified[1].GetUInt64())
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
}
|
||||
|
||||
InitiativeRewardClaimResult FinalizeArchivedInitiativeReward(Player const* player, uint64 claimId)
|
||||
{
|
||||
if (!player || !claimId)
|
||||
return InitiativeRewardClaimResult::InvalidRequest;
|
||||
|
||||
std::lock_guard<std::mutex> lock(InitiativeRewardClaimMutex);
|
||||
|
||||
RewardClaimState claim;
|
||||
if (!LoadClaimById(claimId, claim))
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
|
||||
if (claim.CharacterGuid != player->GetGUID().GetCounter())
|
||||
return InitiativeRewardClaimResult::PermissionDenied;
|
||||
|
||||
ArchivedRewardState reward;
|
||||
if (!LoadArchivedReward(claim.NeighborhoodGuid, claim.GenerationId, reward) ||
|
||||
reward.InitiativeId != claim.InitiativeId)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
if (claim.State == RewardClaimCompleted)
|
||||
return reward.RewardClaimed ? InitiativeRewardClaimResult::Success : InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
if (!reward.RewardAvailable)
|
||||
return InitiativeRewardClaimResult::NoReward;
|
||||
|
||||
QueryResult deliveryCount = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*), COALESCE(SUM(State = {}), 0), COALESCE(SUM(State = {}), 0) "
|
||||
"FROM housing_initiative_reward_delivery WHERE ClaimID = {}",
|
||||
uint32(RewardGrantPending), uint32(RewardGrantDelivered), claimId);
|
||||
if (!deliveryCount)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
Field* deliveryFields = deliveryCount->Fetch();
|
||||
uint64 const totalEntries = deliveryFields[0].GetUInt64();
|
||||
uint64 const pendingEntries = deliveryFields[1].GetUInt64();
|
||||
uint64 const deliveredEntries = deliveryFields[2].GetUInt64();
|
||||
if (!totalEntries)
|
||||
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||
if (totalEntries > MaxRewardEntriesPerClaim)
|
||||
return InitiativeRewardClaimResult::SafetyLimitExceeded;
|
||||
if (pendingEntries)
|
||||
return InitiativeRewardClaimResult::DeliveryIncomplete;
|
||||
if (deliveredEntries != totalEntries)
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
uint64 const now = uint64(GameTime::GetGameTime());
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_initiative_reward_claim SET State = {}, UpdatedAt = {}, "
|
||||
"CompletedAt = IF(CompletedAt = 0, {}, CompletedAt) "
|
||||
"WHERE ClaimID = {} AND CharacterGuid = {} AND State = {}",
|
||||
uint32(RewardClaimCompleted), now, now, claimId, claim.CharacterGuid, uint32(RewardClaimPending));
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_initiative_history SET RewardClaimed = 1 "
|
||||
"WHERE NeighborhoodGuid = '{}' AND GenerationID = {} AND InitiativeID = {} AND RewardAvailable = 1",
|
||||
claim.NeighborhoodGuid.ToString(), claim.GenerationId, claim.InitiativeId);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
RewardClaimState verifiedClaim;
|
||||
ArchivedRewardState verifiedReward;
|
||||
if (!LoadClaimById(claimId, verifiedClaim) ||
|
||||
!LoadArchivedReward(claim.NeighborhoodGuid, claim.GenerationId, verifiedReward))
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
|
||||
if (verifiedClaim.State != RewardClaimCompleted || verifiedClaim.CharacterGuid != claim.CharacterGuid ||
|
||||
!verifiedClaim.CompletedAt || !verifiedReward.RewardAvailable || !verifiedReward.RewardClaimed ||
|
||||
verifiedReward.InitiativeId != claim.InitiativeId)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Initiative reward claim {} finalization verification failed", claimId);
|
||||
return InitiativeRewardClaimResult::PersistenceError;
|
||||
}
|
||||
|
||||
return InitiativeRewardClaimResult::Success;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_INITIATIVE_REWARD_H
|
||||
#define TRINITYCORE_HOUSING_INITIATIVE_REWARD_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <vector>
|
||||
|
||||
class Player;
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
enum class InitiativeRewardClaimResult : uint8
|
||||
{
|
||||
Success = 0,
|
||||
InvalidRequest,
|
||||
PermissionDenied,
|
||||
NoReward,
|
||||
ClaimInProgress,
|
||||
AlreadyClaimed,
|
||||
SafetyLimitExceeded,
|
||||
InvalidCatalog,
|
||||
DeliveryIncomplete,
|
||||
PersistenceError
|
||||
};
|
||||
|
||||
enum class InitiativeRewardGrantType : uint8
|
||||
{
|
||||
Item = 1,
|
||||
Currency = 2
|
||||
};
|
||||
|
||||
struct ArchivedInitiativeReward
|
||||
{
|
||||
uint64 GenerationId = 0;
|
||||
uint32 InitiativeId = 0;
|
||||
uint16 RewardTier = 0;
|
||||
uint64 CompletedAt = 0;
|
||||
uint64 ExpiredAt = 0;
|
||||
uint64 ClaimId = 0;
|
||||
bool ClaimPending = false;
|
||||
};
|
||||
|
||||
struct InitiativeRewardClaimReservation
|
||||
{
|
||||
uint64 ClaimId = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint64 GenerationId = 0;
|
||||
uint32 InitiativeId = 0;
|
||||
uint16 RewardTier = 0;
|
||||
};
|
||||
|
||||
struct InitiativeRewardGrant
|
||||
{
|
||||
uint32 EntryId = 0;
|
||||
InitiativeRewardGrantType Type = InitiativeRewardGrantType::Item;
|
||||
uint32 AssetId = 0;
|
||||
uint32 Quantity = 0;
|
||||
bool Delivered = false;
|
||||
};
|
||||
|
||||
TC_GAME_API InitiativeRewardClaimResult LoadArchivedInitiativeRewards(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, std::vector<ArchivedInitiativeReward>& rewards);
|
||||
|
||||
TC_GAME_API InitiativeRewardClaimResult ReserveArchivedInitiativeReward(Player const* player,
|
||||
ObjectGuid const& neighborhoodGuid, uint64 generationId, InitiativeRewardClaimReservation& reservation);
|
||||
|
||||
// Creates an immutable per-claim snapshot from the configured reward catalog. Once
|
||||
// created, later catalog edits do not change a claim that is already in progress.
|
||||
TC_GAME_API InitiativeRewardClaimResult PrepareArchivedInitiativeRewardDelivery(Player const* player,
|
||||
uint64 claimId, std::vector<InitiativeRewardGrant>& grants);
|
||||
|
||||
// The caller must grant the exact snapshot entry before marking it delivered. This
|
||||
// operation is idempotent and never grants items or currency by itself.
|
||||
TC_GAME_API InitiativeRewardClaimResult MarkArchivedInitiativeRewardGrantDelivered(Player const* player,
|
||||
uint64 claimId, uint32 entryId);
|
||||
|
||||
// The caller must grant every prepared entry before finalizing. Finalization only
|
||||
// seals persistence; it never grants items or currency.
|
||||
TC_GAME_API InitiativeRewardClaimResult FinalizeArchivedInitiativeReward(Player const* player,
|
||||
uint64 claimId);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_INITIATIVE_REWARD_H
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_INITIATIVE_REWARD_EXECUTOR_H
|
||||
#define TRINITYCORE_HOUSING_INITIATIVE_REWARD_EXECUTOR_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectGuid.h"
|
||||
|
||||
class Player;
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
enum class InitiativeRewardExecutionResult : uint8
|
||||
{
|
||||
Success = 0,
|
||||
InvalidRequest,
|
||||
PermissionDenied,
|
||||
NoReward,
|
||||
AlreadyClaimed,
|
||||
RetryRequired,
|
||||
AmbiguousState,
|
||||
GrantFailed,
|
||||
PersistenceError
|
||||
};
|
||||
|
||||
// Executes the immutable reward snapshot one entry at a time. Each entry records
|
||||
// the observed quantity before mutation and the exact expected quantity after it,
|
||||
// allowing interrupted grants to be reconciled conservatively on retry.
|
||||
TC_GAME_API InitiativeRewardExecutionResult ExecuteArchivedInitiativeReward(Player* player, uint64 claimId);
|
||||
|
||||
// Reserves the archived neighborhood generation for the current player and then
|
||||
// executes the immutable delivery snapshot. Existing reservations owned by this
|
||||
// player resume the same ClaimID, making interrupted claims idempotent.
|
||||
TC_GAME_API InitiativeRewardExecutionResult ClaimArchivedInitiativeReward(Player* player,
|
||||
ObjectGuid const& neighborhoodGuid, uint64 generationId, uint64* claimId = nullptr);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_INITIATIVE_REWARD_EXECUTOR_H
|
||||
@@ -1,507 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingInitiativeRotation.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "GameTime.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "Log.h"
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr uint32 MaxInitiativeActivitiesPerGeneration = 1024;
|
||||
|
||||
struct InitiativeRootState
|
||||
{
|
||||
uint32 InitiativeId = 0;
|
||||
uint8 State = 0;
|
||||
uint64 Progress = 0;
|
||||
uint16 RewardTier = 0;
|
||||
uint64 StartedAt = 0;
|
||||
uint64 EndsAt = 0;
|
||||
};
|
||||
|
||||
struct InitiativeScheduleEntry
|
||||
{
|
||||
uint64 ScheduleId = 0;
|
||||
uint32 InitiativeId = 0;
|
||||
uint16 RewardTier = 0;
|
||||
uint64 StartsAt = 0;
|
||||
uint64 EndsAt = 0;
|
||||
};
|
||||
|
||||
struct InitiativeRotationState
|
||||
{
|
||||
bool Exists = false;
|
||||
uint64 GenerationId = 0;
|
||||
uint64 ActiveScheduleId = 0;
|
||||
uint32 InitiativeId = 0;
|
||||
};
|
||||
|
||||
struct InitiativeCompletionState
|
||||
{
|
||||
uint64 CompletedAt = 0;
|
||||
bool RewardAvailable = false;
|
||||
bool RewardClaimed = false;
|
||||
};
|
||||
|
||||
std::mutex InitiativeRotationMutex;
|
||||
|
||||
bool LoadRootState(ObjectGuid const& neighborhoodGuid, InitiativeRootState& state)
|
||||
{
|
||||
state = {};
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID, State, Progress, RewardTier, COALESCE(StartedAt, 0), COALESCE(EndsAt, 0) "
|
||||
"FROM housing_neighborhood_initiative WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
state.InitiativeId = fields[0].GetUInt32();
|
||||
state.State = fields[1].GetUInt8();
|
||||
state.Progress = fields[2].GetUInt64();
|
||||
state.RewardTier = fields[3].GetUInt16();
|
||||
state.StartedAt = fields[4].GetUInt64();
|
||||
state.EndsAt = fields[5].GetUInt64();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadRotationState(ObjectGuid const& neighborhoodGuid, InitiativeRotationState& state)
|
||||
{
|
||||
state = {};
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT GenerationID, ActiveScheduleID, InitiativeID FROM housing_initiative_rotation_state "
|
||||
"WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!result)
|
||||
return true;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
state.Exists = true;
|
||||
state.GenerationId = fields[0].GetUInt64();
|
||||
state.ActiveScheduleId = fields[1].GetUInt64();
|
||||
state.InitiativeId = fields[2].GetUInt32();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadMaxArchivedGeneration(ObjectGuid const& neighborhoodGuid, uint64& generationId)
|
||||
{
|
||||
generationId = 0;
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT COALESCE(MAX(GenerationID), 0) FROM housing_initiative_history "
|
||||
"WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
generationId = result->Fetch()[0].GetUInt64();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeedLegacyRotationState(ObjectGuid const& neighborhoodGuid, InitiativeRootState const& root,
|
||||
InitiativeRotationState& state)
|
||||
{
|
||||
uint64 maxArchivedGeneration = 0;
|
||||
if (!LoadMaxArchivedGeneration(neighborhoodGuid, maxArchivedGeneration) ||
|
||||
maxArchivedGeneration == std::numeric_limits<uint64>::max())
|
||||
return false;
|
||||
|
||||
state = {};
|
||||
state.Exists = true;
|
||||
state.GenerationId = maxArchivedGeneration + 1;
|
||||
state.InitiativeId = root.InitiativeId;
|
||||
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_initiative_rotation_state "
|
||||
"(NeighborhoodGuid, GenerationID, ActiveScheduleID, InitiativeID) "
|
||||
"VALUES ('{}', {}, 0, {}) "
|
||||
"ON DUPLICATE KEY UPDATE NeighborhoodGuid = VALUES(NeighborhoodGuid)",
|
||||
neighborhoodGuid.ToString(), state.GenerationId, state.InitiativeId);
|
||||
|
||||
QueryResult verification = CharacterDatabase.PQuery(
|
||||
"SELECT GenerationID, ActiveScheduleID, InitiativeID FROM housing_initiative_rotation_state "
|
||||
"WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!verification)
|
||||
return false;
|
||||
|
||||
Field* fields = verification->Fetch();
|
||||
state.GenerationId = fields[0].GetUInt64();
|
||||
state.ActiveScheduleId = fields[1].GetUInt64();
|
||||
state.InitiativeId = fields[2].GetUInt32();
|
||||
return state.GenerationId != 0 && state.InitiativeId == root.InitiativeId;
|
||||
}
|
||||
|
||||
bool LoadScheduleEntry(uint64 now, InitiativeScheduleEntry& entry, bool& ambiguous)
|
||||
{
|
||||
entry = {};
|
||||
ambiguous = false;
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT ScheduleID, InitiativeID, RewardTier, StartsAt, EndsAt "
|
||||
"FROM housing_initiative_schedule "
|
||||
"WHERE `Enabled` = 1 AND InitiativeID <> 0 AND StartsAt <> 0 AND StartsAt <= {} "
|
||||
"AND (EndsAt = 0 OR EndsAt > {}) "
|
||||
"ORDER BY StartsAt DESC, ScheduleID DESC LIMIT 2",
|
||||
now, now);
|
||||
if (!result)
|
||||
return true;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
entry.ScheduleId = fields[0].GetUInt64();
|
||||
entry.InitiativeId = fields[1].GetUInt32();
|
||||
entry.RewardTier = fields[2].GetUInt16();
|
||||
entry.StartsAt = fields[3].GetUInt64();
|
||||
entry.EndsAt = fields[4].GetUInt64();
|
||||
ambiguous = result->NextRow();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateScheduleEntry(InitiativeScheduleEntry const& entry)
|
||||
{
|
||||
if (!entry.ScheduleId || !entry.InitiativeId || !entry.StartsAt ||
|
||||
(entry.EndsAt && entry.EndsAt <= entry.StartsAt))
|
||||
return false;
|
||||
|
||||
QueryResult definitions = CharacterDatabase.PQuery(
|
||||
"SELECT ActivityID, Target FROM housing_initiative_activity_definition "
|
||||
"WHERE InitiativeID = {} AND `Enabled` = 1 ORDER BY ActivityID LIMIT {}",
|
||||
entry.InitiativeId, MaxInitiativeActivitiesPerGeneration + 1);
|
||||
if (!definitions)
|
||||
return false;
|
||||
|
||||
uint32 count = 0;
|
||||
do
|
||||
{
|
||||
if (++count > MaxInitiativeActivitiesPerGeneration)
|
||||
return false;
|
||||
|
||||
Field* fields = definitions->Fetch();
|
||||
if (!fields[0].GetUInt32() || !fields[1].GetUInt64())
|
||||
return false;
|
||||
} while (definitions->NextRow());
|
||||
|
||||
return count != 0;
|
||||
}
|
||||
|
||||
bool LoadCompletionState(ObjectGuid const& neighborhoodGuid, uint32 initiativeId,
|
||||
InitiativeCompletionState& state)
|
||||
{
|
||||
state = {};
|
||||
if (!initiativeId)
|
||||
return true;
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT CompletedAt, RewardAvailable, RewardClaimed FROM housing_initiative_completion "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
if (!result)
|
||||
return true;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
uint8 const rewardAvailable = fields[1].GetUInt8();
|
||||
uint8 const rewardClaimed = fields[2].GetUInt8();
|
||||
if (rewardAvailable > 1 || rewardClaimed > 1)
|
||||
return false;
|
||||
|
||||
state.CompletedAt = fields[0].GetUInt64();
|
||||
state.RewardAvailable = rewardAvailable != 0;
|
||||
state.RewardClaimed = rewardClaimed != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64 LoadExpiredAt(ObjectGuid const& neighborhoodGuid, uint32 initiativeId)
|
||||
{
|
||||
if (!initiativeId)
|
||||
return 0;
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT ExpiredAt FROM housing_initiative_expiration "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
return result ? result->Fetch()[0].GetUInt64() : 0;
|
||||
}
|
||||
|
||||
bool HasStaleLifecycleState(ObjectGuid const& neighborhoodGuid, uint32 initiativeId)
|
||||
{
|
||||
QueryResult completion = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_completion "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
if (completion)
|
||||
return true;
|
||||
|
||||
QueryResult expiration = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_expiration "
|
||||
"WHERE NeighborhoodGuid = '{}' AND InitiativeID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), initiativeId);
|
||||
return bool(expiration);
|
||||
}
|
||||
|
||||
bool HistoryGenerationMatches(ObjectGuid const& neighborhoodGuid, uint64 generationId, uint32 initiativeId,
|
||||
bool& exists)
|
||||
{
|
||||
exists = false;
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID FROM housing_initiative_history "
|
||||
"WHERE NeighborhoodGuid = '{}' AND GenerationID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), generationId);
|
||||
if (!result)
|
||||
return true;
|
||||
|
||||
exists = true;
|
||||
return result->Fetch()[0].GetUInt32() == initiativeId;
|
||||
}
|
||||
|
||||
bool VerifyRotation(ObjectGuid const& neighborhoodGuid, InitiativeScheduleEntry const& entry,
|
||||
uint64 generationId, uint32 archivedInitiativeId, uint64 archivedGenerationId)
|
||||
{
|
||||
QueryResult root = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID, State, Progress, RewardTier, COALESCE(StartedAt, 0), COALESCE(EndsAt, 0) "
|
||||
"FROM housing_neighborhood_initiative WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!root)
|
||||
return false;
|
||||
|
||||
Field* rootFields = root->Fetch();
|
||||
if (rootFields[0].GetUInt32() != entry.InitiativeId || rootFields[1].GetUInt8() != 0 ||
|
||||
rootFields[2].GetUInt64() != 0 || rootFields[3].GetUInt16() != entry.RewardTier ||
|
||||
rootFields[4].GetUInt64() != entry.StartsAt || rootFields[5].GetUInt64() != entry.EndsAt)
|
||||
return false;
|
||||
|
||||
QueryResult rotation = CharacterDatabase.PQuery(
|
||||
"SELECT GenerationID, ActiveScheduleID, InitiativeID FROM housing_initiative_rotation_state "
|
||||
"WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
if (!rotation)
|
||||
return false;
|
||||
|
||||
Field* rotationFields = rotation->Fetch();
|
||||
if (rotationFields[0].GetUInt64() != generationId ||
|
||||
rotationFields[1].GetUInt64() != entry.ScheduleId ||
|
||||
rotationFields[2].GetUInt32() != entry.InitiativeId)
|
||||
return false;
|
||||
|
||||
if (archivedInitiativeId)
|
||||
{
|
||||
QueryResult history = CharacterDatabase.PQuery(
|
||||
"SELECT InitiativeID FROM housing_initiative_history "
|
||||
"WHERE NeighborhoodGuid = '{}' AND GenerationID = {} LIMIT 1",
|
||||
neighborhoodGuid.ToString(), archivedGenerationId);
|
||||
if (!history || history->Fetch()[0].GetUInt32() != archivedInitiativeId)
|
||||
return false;
|
||||
}
|
||||
|
||||
QueryResult staleActivity = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_activity_progress WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
QueryResult staleMember = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_member_progress WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
QueryResult staleLog = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_activity_log WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
QueryResult staleCheckpoint = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_criteria_checkpoint WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
QueryResult staleSelection = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_player_state WHERE ActiveNeighborhoodGuid = '{}' LIMIT 1",
|
||||
neighborhoodGuid.ToString());
|
||||
|
||||
if (staleActivity || staleMember || staleLog || staleCheckpoint || staleSelection)
|
||||
return false;
|
||||
|
||||
return !HasStaleLifecycleState(neighborhoodGuid, entry.InitiativeId);
|
||||
}
|
||||
}
|
||||
|
||||
InitiativeRotationResult RefreshInitiativeRotation(ObjectGuid const& neighborhoodGuid)
|
||||
{
|
||||
if (!IsNeighborhoodGuid(neighborhoodGuid))
|
||||
return InitiativeRotationResult::InvalidConfiguration;
|
||||
|
||||
std::lock_guard<std::mutex> lock(InitiativeRotationMutex);
|
||||
|
||||
InitiativeRootState root;
|
||||
if (!LoadRootState(neighborhoodGuid, root))
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
|
||||
uint64 const now = uint64(GameTime::GetGameTime());
|
||||
uint64 const expiredAt = LoadExpiredAt(neighborhoodGuid, root.InitiativeId);
|
||||
bool const currentExpired = root.InitiativeId && (expiredAt != 0 || (root.EndsAt != 0 && root.EndsAt <= now));
|
||||
if (root.InitiativeId && !currentExpired)
|
||||
return InitiativeRotationResult::NoChange;
|
||||
|
||||
InitiativeRotationState rotationState;
|
||||
if (!LoadRotationState(neighborhoodGuid, rotationState))
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
|
||||
if (rotationState.Exists && rotationState.InitiativeId != root.InitiativeId)
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Initiative rotation state mismatch for neighborhood {}: root initiative {} rotation initiative {}",
|
||||
neighborhoodGuid.ToString(), root.InitiativeId, rotationState.InitiativeId);
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
}
|
||||
|
||||
if (!rotationState.Exists && root.InitiativeId)
|
||||
{
|
||||
if (!SeedLegacyRotationState(neighborhoodGuid, root, rotationState))
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
}
|
||||
|
||||
InitiativeScheduleEntry entry;
|
||||
bool ambiguousSchedule = false;
|
||||
if (!LoadScheduleEntry(now, entry, ambiguousSchedule))
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
|
||||
if (!entry.ScheduleId)
|
||||
return InitiativeRotationResult::NoChange;
|
||||
|
||||
if (ambiguousSchedule)
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Multiple Initiative schedules are active at the same time; rotation for neighborhood {} was rejected",
|
||||
neighborhoodGuid.ToString());
|
||||
return InitiativeRotationResult::InvalidConfiguration;
|
||||
}
|
||||
|
||||
if (rotationState.Exists && rotationState.ActiveScheduleId == entry.ScheduleId)
|
||||
return InitiativeRotationResult::NoChange;
|
||||
|
||||
if (!ValidateScheduleEntry(entry))
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Initiative schedule {} is invalid or has no valid activity definitions",
|
||||
entry.ScheduleId);
|
||||
return InitiativeRotationResult::InvalidConfiguration;
|
||||
}
|
||||
|
||||
if (entry.InitiativeId != root.InitiativeId && HasStaleLifecycleState(neighborhoodGuid, entry.InitiativeId))
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Initiative {} has unarchived lifecycle state for neighborhood {}; refusing to discard legacy reward or expiration data",
|
||||
entry.InitiativeId, neighborhoodGuid.ToString());
|
||||
return InitiativeRotationResult::InvalidConfiguration;
|
||||
}
|
||||
|
||||
uint64 currentGeneration = rotationState.GenerationId;
|
||||
if (!rotationState.Exists)
|
||||
{
|
||||
if (!LoadMaxArchivedGeneration(neighborhoodGuid, currentGeneration))
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
}
|
||||
|
||||
if (currentGeneration == std::numeric_limits<uint64>::max())
|
||||
return InitiativeRotationResult::SafetyLimitExceeded;
|
||||
|
||||
uint64 const nextGeneration = currentGeneration + 1;
|
||||
InitiativeCompletionState completion;
|
||||
if (!LoadCompletionState(neighborhoodGuid, root.InitiativeId, completion))
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
|
||||
uint64 archivedExpiredAt = expiredAt;
|
||||
if (root.InitiativeId && !archivedExpiredAt && currentExpired)
|
||||
archivedExpiredAt = now;
|
||||
|
||||
bool historyExists = false;
|
||||
if (root.InitiativeId &&
|
||||
!HistoryGenerationMatches(neighborhoodGuid, currentGeneration, root.InitiativeId, historyExists))
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
|
||||
if (root.InitiativeId && !historyExists)
|
||||
{
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_history "
|
||||
"(NeighborhoodGuid, GenerationID, ScheduleID, InitiativeID, State, Progress, RewardTier, StartedAt, EndsAt, "
|
||||
"CompletedAt, ExpiredAt, RewardAvailable, RewardClaimed, ArchivedAt) "
|
||||
"VALUES ('{}', {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
|
||||
neighborhoodGuid.ToString(), currentGeneration, rotationState.ActiveScheduleId, root.InitiativeId,
|
||||
uint32(root.State), root.Progress, uint32(root.RewardTier), root.StartedAt, root.EndsAt,
|
||||
completion.CompletedAt, archivedExpiredAt, completion.RewardAvailable ? 1 : 0,
|
||||
completion.RewardClaimed ? 1 : 0, now);
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_activity_history "
|
||||
"(NeighborhoodGuid, GenerationID, ActivityID, Progress, Target, Completed) "
|
||||
"SELECT NeighborhoodGuid, {}, ActivityID, Progress, Target, Completed "
|
||||
"FROM housing_initiative_activity_progress WHERE NeighborhoodGuid = '{}'",
|
||||
currentGeneration, neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_member_history "
|
||||
"(NeighborhoodGuid, GenerationID, CharacterGuid, ActivityID, Progress) "
|
||||
"SELECT NeighborhoodGuid, {}, CharacterGuid, ActivityID, Progress "
|
||||
"FROM housing_initiative_member_progress WHERE NeighborhoodGuid = '{}'",
|
||||
currentGeneration, neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_activity_log_history "
|
||||
"(NeighborhoodGuid, GenerationID, CharacterGuid, ActivityID, ProgressDelta, CreatedAt) "
|
||||
"SELECT NeighborhoodGuid, {}, CharacterGuid, ActivityID, ProgressDelta, CreatedAt "
|
||||
"FROM housing_initiative_activity_log WHERE NeighborhoodGuid = '{}' ORDER BY ID",
|
||||
currentGeneration, neighborhoodGuid.ToString());
|
||||
}
|
||||
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_activity_progress WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_member_progress WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_activity_log WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_criteria_checkpoint WHERE NeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_player_state WHERE ActiveNeighborhoodGuid = '{}'",
|
||||
neighborhoodGuid.ToString());
|
||||
|
||||
if (root.InitiativeId)
|
||||
{
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_completion WHERE NeighborhoodGuid = '{}' AND InitiativeID = {}",
|
||||
neighborhoodGuid.ToString(), root.InitiativeId);
|
||||
transaction->PAppend(
|
||||
"DELETE FROM housing_initiative_expiration WHERE NeighborhoodGuid = '{}' AND InitiativeID = {}",
|
||||
neighborhoodGuid.ToString(), root.InitiativeId);
|
||||
}
|
||||
|
||||
transaction->PAppend(
|
||||
"UPDATE housing_neighborhood_initiative "
|
||||
"SET InitiativeID = {}, State = 0, Progress = 0, RewardTier = {}, StartedAt = {}, EndsAt = {} "
|
||||
"WHERE NeighborhoodGuid = '{}'",
|
||||
entry.InitiativeId, uint32(entry.RewardTier), entry.StartsAt, entry.EndsAt, neighborhoodGuid.ToString());
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_progress_generation (NeighborhoodGuid, InitiativeID) "
|
||||
"VALUES ('{}', {}) ON DUPLICATE KEY UPDATE InitiativeID = VALUES(InitiativeID)",
|
||||
neighborhoodGuid.ToString(), entry.InitiativeId);
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_initiative_rotation_state "
|
||||
"(NeighborhoodGuid, GenerationID, ActiveScheduleID, InitiativeID) VALUES ('{}', {}, {}, {}) "
|
||||
"ON DUPLICATE KEY UPDATE GenerationID = VALUES(GenerationID), ActiveScheduleID = VALUES(ActiveScheduleID), "
|
||||
"InitiativeID = VALUES(InitiativeID)",
|
||||
neighborhoodGuid.ToString(), nextGeneration, entry.ScheduleId, entry.InitiativeId);
|
||||
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
if (!VerifyRotation(neighborhoodGuid, entry, nextGeneration, root.InitiativeId, currentGeneration))
|
||||
{
|
||||
TC_LOG_ERROR("housing",
|
||||
"Initiative rotation verification failed for neighborhood {} schedule {} initiative {}",
|
||||
neighborhoodGuid.ToString(), entry.ScheduleId, entry.InitiativeId);
|
||||
return InitiativeRotationResult::PersistenceError;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("housing",
|
||||
"Activated Initiative {} schedule {} generation {} for neighborhood {}; previous generation was archived",
|
||||
entry.InitiativeId, entry.ScheduleId, nextGeneration, neighborhoodGuid.ToString());
|
||||
return InitiativeRotationResult::Activated;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_INITIATIVE_ROTATION_H
|
||||
#define TRINITYCORE_HOUSING_INITIATIVE_ROTATION_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectGuid.h"
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
enum class InitiativeRotationResult : uint8
|
||||
{
|
||||
NoChange = 0,
|
||||
Activated,
|
||||
InvalidConfiguration,
|
||||
SafetyLimitExceeded,
|
||||
PersistenceError
|
||||
};
|
||||
|
||||
TC_GAME_API InitiativeRotationResult RefreshInitiativeRotation(ObjectGuid const& neighborhoodGuid);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_INITIATIVE_ROTATION_H
|
||||
@@ -1,261 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingInitiativeSession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingInitiativePackets.h"
|
||||
#include "HousingInitiativeProgress.h"
|
||||
#include "HousingInitiativeRotation.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "HousingPackets.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool LoadOwnedNeighborhood(WorldSession* session, ObjectGuid const& neighborhoodGuid, HouseState& house)
|
||||
{
|
||||
house = {};
|
||||
if (!session || !IsNeighborhoodGuid(neighborhoodGuid))
|
||||
return false;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
for (HouseState const& ownedHouse : sHousingMgr.LoadOwnedHouses(player))
|
||||
{
|
||||
if (ownedHouse.NeighborhoodGuid != neighborhoodGuid)
|
||||
continue;
|
||||
|
||||
house = ownedHouse;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnsureInitiativePersistence(HouseState const& house)
|
||||
{
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_neighborhood_initiative "
|
||||
"(NeighborhoodGuid, InitiativeID, State, Progress, RewardTier, StartedAt, EndsAt) "
|
||||
"VALUES ('{}', 0, 0, 0, 0, NULL, NULL) "
|
||||
"ON DUPLICATE KEY UPDATE NeighborhoodGuid = VALUES(NeighborhoodGuid)",
|
||||
house.NeighborhoodGuid.ToString());
|
||||
}
|
||||
|
||||
void RefreshRotationForNeighborhood(ObjectGuid const& neighborhoodGuid)
|
||||
{
|
||||
InitiativeRotationResult const result = RefreshInitiativeRotation(neighborhoodGuid);
|
||||
if (result == InitiativeRotationResult::NoChange || result == InitiativeRotationResult::Activated)
|
||||
return;
|
||||
|
||||
if (result == InitiativeRotationResult::InvalidConfiguration)
|
||||
{
|
||||
TC_LOG_WARN("housing", "Initiative rotation configuration is invalid for neighborhood {}",
|
||||
neighborhoodGuid.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
TC_LOG_ERROR("housing", "Initiative rotation failed for neighborhood {} with result {}",
|
||||
neighborhoodGuid.ToString(), uint32(result));
|
||||
}
|
||||
|
||||
bool PersistActiveNeighborhood(WorldSession* session, ObjectGuid const& neighborhoodGuid)
|
||||
{
|
||||
if (!session)
|
||||
return false;
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
uint64 const characterGuid = player->GetGUID().GetCounter();
|
||||
if (neighborhoodGuid.IsEmpty())
|
||||
{
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"DELETE FROM housing_initiative_player_state WHERE CharacterGuid = {}",
|
||||
characterGuid);
|
||||
|
||||
QueryResult remaining = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_initiative_player_state WHERE CharacterGuid = {} LIMIT 1",
|
||||
characterGuid);
|
||||
return !remaining;
|
||||
}
|
||||
|
||||
if (!IsNeighborhoodGuid(neighborhoodGuid))
|
||||
return false;
|
||||
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO housing_initiative_player_state (CharacterGuid, ActiveNeighborhoodGuid) "
|
||||
"VALUES ({}, '{}') ON DUPLICATE KEY UPDATE ActiveNeighborhoodGuid = VALUES(ActiveNeighborhoodGuid)",
|
||||
characterGuid, neighborhoodGuid.ToString());
|
||||
|
||||
QueryResult persisted = CharacterDatabase.PQuery(
|
||||
"SELECT ActiveNeighborhoodGuid FROM housing_initiative_player_state "
|
||||
"WHERE CharacterGuid = {} LIMIT 1",
|
||||
characterGuid);
|
||||
if (!persisted)
|
||||
return false;
|
||||
|
||||
ObjectGuid const persistedNeighborhood = ObjectGuid::FromString(persisted->Fetch()[0].GetString());
|
||||
return persistedNeighborhood == neighborhoodGuid;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleInitiativeServiceStatus(WorldSession* session,
|
||||
WorldPackets::Housing::NeighborhoodInitiativeServiceStatusCheck& /*packet*/)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::InitiativeServiceStatus response;
|
||||
response.Available = true;
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleGetAvailableInitiative(WorldSession* session,
|
||||
WorldPackets::Housing::GetAvailableInitiativeRequest& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::GetPlayerInitiativeInfoResult response;
|
||||
response.NeighborhoodGUID = packet.NeighborhoodGUID;
|
||||
response.Flags = 0;
|
||||
|
||||
HouseState house;
|
||||
if (LoadOwnedNeighborhood(session, packet.NeighborhoodGUID, house))
|
||||
{
|
||||
EnsureInitiativePersistence(house);
|
||||
RefreshRotationForNeighborhood(packet.NeighborhoodGUID);
|
||||
}
|
||||
|
||||
InitiativeProgressSnapshot snapshot;
|
||||
InitiativeProgressResult const loadResult = LoadInitiativeProgress(
|
||||
session->GetPlayer(), packet.NeighborhoodGUID, snapshot);
|
||||
if (loadResult != InitiativeProgressResult::Success)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapshot.InitiativeId != 0 || snapshot.State != 0 || snapshot.Progress != 0 ||
|
||||
snapshot.RewardTier != 0 || snapshot.StartedAt != 0 || snapshot.EndsAt != 0 || snapshot.Expired ||
|
||||
!snapshot.Activities.empty() || !snapshot.PlayerActivities.empty())
|
||||
{
|
||||
// The empty player-info form is verified and terminates after the flag byte.
|
||||
// Keep richer persisted Initiative state server-side until the current-client
|
||||
// trailing data block has been capture-verified.
|
||||
TC_LOG_DEBUG("housing",
|
||||
"Neighborhood {} has Initiative {} state {} progress {} reward tier {} start {} end {} expired {} with {} activities and {} player contributions; detailed wire publication is deferred",
|
||||
packet.NeighborhoodGUID.ToString(), snapshot.InitiativeId, uint32(snapshot.State), snapshot.Progress,
|
||||
uint32(snapshot.RewardTier), snapshot.StartedAt, snapshot.EndsAt, snapshot.Expired,
|
||||
snapshot.Activities.size(), snapshot.PlayerActivities.size());
|
||||
}
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleInitiativeActivityLog(WorldSession* session,
|
||||
WorldPackets::Housing::GetInitiativeActivityLogRequest& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::GetInitiativeActivityLogResult response;
|
||||
response.NeighborhoodGUID = packet.NeighborhoodGUID;
|
||||
response.EntryCount = 0;
|
||||
|
||||
HouseState house;
|
||||
if (!LoadOwnedNeighborhood(session, packet.NeighborhoodGUID, house))
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureInitiativePersistence(house);
|
||||
RefreshRotationForNeighborhood(packet.NeighborhoodGUID);
|
||||
|
||||
InitiativeProgressSnapshot snapshot;
|
||||
InitiativeProgressResult const loadResult = LoadInitiativeProgress(
|
||||
session->GetPlayer(), packet.NeighborhoodGUID, snapshot);
|
||||
if (loadResult != InitiativeProgressResult::Success)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult persistedEntries = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*) FROM housing_initiative_activity_log "
|
||||
"WHERE NeighborhoodGuid = '{}'",
|
||||
packet.NeighborhoodGUID.ToString());
|
||||
uint64 const persistedEntryCount = persistedEntries ? persistedEntries->Fetch()[0].GetUInt64() : 0;
|
||||
if (persistedEntryCount != 0)
|
||||
{
|
||||
// The non-empty build 69587 activity-log record layout is not yet verified.
|
||||
// Keep persisted progress server-side instead of advertising a count whose
|
||||
// corresponding records cannot be serialized safely to the retail client.
|
||||
TC_LOG_DEBUG("housing", "Neighborhood {} has {} persisted Initiative log entries; wire publication is deferred",
|
||||
packet.NeighborhoodGUID.ToString(), persistedEntryCount);
|
||||
}
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleInitiativeUpdateActiveNeighborhood(WorldSession* session,
|
||||
WorldPackets::Housing::InitiativeUpdateActiveNeighborhood& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
if (packet.NeighborhoodGUID.IsEmpty())
|
||||
{
|
||||
if (!PersistActiveNeighborhood(session, ObjectGuid::Empty))
|
||||
TC_LOG_WARN("housing", "Failed to clear active Initiative neighborhood for player {}",
|
||||
session->GetPlayer() ? session->GetPlayer()->GetGUID().ToString() : "<none>");
|
||||
return;
|
||||
}
|
||||
|
||||
HouseState house;
|
||||
if (!LoadOwnedNeighborhood(session, packet.NeighborhoodGUID, house))
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "Rejected active Initiative neighborhood {} because it is not owned by the current player",
|
||||
packet.NeighborhoodGUID.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureInitiativePersistence(house);
|
||||
RefreshRotationForNeighborhood(packet.NeighborhoodGUID);
|
||||
|
||||
InitiativeProgressSnapshot snapshot;
|
||||
InitiativeProgressResult const loadResult = LoadInitiativeProgress(
|
||||
session->GetPlayer(), packet.NeighborhoodGUID, snapshot);
|
||||
if (loadResult != InitiativeProgressResult::Success)
|
||||
{
|
||||
TC_LOG_WARN("housing", "Failed to validate Initiative neighborhood {} before activation for player {}",
|
||||
packet.NeighborhoodGUID.ToString(),
|
||||
session->GetPlayer() ? session->GetPlayer()->GetGUID().ToString() : "<none>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapshot.Expired)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "Rejected expired Initiative {} neighborhood {} for player {}",
|
||||
snapshot.InitiativeId, packet.NeighborhoodGUID.ToString(),
|
||||
session->GetPlayer() ? session->GetPlayer()->GetGUID().ToString() : "<none>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PersistActiveNeighborhood(session, packet.NeighborhoodGUID))
|
||||
TC_LOG_WARN("housing", "Failed to persist active Initiative neighborhood {} for player {}",
|
||||
packet.NeighborhoodGUID.ToString(),
|
||||
session->GetPlayer() ? session->GetPlayer()->GetGUID().ToString() : "<none>");
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_INITIATIVE_SESSION_H
|
||||
#define TRINITYCORE_HOUSING_INITIATIVE_SESSION_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
class WorldSession;
|
||||
|
||||
namespace WorldPackets::Housing
|
||||
{
|
||||
class GetAvailableInitiativeRequest;
|
||||
class GetInitiativeActivityLogRequest;
|
||||
class InitiativeUpdateActiveNeighborhood;
|
||||
class NeighborhoodInitiativeServiceStatusCheck;
|
||||
}
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
TC_GAME_API void HandleInitiativeServiceStatus(WorldSession* session,
|
||||
WorldPackets::Housing::NeighborhoodInitiativeServiceStatusCheck& packet);
|
||||
TC_GAME_API void HandleGetAvailableInitiative(WorldSession* session,
|
||||
WorldPackets::Housing::GetAvailableInitiativeRequest& packet);
|
||||
TC_GAME_API void HandleInitiativeActivityLog(WorldSession* session,
|
||||
WorldPackets::Housing::GetInitiativeActivityLogRequest& packet);
|
||||
TC_GAME_API void HandleInitiativeUpdateActiveNeighborhood(WorldSession* session,
|
||||
WorldPackets::Housing::InitiativeUpdateActiveNeighborhood& packet);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_INITIATIVE_SESSION_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef HousingMap_h__
|
||||
#define HousingMap_h__
|
||||
|
||||
#include "Housing.h"
|
||||
#include "Map.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
class AreaTrigger;
|
||||
class Housing;
|
||||
class HousingMirrorEntity;
|
||||
class HousingRoomEntity;
|
||||
class MeshObject;
|
||||
class Neighborhood;
|
||||
class Player;
|
||||
struct NeighborhoodPlotData;
|
||||
|
||||
class TC_GAME_API HousingMap : public Map
|
||||
{
|
||||
public:
|
||||
HousingMap(uint32 id, time_t expiry, uint32 instanceId, Difficulty spawnMode, uint32 neighborhoodId);
|
||||
~HousingMap();
|
||||
|
||||
void InitVisibilityDistance() override;
|
||||
void LoadGridObjects(NGridType* grid) override;
|
||||
bool AddPlayerToMap(Player* player, bool initPlayer = true) override;
|
||||
void RemovePlayerFromMap(Player* player, bool remove) override;
|
||||
|
||||
Housing* GetHousingForPlayer(ObjectGuid playerGuid) const;
|
||||
AreaTrigger* GetPlotAreaTrigger(uint8 plotIndex);
|
||||
// Returns the plot index whose plot-bounds AT equals `atGuid`, or -1 when none.
|
||||
int8 GetPlotIndexForAreaTrigger(ObjectGuid atGuid) const;
|
||||
GameObject* GetPlotGameObject(uint8 plotIndex);
|
||||
void SetPlotOwnershipState(uint8 plotIndex, bool owned);
|
||||
AreaTrigger* SpawnPlotAreaTrigger(NeighborhoodPlotData const* plot);
|
||||
void DespawnPlotAreaTrigger(uint8 plotIndex);
|
||||
// Retail clears every world GameObject on a plot when it is bought and brings them back when freed.
|
||||
void SetPlotGroundCleared(NeighborhoodPlotData const* plot, bool cleared);
|
||||
// World position and yaw of a plot's room (its GameObjects.db2 plot object, turned half a revolution).
|
||||
bool GetPlotRoomFrame(uint8 plotIndex, Position& frame) const;
|
||||
bool IsSpawnSuppressed(SpawnObjectType type, ObjectGuid::LowType spawnId) const override;
|
||||
HousingPlotOwnerType GetPlotOwnerTypeForPlayer(Player const* player, uint8 plotIndex) const;
|
||||
void SendPerPlayerPlotWorldStates(Player* player);
|
||||
Neighborhood* GetNeighborhood() const { return _neighborhood; }
|
||||
uint32 GetNeighborhoodId() const { return _neighborhoodId; }
|
||||
|
||||
void LoadNeighborhoodData();
|
||||
void SpawnPlotGameObjects();
|
||||
void LockPlotGrids();
|
||||
|
||||
// Player housing instance tracking
|
||||
void AddPlayerHousing(ObjectGuid playerGuid, Housing* housing);
|
||||
void RemovePlayerHousing(ObjectGuid playerGuid);
|
||||
|
||||
// Fixture override map: hookID → ExteriorComponentID from the player's fixture selections.
|
||||
using FixtureOverrideMap = std::unordered_map<uint32 /*hookID*/, uint32 /*extCompID*/>;
|
||||
|
||||
// Root override map: componentType → componentID from the player's root fixture selections.
|
||||
using RootOverrideMap = std::unordered_map<uint8 /*componentType*/, uint32 /*compID*/>;
|
||||
|
||||
// House structure GO management
|
||||
GameObject* SpawnHouseForPlot(uint8 plotIndex, Position const* customPos,
|
||||
int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
FixtureOverrideMap const* fixtureOverrides = nullptr,
|
||||
RootOverrideMap const* rootOverrides = nullptr);
|
||||
void DespawnHouseForPlot(uint8 plotIndex);
|
||||
void DespawnDoorGO(uint8 plotIndex);
|
||||
GameObject* GetHouseGameObject(uint8 plotIndex);
|
||||
int8 GetPlotIndexForHouseGO(ObjectGuid goGuid) const;
|
||||
uint32 GetHouseGameObjectCount() const { return static_cast<uint32>(_houseGameObjects.size()); }
|
||||
|
||||
// House-exterior root Entity attached to the plot room; base and roof meshes hang off it (FHousingPlayerHouse_C.EntityGUID).
|
||||
HousingRoomEntity* GetHouseRootEntity(uint8 plotIndex) const;
|
||||
ObjectGuid GetHouseMirrorGuid(uint8 plotIndex) const;
|
||||
// Deterministic mirror-GUID derivation usable before the mirror exists; pieceIndex 0 is the Type-9 root mirror.
|
||||
ObjectGuid MakeHouseMirrorGuid(uint8 plotIndex, uint32 bnetAccountId, uint8 pieceIndex = 0) const;
|
||||
|
||||
// "Group B" per-piece mirrors paired with each visible exterior fixture MeshObject (Type 9/10/11/12); used by the client's spatial index.
|
||||
HousingMirrorEntity* GetHouseMeshMirror(uint8 plotIndex) const;
|
||||
ObjectGuid GetHouseMeshMirrorGuid(uint8 plotIndex) const;
|
||||
ObjectGuid MakeHouseMeshMirrorGuid(uint8 plotIndex, uint32 bnetAccountId, uint8 pieceIndex = 0) const;
|
||||
// Full list of per-piece Group B mirrors for this plot; empty if no house spawned.
|
||||
std::vector<HousingMirrorEntity*> GetHouseMeshMirrors(uint8 plotIndex) const;
|
||||
|
||||
// Lightweight Housing/2 identity room entity — the authoritative room the Group A mirrors attach to.
|
||||
HousingRoomEntity* GetRoomIdentityEntity(uint8 plotIndex) const;
|
||||
ObjectGuid GetRoomIdentityGuid(uint8 plotIndex) const;
|
||||
|
||||
// MeshObject management (housing fixture rendering)
|
||||
// pos: local-space position for child pieces (or world position for roots); worldPos: server-side grid placement
|
||||
MeshObject* SpawnHouseMeshObject(uint8 plotIndex, int32 fileDataID, bool isWMO,
|
||||
Position const& pos, QuaternionData const& rot, float scale,
|
||||
ObjectGuid houseGuid, int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
uint8 exteriorComponentType = 9, uint8 houseSize = 2, int32 exteriorComponentHookID = -1,
|
||||
ObjectGuid attachParent = ObjectGuid::Empty, uint8 attachFlags = 0,
|
||||
Position const* worldPos = nullptr);
|
||||
void SpawnFullHouseMeshObjects(uint8 plotIndex, Position const& housePos,
|
||||
QuaternionData const& houseRot, ObjectGuid houseGuid,
|
||||
int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
int32 factionRestriction = NEIGHBORHOOD_FACTION_ALLIANCE,
|
||||
FixtureOverrideMap const* fixtureOverrides = nullptr,
|
||||
RootOverrideMap const* rootOverrides = nullptr);
|
||||
void SpawnHordeHouseMeshObjects(uint8 plotIndex, Position const& housePos,
|
||||
QuaternionData const& houseRot, ObjectGuid houseGuid,
|
||||
int32 exteriorComponentID, int32 houseExteriorWmoDataID);
|
||||
uint32 SpawnExtCompTree(uint8 plotIndex, uint32 extCompID,
|
||||
Position const& pos, QuaternionData const& rot,
|
||||
ObjectGuid houseGuid, int32 houseExteriorWmoDataID,
|
||||
ObjectGuid parentGuid, Position const* worldPos, int32 depth = 0,
|
||||
FixtureOverrideMap const* fixtureOverrides = nullptr,
|
||||
int32 hookIDOverride = -1);
|
||||
void DespawnAllMeshObjectsForPlot(uint8 plotIndex);
|
||||
|
||||
// Targeted fixture mesh operations (no full house rebuild)
|
||||
// Finds and removes the MeshObject at the given hookID, sending DESTROY to nearby players.
|
||||
MeshObject* FindMeshObjectByHookID(uint8 plotIndex, int32 hookID);
|
||||
void DespawnSingleMeshObject(uint8 plotIndex, ObjectGuid meshGuid);
|
||||
// Spawn a single fixture at a hook; parentHint is the client-named parent mesh (variant re-keys invalidate the DB2 lookup).
|
||||
MeshObject* SpawnFixtureAtHook(uint8 plotIndex, uint32 hookID, uint32 componentID,
|
||||
ObjectGuid houseGuid, int32 houseExteriorWmoDataID, Player* target,
|
||||
ObjectGuid parentHint = ObjectGuid::Empty);
|
||||
|
||||
// Room entity management (provides Geobox for client OutsidePlotBounds check)
|
||||
void SpawnRoomForPlot(uint8 plotIndex, Position const& housePos,
|
||||
QuaternionData const& houseRot, ObjectGuid houseGuid);
|
||||
void DespawnRoomForPlot(uint8 plotIndex);
|
||||
void SpawnOrMoveHouseRootEntity(uint8 plotIndex, Position const& housePos, ObjectGuid rootGuid);
|
||||
void DespawnHouseRootEntity(uint8 plotIndex);
|
||||
|
||||
// Decor management. Functional decor (HouseDecorData.GameObjectID > 0) spawns as an interactive GameObject; visual-only decor as a MeshObject.
|
||||
bool SpawnDecorItem(uint8 plotIndex, Housing::PlacedDecor const& decor, ObjectGuid houseGuid);
|
||||
void DespawnDecorItem(uint8 plotIndex, ObjectGuid decorGuid);
|
||||
void DespawnAllDecorForPlot(uint8 plotIndex);
|
||||
void SpawnAllDecorForPlot(uint8 plotIndex, Housing const* housing);
|
||||
void UpdateDecorPosition(uint8 plotIndex, ObjectGuid decorGuid, Position const& pos, QuaternionData const& rot, float scale = 1.0f);
|
||||
|
||||
/// Re-point a decor's attachment to its current parent (parent decor object, or the plot's room
|
||||
/// identity once the stack link is gone); call before UpdateDecorPosition so the local transform
|
||||
/// uses the fresh anchor.
|
||||
void UpdateDecorAttachment(uint8 plotIndex, Housing::PlacedDecor const& decor);
|
||||
void UpdateDecorDyes(ObjectGuid decorGuid, std::array<uint32, MAX_HOUSING_DYE_SLOTS> const& dyeSlots);
|
||||
void UpdateDecorPet(ObjectGuid decorGuid, ObjectGuid battlePetGuid, uint32 creatureId, std::string const& petName, uint8 petBehavior);
|
||||
|
||||
// Track which plot a player is currently visiting (set by at_housing_plot)
|
||||
void SetPlayerCurrentPlot(ObjectGuid playerGuid, uint8 plotIndex) { _playerCurrentPlot[playerGuid] = plotIndex; }
|
||||
void ClearPlayerCurrentPlot(ObjectGuid playerGuid) { _playerCurrentPlot.erase(playerGuid); }
|
||||
int8 GetPlayerCurrentPlot(ObjectGuid playerGuid) const
|
||||
{
|
||||
auto itr = _playerCurrentPlot.find(playerGuid);
|
||||
return itr != _playerCurrentPlot.end() ? static_cast<int8>(itr->second) : -1;
|
||||
}
|
||||
|
||||
// Accessor for diagnostic logging (decor GUID → MeshObject GUID map)
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> const& GetDecorGuidMap() const { return _decorGuidToGoGuid; }
|
||||
|
||||
// Accessor for fixture MeshObjects (plotIndex → vector of MeshObject GUIDs)
|
||||
std::unordered_map<uint8, std::vector<ObjectGuid>> const& GetPlotMeshObjects() const { return _meshObjects; }
|
||||
|
||||
// Transmit a plot's house MeshObjects to everyone on this map; grid visibility does not deliver them.
|
||||
void SendPlotMeshObjectsToPlayers(uint8 plotIndex);
|
||||
|
||||
// Transmit a plot's geometry entities (room identity, Geobox mesh, root Entity, Group B mirrors) to one player.
|
||||
void SendPlotGeometryEntitiesToPlayer(uint8 plotIndex, Player* player);
|
||||
void SendPlotGeometryEntitiesToMap(uint8 plotIndex);
|
||||
|
||||
// Manual spell packet helpers — called from AddPlayerToMap and at_housing_plot AT script.
|
||||
// These spells don't exist in DB2, so CastSpell() silently fails; manual packets are required.
|
||||
void SendPlotEnterSpellPackets(Player* player, uint8 plotIndex);
|
||||
void SendPlotLeaveAuraRemoval(Player* player);
|
||||
|
||||
// Retail neighborhood-map-entry aura burst: four housing-specific AURA_UPDATE+SPELL_START+SPELL_GO triples.
|
||||
void SendNeighborhoodMapEntryAuras(Player* player);
|
||||
|
||||
private:
|
||||
uint32 _neighborhoodId;
|
||||
Neighborhood* _neighborhood;
|
||||
std::unordered_map<ObjectGuid, Housing*> _playerHousings;
|
||||
std::unordered_map<uint8, ObjectGuid> _plotAreaTriggers;
|
||||
std::unordered_map<uint8, ObjectGuid> _plotGameObjects;
|
||||
|
||||
// House structure GO tracking (plotIndex -> house GO GUID)
|
||||
std::unordered_map<uint8, ObjectGuid> _houseGameObjects;
|
||||
|
||||
// House-exterior root Entity per plot (see GetHouseRootEntity). Kept across house rebuilds like the plot room.
|
||||
std::unordered_map<uint8, ObjectGuid> _houseRootEntityGuids;
|
||||
|
||||
// World GameObject spawns inside each plot's bounds (cached) and the ones kept out while their plot is owned.
|
||||
std::unordered_map<uint8, std::vector<ObjectGuid::LowType>> _plotGroundSpawns;
|
||||
std::unordered_set<ObjectGuid::LowType> _suppressedPlotSpawns;
|
||||
|
||||
// "Group B" per-piece Entity mirrors, co-spawned with the house and despawned together.
|
||||
std::unordered_map<uint8, std::vector<std::unique_ptr<HousingMirrorEntity>>> _houseMeshMirrorEntities;
|
||||
|
||||
// Housing/2 identity room GUID per plot; the entity is owned by the map's object store.
|
||||
std::unordered_map<uint8, ObjectGuid> _roomIdentityGuids;
|
||||
|
||||
// MeshObject tracking (plotIndex -> vector of MeshObject GUIDs)
|
||||
std::unordered_map<uint8, std::vector<ObjectGuid>> _meshObjects;
|
||||
|
||||
// Room entity tracking (plotIndex -> room/component MeshObject GUIDs)
|
||||
std::unordered_map<uint8, ObjectGuid> _roomEntities; // room "entity" MeshObject
|
||||
std::unordered_map<uint8, ObjectGuid> _roomComponentMeshes; // room component MeshObject (has Geobox)
|
||||
|
||||
// Decor GO tracking
|
||||
std::unordered_map<uint8, std::vector<ObjectGuid>> _decorGameObjects; // plotIndex -> decor GO GUIDs
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> _decorGuidToGoGuid; // decor GUID -> GO GUID
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> _decorAttachParentObj; // decor GUID -> parent decor's client object (snapped stacks)
|
||||
std::unordered_map<ObjectGuid, ObjectGuid> _decorGuidToPetSummon; // decor GUID -> companion battle pet creature
|
||||
std::unordered_map<ObjectGuid, uint8> _decorGuidToPlotIndex; // decor GUID -> plotIndex
|
||||
std::unordered_set<uint8> _decorSpawnedPlots; // plots whose decor has been spawned
|
||||
std::unordered_map<ObjectGuid, uint8> _playerCurrentPlot; // player GUID -> current visited plot index
|
||||
};
|
||||
|
||||
#endif // HousingMap_h__
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingMapContext.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "Map.h"
|
||||
#include "MapManager.h"
|
||||
#include "Player.h"
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
bool IsHousingMap(uint32 mapId)
|
||||
{
|
||||
return IsExteriorMapId(mapId) || mapId == HouseInteriorMapId;
|
||||
}
|
||||
|
||||
bool CanEnterMapContext(Player const* player, uint32 mapId, ObjectGuid const& context)
|
||||
{
|
||||
if (!player || !IsHousingMap(mapId))
|
||||
return false;
|
||||
|
||||
uint64 const owner = player->GetGUID().GetCounter();
|
||||
if (mapId == HouseInteriorMapId)
|
||||
{
|
||||
if (!IsHouseGuid(context))
|
||||
return false;
|
||||
|
||||
HouseState house;
|
||||
ObjectGuid houseOwner;
|
||||
if (sHousingMgr.LoadHouse(context, house, &houseOwner) != Result::Success)
|
||||
return false;
|
||||
QueryResult interior = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_plot_template WHERE ExteriorMapID = {} AND PlotID = {} "
|
||||
"AND InteriorMapID = {} LIMIT 1", house.ExteriorMapId, uint32(house.PlotId), mapId);
|
||||
return interior && sHousingMgr.CanVisitorAccessPlot(
|
||||
player, houseOwner, house.NeighborhoodGuid, house.SettingsFlags, true);
|
||||
}
|
||||
|
||||
uint32 const factionMap = player->GetTeamId() == TEAM_HORDE ? HordeExteriorMapId : AllianceExteriorMapId;
|
||||
if (mapId != factionMap)
|
||||
return false;
|
||||
|
||||
// The retail tutorial uses the faction exterior before a neighborhood
|
||||
// exists. It remains the shared exterior map with no house context.
|
||||
if (context.IsEmpty())
|
||||
return true;
|
||||
|
||||
if (!IsNeighborhoodGuid(context))
|
||||
return false;
|
||||
|
||||
QueryResult neighborhood = CharacterDatabase.PQuery(
|
||||
"SELECT OwnerGuid FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1",
|
||||
context.ToString());
|
||||
if (!neighborhood)
|
||||
return false;
|
||||
if (neighborhood->Fetch()[0].GetUInt64() == owner)
|
||||
return true;
|
||||
|
||||
QueryResult houses = CharacterDatabase.PQuery(
|
||||
"SELECT OwnerGuid, HouseSettingFlags FROM character_housing "
|
||||
"WHERE NeighborhoodGuid = '{}' AND ExteriorMapID = {} ORDER BY PlotID",
|
||||
context.ToString(), mapId);
|
||||
if (!houses)
|
||||
return false;
|
||||
do
|
||||
{
|
||||
Field* fields = houses->Fetch();
|
||||
ObjectGuid const houseOwner = ObjectGuid::Create<HighGuid::Player>(fields[0].GetUInt64());
|
||||
if (sHousingMgr.CanVisitorAccessPlot(player, houseOwner, context, fields[1].GetUInt32(), false))
|
||||
return true;
|
||||
} while (houses->NextRow());
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectGuid LoadMapContext(Player const* player, uint32 mapId)
|
||||
{
|
||||
if (!player || !IsHousingMap(mapId))
|
||||
return ObjectGuid::Empty;
|
||||
|
||||
QueryResult saved = CharacterDatabase.PQuery(
|
||||
"SELECT ContextGuid FROM character_housing_map_context WHERE OwnerGuid = {} AND MapID = {}",
|
||||
player->GetGUID().GetCounter(), mapId);
|
||||
if (saved)
|
||||
{
|
||||
ObjectGuid const context = ObjectGuid::FromString(saved->Fetch()[0].GetString());
|
||||
if (CanEnterMapContext(player, mapId, context))
|
||||
return context;
|
||||
// A stale binding must not silently redirect to a different house.
|
||||
return ObjectGuid::Empty;
|
||||
}
|
||||
|
||||
// Upgrade path for characters saved in Housing before context persistence.
|
||||
// Resolve only their own house, never a plot belonging to another player.
|
||||
QueryResult owned = CharacterDatabase.PQuery(
|
||||
"SELECT h.HouseGuid, h.NeighborhoodGuid FROM character_housing h "
|
||||
"INNER JOIN housing_plot_template p ON p.PlotID = h.PlotID AND p.ExteriorMapID = h.ExteriorMapID "
|
||||
"WHERE h.OwnerGuid = {} AND (h.ExteriorMapID = {} OR p.InteriorMapID = {}) "
|
||||
"ORDER BY h.HouseGuid LIMIT 1", player->GetGUID().GetCounter(), mapId, mapId);
|
||||
if (owned)
|
||||
{
|
||||
ObjectGuid const context = ObjectGuid::FromString(owned->Fetch()[mapId == HouseInteriorMapId ? 0 : 1].GetString());
|
||||
if (CanEnterMapContext(player, mapId, context))
|
||||
return context;
|
||||
}
|
||||
|
||||
if (IsExteriorMapId(mapId))
|
||||
{
|
||||
QueryResult neighborhood = CharacterDatabase.PQuery(
|
||||
"SELECT NeighborhoodGuid FROM housing_neighborhood WHERE OwnerGuid = {} ORDER BY NeighborhoodGuid LIMIT 1",
|
||||
player->GetGUID().GetCounter());
|
||||
if (neighborhood)
|
||||
{
|
||||
ObjectGuid const context = ObjectGuid::FromString(neighborhood->Fetch()[0].GetString());
|
||||
if (CanEnterMapContext(player, mapId, context))
|
||||
return context;
|
||||
}
|
||||
}
|
||||
return ObjectGuid::Empty;
|
||||
}
|
||||
|
||||
uint32 LoadMapInstanceId(uint32 mapId, ObjectGuid const& context, bool create)
|
||||
{
|
||||
if (!IsHousingMap(mapId) || (IsExteriorMapId(mapId) ? (!context.IsEmpty() && !IsNeighborhoodGuid(context)) : !IsHouseGuid(context)))
|
||||
return 0;
|
||||
if (IsExteriorMapId(mapId) && context.IsEmpty())
|
||||
return 0;
|
||||
|
||||
// Instance IDs are durable and unique in Housing maps. They are not
|
||||
// dungeon lock IDs and must never be returned to the dungeon ID allocator.
|
||||
if (create)
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT IGNORE INTO housing_map_instance (MapID, ContextGuid) VALUES ({}, '{}')",
|
||||
mapId, context.ToString());
|
||||
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT InstanceID FROM housing_map_instance WHERE MapID = {} AND ContextGuid = '{}'",
|
||||
mapId, context.ToString());
|
||||
return result ? (0x40000000u | result->Fetch()[0].GetUInt32()) : 0;
|
||||
}
|
||||
|
||||
bool SaveMapContext(Player const* player, Map const* map)
|
||||
{
|
||||
if (!player || !map || !CanEnterMapContext(player, map->GetId(), map->GetHousingContextGuid()))
|
||||
return false;
|
||||
if (map->GetHousingContextGuid().IsEmpty())
|
||||
return true;
|
||||
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"INSERT INTO character_housing_map_context (OwnerGuid, MapID, ContextGuid) VALUES ({}, {}, '{}') "
|
||||
"ON DUPLICATE KEY UPDATE ContextGuid = VALUES(ContextGuid)",
|
||||
player->GetGUID().GetCounter(), map->GetId(), map->GetHousingContextGuid().ToString());
|
||||
return LoadMapContext(player, map->GetId()) == map->GetHousingContextGuid();
|
||||
}
|
||||
|
||||
bool IsHouseInMap(Map const* map, ObjectGuid const& houseGuid)
|
||||
{
|
||||
if (!map || !IsHouseGuid(houseGuid) || map->GetHousingContextGuid().IsEmpty())
|
||||
return false;
|
||||
if (map->GetId() == HouseInteriorMapId)
|
||||
return map->GetHousingContextGuid() == houseGuid;
|
||||
if (!IsExteriorMapId(map->GetId()))
|
||||
return false;
|
||||
return bool(CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM character_housing WHERE HouseGuid = '{}' AND NeighborhoodGuid = '{}' "
|
||||
"AND ExteriorMapID = {} LIMIT 1", houseGuid.ToString(), map->GetHousingContextGuid().ToString(), map->GetId()));
|
||||
}
|
||||
|
||||
bool TeleportToMapContext(Player* player, uint32 mapId, ObjectGuid const& context,
|
||||
float x, float y, float z, float orientation)
|
||||
{
|
||||
if (!player || player->IsBeingTeleported() || !MapManager::IsValidMapCoord(mapId, x, y, z, orientation))
|
||||
return false;
|
||||
if (context.IsEmpty())
|
||||
return player->TeleportTo(mapId, x, y, z, orientation);
|
||||
|
||||
Map* destination = sMapMgr->CreateHousingMap(mapId, player, context);
|
||||
if (!destination)
|
||||
return false;
|
||||
// Explicit instance selection is necessary even when both neighborhoods
|
||||
// use the same map ID: it forces the core's world-transfer path.
|
||||
return player->TeleportTo(mapId, x, y, z, orientation, TELE_TO_NONE, destination->GetInstanceId());
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_MAP_CONTEXT_H
|
||||
#define TRINITYCORE_HOUSING_MAP_CONTEXT_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectGuid.h"
|
||||
|
||||
class Map;
|
||||
class Player;
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
constexpr uint32 HouseInteriorMapId = 2783;
|
||||
|
||||
TC_GAME_API bool IsHousingMap(uint32 mapId);
|
||||
TC_GAME_API bool CanEnterMapContext(Player const* player, uint32 mapId, ObjectGuid const& context);
|
||||
TC_GAME_API ObjectGuid LoadMapContext(Player const* player, uint32 mapId);
|
||||
TC_GAME_API uint32 LoadMapInstanceId(uint32 mapId, ObjectGuid const& context, bool create);
|
||||
TC_GAME_API bool SaveMapContext(Player const* player, Map const* map);
|
||||
TC_GAME_API bool IsHouseInMap(Map const* map, ObjectGuid const& houseGuid);
|
||||
TC_GAME_API bool TeleportToMapContext(Player* player, uint32 mapId, ObjectGuid const& context,
|
||||
float x, float y, float z, float orientation);
|
||||
}
|
||||
|
||||
#endif
|
||||
+1509
-552
File diff suppressed because it is too large
Load Diff
@@ -1,51 +1,473 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HOUSING_MGR_H
|
||||
#define TRINITYCORE_HOUSING_MGR_H
|
||||
#ifndef HousingMgr_h__
|
||||
#define HousingMgr_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingState.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "Optional.h"
|
||||
#include "Position.h"
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
class Player;
|
||||
class Neighborhood;
|
||||
class WorldObject;
|
||||
struct ExteriorComponentEntry;
|
||||
struct ExteriorComponentExitPointEntry;
|
||||
struct ExteriorComponentHookEntry;
|
||||
struct RoomComponentOptionEntry;
|
||||
struct RoomComponentTextureEntry;
|
||||
|
||||
namespace Housing
|
||||
struct HouseDecorData
|
||||
{
|
||||
class TC_GAME_API Manager
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
float InitialRotation[3] = {};
|
||||
int32 GameObjectID = 0;
|
||||
int32 Flags = 0;
|
||||
uint8 Type = 0;
|
||||
uint8 ModelType = 0;
|
||||
int32 ModelFileDataID = 0;
|
||||
int32 ThumbnailFileDataID = 0;
|
||||
int32 WeightCost = 1;
|
||||
int32 ItemID = 0;
|
||||
float InitialScale = 1.0f;
|
||||
int32 FirstAcquisitionBonus = 0; // House XP gained on first acquisition (from Lua API)
|
||||
int32 OrderIndex = 0;
|
||||
int8 Size = 0; // HousingCatalogEntrySize (inferred from Lua API)
|
||||
int32 StartingQuantity = 0;
|
||||
int32 UiModelSceneID = 0;
|
||||
};
|
||||
|
||||
struct HouseLevelData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
int32 Level = 0;
|
||||
int32 QuestID = 0;
|
||||
};
|
||||
|
||||
struct HouseRoomData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
int8 Size = 0;
|
||||
int32 Flags = 0; // HousingRoomFlags bitmask
|
||||
int32 Field_002 = 0;
|
||||
int32 RoomWmoDataID = 0;
|
||||
int32 UiTextureAtlasElementID = 0;
|
||||
int32 WeightCost = 1;
|
||||
|
||||
bool IsBaseRoom() const { return (Flags & HOUSING_ROOM_FLAG_BASE_ROOM) != 0; }
|
||||
bool HasStairs() const { return (Flags & HOUSING_ROOM_FLAG_HAS_STAIRS) != 0; }
|
||||
};
|
||||
|
||||
struct RoomDoorInfo
|
||||
{
|
||||
uint32 RoomComponentID = 0;
|
||||
float OffsetPos[3] = {};
|
||||
float OffsetRot[3] = {};
|
||||
uint8 ConnectionType = 0; // RoomConnectionType
|
||||
};
|
||||
|
||||
/// Full room component data (all types: wall, floor, ceiling, stairs, pillar, doorway)
|
||||
struct RoomComponentData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
uint32 RoomWmoDataID = 0;
|
||||
float OffsetPos[3] = {};
|
||||
float OffsetRot[3] = {};
|
||||
int32 ModelFileDataID = 0; // The WMO/mesh file to spawn
|
||||
uint8 Type = 0; // HousingRoomComponentType
|
||||
int32 MeshStyleFilterID = 0;
|
||||
uint8 ConnectionType = 0; // RoomConnectionType (for doorways)
|
||||
int32 Flags = 0;
|
||||
};
|
||||
|
||||
struct HouseThemeData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
int32 Flags = 0; // DB2: Flags
|
||||
int32 ParentThemeID = 0; // DB2: ParentThemeID FK->HouseTheme
|
||||
};
|
||||
|
||||
struct HouseDecorThemeSetData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
int32 HouseThemeID = 0;
|
||||
int32 HouseDecorCategoryID = 0;
|
||||
};
|
||||
|
||||
struct NeighborhoodMapData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
float Origin[3] = {}; // DB2: Position[3]
|
||||
int32 MapID = 0; // DB2: MapID
|
||||
float EntryRotation = 0.0f; // DB2: EntryRotation
|
||||
uint32 UiTextureKitID = 0; // DB2: UiTextureKitID
|
||||
int32 Flags = 0; // DB2: Flags (bitmask: 0x1=Alliance, 0x2=Horde, 0x4=SystemGenerate)
|
||||
};
|
||||
|
||||
struct NeighborhoodPlotData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
uint64 Cost = 0;
|
||||
std::string Name;
|
||||
float HousePosition[3] = {};
|
||||
float HouseRotation[3] = {};
|
||||
float CornerstonePosition[3] = {};
|
||||
float CornerstoneRotation[3] = {};
|
||||
float TeleportPosition[3] = {};
|
||||
int32 NeighborhoodMapID = 0;
|
||||
int32 Field_010 = 0;
|
||||
int32 CornerstoneGameObjectID = 0;
|
||||
int32 PlotIndex = 0;
|
||||
int32 WorldState = 0;
|
||||
int32 PlotGameObjectID = 0;
|
||||
float TeleportFacing = 0.0f; // Facing angle at TeleportPosition
|
||||
int32 Field_016 = 0;
|
||||
};
|
||||
|
||||
struct NeighborhoodNameGenData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Prefix;
|
||||
std::string Middle;
|
||||
std::string Suffix;
|
||||
int32 NeighborhoodMapID = 0;
|
||||
};
|
||||
|
||||
struct HouseDecorMaterialData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
uint64 WMOMaterialReference = 0; // DB2: WMOMaterialReference
|
||||
int32 MaterialTextureIndex = 0; // DB2: MaterialTextureIndex
|
||||
int32 HouseThemeID = 0; // DB2: HouseThemeID FK->HouseTheme
|
||||
int32 TextureAFileDataID = 0; // DB2: TextureAFileDataID FK->FileData
|
||||
int32 TextureBFileDataID = 0; // DB2: TextureBFileDataID FK->FileData
|
||||
};
|
||||
|
||||
struct HouseExteriorWmoData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
int32 Flags = 0;
|
||||
};
|
||||
|
||||
struct HouseLevelRewardInfoData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
std::string Description;
|
||||
int32 HouseLevelDataID = 0; // DB2: HouseLevelDataID FK->HouseLevelData
|
||||
int32 Field_4 = 0; // DB2: Field_12_0_0_63967_004
|
||||
int32 IconFileDataID = 0; // DB2: IconFileDataID FK->FileData
|
||||
};
|
||||
|
||||
struct NeighborhoodInitiativeData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
std::string Description;
|
||||
int32 InitiativeType = 0;
|
||||
int32 Duration = 0;
|
||||
int32 RequiredParticipants = 0;
|
||||
int32 RewardCurrencyID = 0;
|
||||
};
|
||||
|
||||
struct DecorCategoryData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
int32 UiTextureAtlasElementID = 0; // DB2: UiTextureAtlasElementID
|
||||
int32 OrderIndex = 0; // DB2: OrderIndex
|
||||
};
|
||||
|
||||
struct DecorSubcategoryData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
std::string Name;
|
||||
int32 UiTextureAtlasElementID = 0; // DB2: UiTextureAtlasElementID
|
||||
int32 DecorCategoryID = 0;
|
||||
int32 OrderIndex = 0; // DB2: OrderIndex
|
||||
};
|
||||
|
||||
struct DecorDyeSlotData
|
||||
{
|
||||
uint32 ID = 0;
|
||||
int32 DyeColorCategoryID = 0; // DB2: DyeColorCategoryID FK->DyeColorCategory
|
||||
int32 HouseDecorID = 0;
|
||||
int32 OrderIndex = 0; // DB2: OrderIndex
|
||||
int32 Channel = 0; // DB2: Channel
|
||||
};
|
||||
|
||||
class TC_GAME_API HousingMgr
|
||||
{
|
||||
public:
|
||||
HousingMgr();
|
||||
HousingMgr(HousingMgr const&) = delete;
|
||||
HousingMgr(HousingMgr&&) = delete;
|
||||
HousingMgr& operator=(HousingMgr const&) = delete;
|
||||
HousingMgr& operator=(HousingMgr&&) = delete;
|
||||
~HousingMgr();
|
||||
|
||||
static HousingMgr& Instance();
|
||||
|
||||
void Initialize();
|
||||
|
||||
// DB2 data accessors
|
||||
HouseDecorData const* GetHouseDecorData(uint32 id) const;
|
||||
HouseLevelData const* GetLevelData(uint32 level) const;
|
||||
HouseRoomData const* GetHouseRoomData(uint32 id) const;
|
||||
HouseThemeData const* GetHouseThemeData(uint32 id) const;
|
||||
HouseDecorThemeSetData const* GetHouseDecorThemeSetData(uint32 id) const;
|
||||
NeighborhoodMapData const* GetNeighborhoodMapData(uint32 id) const;
|
||||
std::unordered_map<uint32, NeighborhoodMapData> const& GetAllNeighborhoodMapData() const { return _neighborhoodMapStore; }
|
||||
HouseDecorMaterialData const* GetHouseDecorMaterialData(uint32 id) const;
|
||||
HouseExteriorWmoData const* GetHouseExteriorWmoData(uint32 id) const;
|
||||
HouseLevelRewardInfoData const* GetHouseLevelRewardInfoData(uint32 id) const;
|
||||
NeighborhoodInitiativeData const* GetNeighborhoodInitiativeData(uint32 id) const;
|
||||
DecorCategoryData const* GetDecorCategoryData(uint32 id) const;
|
||||
DecorSubcategoryData const* GetDecorSubcategoryData(uint32 id) const;
|
||||
|
||||
// Classify a HouseDecor via DecorXDecorSubcategory -> DecorSubcategory.DecorCategoryID; 0 when unlinked (lighting = 4).
|
||||
uint32 GetDecorCategoryForDecor(uint32 decorId) const;
|
||||
bool IsLightingDecor(uint32 decorId) const;
|
||||
|
||||
// Indexed lookups
|
||||
std::vector<DecorSubcategoryData const*> GetSubcategoriesForCategory(uint32 categoryId) const;
|
||||
std::vector<uint32> GetDecorIdsForSubcategory(uint32 subcategoryId) const;
|
||||
std::vector<DecorDyeSlotData const*> GetDyeSlotsForDecor(uint32 houseDecorId) const;
|
||||
std::vector<HouseDecorMaterialData const*> GetMaterialsForTheme(uint32 houseThemeId) const;
|
||||
std::vector<HouseLevelRewardInfoData const*> GetRewardsForLevel(uint32 houseLevelId) const;
|
||||
|
||||
// Neighborhood plot lookups
|
||||
std::vector<NeighborhoodPlotData const*> const& GetPlotsForMap(uint32 neighborhoodMapId) const;
|
||||
/// Where a house nobody moved stands: the plot's centre, turned like the plot room.
|
||||
Position GetDefaultHousePosition(NeighborhoodPlotData const& plot) const;
|
||||
/// Where a housing teleport lands on a plot: TeleportPosition, facing CornerstoneRotation.Z.
|
||||
static WorldLocation GetPlotTeleportLocation(uint32 worldMapId, NeighborhoodPlotData const& plot);
|
||||
/// Destination of a housing teleport spell (SPELL_HOUSING_TELEPORT_HOME / _VISIT_HOUSE) while it is being cast.
|
||||
struct PendingPlotTeleport
|
||||
{
|
||||
public:
|
||||
static Manager& Instance();
|
||||
|
||||
uint32 GetExteriorMapId(Player const* player) const;
|
||||
bool IsPlayerOnHousingExterior(Player const* player, uint32* exteriorMapId = nullptr) const;
|
||||
bool InitializePlotTemplates(uint32 exteriorMapId) const;
|
||||
|
||||
Result LoadOwnedHouse(Player const* player, ObjectGuid const& requestedHouse, HouseState& state) const;
|
||||
Result LoadHouse(ObjectGuid const& houseGuid, HouseState& state, ObjectGuid* ownerGuid = nullptr) const;
|
||||
std::vector<HouseState> LoadOwnedHouses(Player const* player) const;
|
||||
Result LoadPlot(Player const* player, uint8 plotId, PlotState& state) const;
|
||||
|
||||
bool CanVisitorAccessPlot(Player const* visitor, ObjectGuid const& ownerGuid,
|
||||
ObjectGuid const& neighborhoodGuid, uint32 settingsFlags, bool interior) const;
|
||||
|
||||
Result UpdateOwnedHouseSettings(Player const* player, ObjectGuid const& houseGuid, uint32 settingsFlags, HouseState& state) const;
|
||||
Result ReservePlot(Player const* player, ObjectGuid const& neighborhoodGuid, uint8 plotId, bool reserve) const;
|
||||
Result CommitReservedPlot(Player* player, uint8 plotId, HouseState& state,
|
||||
uint64* chargedCost = nullptr, bool* moved = nullptr) const;
|
||||
Result CommitReservedPlot(Player* player, ObjectGuid const& neighborhoodGuid, uint8 plotId,
|
||||
HouseState& state, uint64* chargedCost = nullptr, bool* moved = nullptr) const;
|
||||
Result RelinquishOwnedHouse(Player const* player, ObjectGuid const& houseGuid, HouseState* previousState = nullptr) const;
|
||||
|
||||
private:
|
||||
Result CommitReservedPlotLocked(Player* player, ObjectGuid const& neighborhoodGuid, uint8 plotId,
|
||||
HouseState& state, uint64* chargedCost, bool* moved) const;
|
||||
Manager() = default;
|
||||
WorldLocation Dest;
|
||||
uint32 NeighborhoodId = 0;
|
||||
};
|
||||
}
|
||||
void SetPendingPlotTeleport(ObjectGuid playerGuid, WorldLocation const& dest, uint32 neighborhoodId);
|
||||
Optional<PendingPlotTeleport> TakePendingPlotTeleport(ObjectGuid playerGuid);
|
||||
// Find a plot by its cornerstone GO entry within a specific neighborhood map
|
||||
NeighborhoodPlotData const* GetPlotByCornerstoneEntry(uint32 neighborhoodMapId, uint32 cornerstoneGoEntry) const;
|
||||
|
||||
#define sHousingMgr Housing::Manager::Instance()
|
||||
// Resolves the DB2 PlotIndex from a client-supplied cornerstone GO GUID (sent as "NeighborhoodGuid" in many CMSGs); -1 on failure.
|
||||
int32 ResolvePlotIndex(WorldObject const* searcher, ObjectGuid cornerstoneGuid, Neighborhood const* neighborhood) const;
|
||||
|
||||
// Get the NeighborhoodMapData for a world MapID (returns nullptr if not a neighborhood)
|
||||
NeighborhoodMapData const* GetNeighborhoodMapDataForWorldMap(uint32 mapId) const;
|
||||
|
||||
// Check if a world MapID corresponds to a neighborhood map
|
||||
bool IsNeighborhoodWorldMap(uint32 mapId) const;
|
||||
// Get the NeighborhoodMapID for a world MapID (returns 0 if not a neighborhood)
|
||||
uint32 GetNeighborhoodMapIdByWorldMap(uint32 mapId) const;
|
||||
// Get the world MapID for a NeighborhoodMapID (reverse lookup, returns 0 if not found)
|
||||
uint32 GetWorldMapIdByNeighborhoodMapId(uint32 neighborhoodMapId) const;
|
||||
|
||||
// Name generation
|
||||
std::string GenerateNeighborhoodName(uint32 neighborhoodMapId) const;
|
||||
|
||||
// Budget accessors (WeightCost-based)
|
||||
uint32 GetQuestForLevel(uint32 level) const;
|
||||
// Cumulative lifetime Favor needed to unlock `level` (retail C_Housing.GetHouseLevelFavorForLevel).
|
||||
uint32 GetFavorThresholdForLevel(uint32 level) const;
|
||||
uint32 GetInteriorDecorBudgetForLevel(uint32 level) const;
|
||||
uint32 GetExteriorDecorBudgetForLevel(uint32 level) const;
|
||||
uint32 GetRoomBudgetForLevel(uint32 level) const;
|
||||
uint32 GetFixtureBudgetForLevel(uint32 level) const;
|
||||
uint32 GetDecorWeightCost(uint32 decorEntryId) const;
|
||||
uint32 GetRoomWeightCost(uint32 roomEntryId) const;
|
||||
|
||||
// Room connectivity
|
||||
bool IsBaseRoom(uint32 roomEntryId) const;
|
||||
uint32 GetRoomDoorCount(uint32 roomEntryId) const;
|
||||
std::vector<RoomDoorInfo> const* GetRoomDoors(uint32 roomWmoDataId) const;
|
||||
|
||||
// Room component data (all types: wall, floor, ceiling, stairs, doorway, etc.)
|
||||
std::vector<RoomComponentData> const* GetRoomComponents(uint32 roomWmoDataId) const;
|
||||
|
||||
// Faction-to-theme mapping: Alliance -> Folk (base theme 1), Horde -> Rugged (base theme 2)
|
||||
int32 GetFactionDefaultThemeID(int32 factionRestriction) const;
|
||||
int32 GetDefaultSubThemeID(int32 baseThemeID) const;
|
||||
int32 GetBaseThemeID(int32 themeID) const;
|
||||
|
||||
// RoomComponentOption matching a MeshStyleFilterID + theme; nullptr if no match.
|
||||
RoomComponentOptionEntry const* FindRoomComponentOption(int32 meshStyleFilterID, int32 houseThemeID) const;
|
||||
// houseThemeID 0 matches every theme
|
||||
std::vector<RoomComponentOptionEntry const*> FindAllRoomComponentOptions(int32 meshStyleFilterID, int32 houseThemeID) const;
|
||||
|
||||
// Get the base room entry ID (exterior geobox room, from DB2 IsBaseRoom flag, fallback 18)
|
||||
uint32 GetBaseRoomEntryId() const { return _baseRoomEntryId; }
|
||||
|
||||
// Entry hall room entry ID (interior base room; Room 18 = exterior plot geobox, Room 46 = interior entry hall)
|
||||
uint32 GetEntryHallRoomEntryId() const { return _entryHallRoomEntryId; }
|
||||
|
||||
// First matched RoomComponentTexture ID for a RoomComponentOption, or 0 if no DB2 link exists.
|
||||
int32 GetTextureIdForComponentOption(int32 roomComponentOptionID) const;
|
||||
// Fallback: first matching texture per component type (1=wall, 2=floor, 3=ceiling).
|
||||
int32 GetTextureIdForComponentType(uint8 componentType) const;
|
||||
|
||||
// ExteriorComponent indexed lookups
|
||||
std::vector<ExteriorComponentHookEntry const*> const* GetHooksOnComponent(uint32 extCompID) const;
|
||||
ExteriorComponentExitPointEntry const* GetExitPoint(uint32 extCompID) const;
|
||||
int32 GetGroupForComponent(uint32 extCompID) const;
|
||||
std::vector<uint32> const* GetChildComponents(uint32 parentCompID) const;
|
||||
std::vector<uint32> const* GetRootComponentsForWmoData(uint32 wmoDataID) const;
|
||||
std::vector<uint32> const* GetComponentsInGroup(int32 groupID) const;
|
||||
|
||||
// Default fixture component for a hook's component type + house WmoDataID (Flags & 0x1, ParentComponentID == 0); houseSize 0 = any size.
|
||||
uint32 GetDefaultFixtureForType(uint8 componentType, uint32 wmoDataID, uint8 houseSize = 0) const;
|
||||
|
||||
// A style can only be built at a size it has Base and Roof components for (item-unlock facades are Small-only).
|
||||
bool IsHouseSizeAvailableForType(uint32 wmoDataID, uint8 houseSize) const;
|
||||
// Largest size up to maxSize the style has (HOUSING_FIXTURE_SIZE_NONE if none): where a style change shrinks the house to.
|
||||
uint8 GetLargestHouseSizeForType(uint32 wmoDataID, uint8 maxSize) const;
|
||||
|
||||
// Racial house style: maps player race to the HouseExteriorWmoDataID (Night Elf 55, Blood Elf 56, other Alliance 9, other Horde 87).
|
||||
static uint32 GetRacialWmoDataID(uint8 race, uint32 teamId);
|
||||
|
||||
// HouseExteriorWMOData.Flags limit a house type to Horde and/or Alliance neighborhoods; the editor's own faction does not matter.
|
||||
static bool IsHouseTypeAllowedInNeighborhood(int32 wmoDataFlags, int32 neighborhoodFaction);
|
||||
|
||||
// Find the first HouseRoom entry with visual components (not the base room 18)
|
||||
uint32 GetDefaultVisualRoomEntry() const;
|
||||
|
||||
// Starter decor (items granted on first purchase): every StartingQuantity > 0 row, no faction filter.
|
||||
std::vector<std::pair<uint32, int32>> GetStarterDecorWithQuantities(uint32 teamId) const;
|
||||
|
||||
// Checks if a visitor can access a plot/house per the owner's settings; works with the owner offline.
|
||||
bool CanVisitorAccessPlot(Player const* visitor, ObjectGuid ownerGuid, uint32 settingsFlags, bool isInterior) const;
|
||||
// May this visitor save the house as a blueprint; nobody but the owner unless the owner opted in.
|
||||
bool CanVisitorExportBlueprint(Player const* visitor, ObjectGuid ownerGuid, uint32 settingsFlags) const;
|
||||
|
||||
// Validation
|
||||
HousingResult ValidateDecorPlacement(uint32 decorId, Position const& pos, Position const& anchor, uint32 houseLevel) const;
|
||||
|
||||
// House-finder per-player ignore list; lazily loaded from DB, write-through on mutation.
|
||||
bool IsNeighborhoodIgnored(ObjectGuid playerGuid, ObjectGuid neighborhoodGuid);
|
||||
void AddIgnoredNeighborhood(ObjectGuid playerGuid, ObjectGuid neighborhoodGuid);
|
||||
void RemoveIgnoredNeighborhood(ObjectGuid playerGuid, ObjectGuid neighborhoodGuid);
|
||||
|
||||
private:
|
||||
std::mutex _pendingPlotTeleportsLock;
|
||||
std::unordered_map<ObjectGuid, PendingPlotTeleport> _pendingPlotTeleports;
|
||||
// Ensure the player's ignore set is loaded from DB into _ignoredNeighborhoods.
|
||||
std::unordered_set<ObjectGuid>& EnsureIgnoredNeighborhoodsLoaded(ObjectGuid playerGuid);
|
||||
void LoadHouseDecorData();
|
||||
void LoadHouseLevelData();
|
||||
void LoadHouseRoomData();
|
||||
void LoadHouseThemeData();
|
||||
void LoadHouseDecorThemeSetData();
|
||||
void LoadNeighborhoodMapData();
|
||||
void LoadNeighborhoodPlotData();
|
||||
void LoadNeighborhoodNameGenData();
|
||||
void LoadHouseDecorMaterialData();
|
||||
void LoadHouseExteriorWmoData();
|
||||
void LoadHouseLevelRewardInfoData();
|
||||
void LoadNeighborhoodInitiativeData();
|
||||
void LoadRoomComponentData();
|
||||
void LoadDecorCategoryData();
|
||||
void LoadDecorSubcategoryData();
|
||||
void LoadDecorDyeSlotData();
|
||||
void LoadDecorXDecorSubcategoryData();
|
||||
|
||||
// DB2 data stores indexed by ID
|
||||
std::unordered_map<uint32, HouseDecorData> _houseDecorStore;
|
||||
std::unordered_map<uint32, HouseLevelData> _houseLevelDataStore;
|
||||
std::unordered_map<uint32, HouseRoomData> _houseRoomStore;
|
||||
std::unordered_map<uint32, HouseThemeData> _houseThemeStore;
|
||||
std::unordered_map<uint32, HouseDecorThemeSetData> _houseDecorThemeSetStore;
|
||||
std::unordered_map<uint32, NeighborhoodMapData> _neighborhoodMapStore;
|
||||
std::unordered_map<uint32, NeighborhoodPlotData> _neighborhoodPlotStore;
|
||||
std::unordered_map<uint32, HouseDecorMaterialData> _houseDecorMaterialStore;
|
||||
std::unordered_map<uint32, HouseExteriorWmoData> _houseExteriorWmoStore;
|
||||
std::unordered_map<uint32, HouseLevelRewardInfoData> _houseLevelRewardInfoStore;
|
||||
std::unordered_map<uint32, NeighborhoodInitiativeData> _neighborhoodInitiativeStore;
|
||||
std::unordered_map<uint32, DecorCategoryData> _decorCategoryStore;
|
||||
std::unordered_map<uint32, DecorSubcategoryData> _decorSubcategoryStore;
|
||||
std::unordered_map<uint32, DecorDyeSlotData> _decorDyeSlotStore;
|
||||
|
||||
// Lookup indexes
|
||||
std::unordered_map<uint32 /*neighborhoodMapId*/, std::vector<NeighborhoodPlotData const*>> _plotsByMap;
|
||||
std::unordered_map<uint32 /*neighborhoodMapId*/, std::vector<NeighborhoodNameGenData>> _nameGenByMap;
|
||||
std::unordered_map<uint32 /*level*/, HouseLevelData const*> _levelDataByLevel;
|
||||
std::unordered_map<uint32 /*houseThemeId*/, std::vector<HouseDecorMaterialData const*>> _materialsByTheme;
|
||||
std::unordered_map<uint32 /*houseLevelId*/, std::vector<HouseLevelRewardInfoData const*>> _rewardsByLevel;
|
||||
std::unordered_map<uint32 /*categoryId*/, std::vector<DecorSubcategoryData const*>> _subcategoriesByCategory;
|
||||
std::unordered_map<uint32 /*subcategoryId*/, std::vector<uint32 /*houseDecorId*/>> _decorsBySubcategory;
|
||||
std::unordered_map<uint32 /*houseDecorId*/, uint32 /*categoryId*/> _categoryByDecor;
|
||||
std::unordered_map<uint32 /*houseDecorId*/, std::vector<DecorDyeSlotData const*>> _dyeSlotsByDecor;
|
||||
|
||||
// Reverse lookup: world MapID -> NeighborhoodMap ID
|
||||
std::unordered_map<int32 /*worldMapId*/, uint32 /*neighborhoodMapId*/> _worldMapToNeighborhoodMap;
|
||||
|
||||
// Room doorway map: RoomWmoDataID -> list of doorway components
|
||||
std::unordered_map<uint32 /*roomWmoDataId*/, std::vector<RoomDoorInfo>> _roomDoorMap;
|
||||
|
||||
// All room components indexed by RoomWmoDataID (walls, floors, ceilings, stairs, doorways)
|
||||
std::unordered_map<uint32 /*roomWmoDataId*/, std::vector<RoomComponentData>> _roomComponentsByWmoData;
|
||||
|
||||
// O(1) RoomComponentOption lookup: key = (uint64(MeshStyleFilterID) << 32) | uint32(HouseThemeID)
|
||||
std::unordered_map<uint64, RoomComponentOptionEntry const*> _roomCompOptionIndex;
|
||||
void BuildRoomComponentOptionIndex();
|
||||
void BuildExteriorComponentIndexes();
|
||||
void BuildRoomComponentTextureIndex();
|
||||
void EnsureDoorGameObjectTemplates();
|
||||
|
||||
// Base room entry ID — exterior geobox (from DB2 IsBaseRoom flag scan, fallback 18)
|
||||
uint32 _baseRoomEntryId = 0;
|
||||
|
||||
// Entry hall room entry ID — second BASE_ROOM in DB2 (Room 46, the entry corridor); fallback to _baseRoomEntryId
|
||||
uint32 _entryHallRoomEntryId = 0;
|
||||
|
||||
// RoomComponentOptionID → RoomComponentTextureID (from RoomComponentOptionTexture join)
|
||||
std::unordered_map<int32 /*optionID*/, int32 /*textureID*/> _textureByOptionId;
|
||||
// componentType → textureID (first matching texture per type, fallback)
|
||||
std::unordered_map<uint8 /*type*/, int32 /*textureID*/> _textureByComponentType;
|
||||
|
||||
// ExteriorComponent indexes
|
||||
std::unordered_map<uint32 /*extCompID*/, std::vector<ExteriorComponentHookEntry const*>> _hooksByExtComp;
|
||||
std::unordered_map<uint32 /*extCompID*/, ExteriorComponentExitPointEntry const*> _exitPointByExtComp;
|
||||
std::unordered_map<uint32 /*extCompID*/, int32 /*groupID*/> _groupByExtComp;
|
||||
std::unordered_map<int32 /*groupID*/, std::vector<uint32 /*extCompID*/>> _extCompsByGroup;
|
||||
std::unordered_map<uint32 /*parentCompID*/, std::vector<uint32 /*childCompID*/>> _childrenByExtComp;
|
||||
std::unordered_map<uint32 /*wmoDataID*/, std::vector<uint32 /*compID*/>> _rootCompsByWmoDataId;
|
||||
|
||||
// Fixture resolution: key = (uint64(componentType) << 40) | (uint64(wmoDataID) << 8) | size
|
||||
std::unordered_map<uint64, uint32> _defaultFixtureByTypeWmo;
|
||||
|
||||
// House-finder ignore list, per player. Value present == loaded from DB.
|
||||
std::unordered_map<ObjectGuid, std::unordered_set<ObjectGuid>> _ignoredNeighborhoods;
|
||||
};
|
||||
|
||||
#define sHousingMgr HousingMgr::Instance()
|
||||
|
||||
#endif // HousingMgr_h__
|
||||
|
||||
#endif // TRINITYCORE_HOUSING_MGR_H
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*/
|
||||
|
||||
#include "HousingRoomAddService.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingClientData.h"
|
||||
#include "HousingDefines.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "HousingRoomPersistence.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool TryGetPersistedRoomCount(ObjectGuid const& houseGuid, uint64& count)
|
||||
{
|
||||
count = 0;
|
||||
QueryResult result = CharacterDatabase.PQuery(
|
||||
"SELECT COUNT(*) FROM housing_room_entity WHERE HouseGuid = '{}'",
|
||||
houseGuid.ToString());
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
count = result->Fetch()[0].GetUInt64();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
RoomAddService& RoomAddService::Instance()
|
||||
{
|
||||
static RoomAddService instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
RoomAddPreflightResult RoomAddService::Prepare(Player const* player, ObjectGuid const& sourceRoomGuid,
|
||||
uint32 targetDoorComponentId, uint32 newHouseRoomId, RoomAddPreflight& preflight) const
|
||||
{
|
||||
preflight = {};
|
||||
if (!player || !IsRoomGuid(sourceRoomGuid) || !targetDoorComponentId || !newHouseRoomId)
|
||||
return RoomAddPreflightResult::InvalidRequest;
|
||||
|
||||
RoomState sourceRoom;
|
||||
Result const sourceLoad = sHousingRoomPersistence.LoadOwnedRoom(player, sourceRoomGuid, sourceRoom);
|
||||
if (sourceLoad != Result::Success)
|
||||
return sourceLoad == Result::PermissionDenied
|
||||
? RoomAddPreflightResult::PermissionDenied
|
||||
: RoomAddPreflightResult::SourceRoomNotFound;
|
||||
|
||||
if (!sHousingRoomPersistence.IsRoomTemplateStoreReady() ||
|
||||
!sHousingRoomPersistence.IsKnownRoomTemplate(newHouseRoomId))
|
||||
return RoomAddPreflightResult::StaticDataUnavailable;
|
||||
|
||||
int32 sourceRoomWmoDataId = 0;
|
||||
int32 newRoomWmoDataId = 0;
|
||||
if (!ClientData::GetRoomTemplateWmoDataID(uint32(sourceRoom.HouseRoomId), sourceRoomWmoDataId) ||
|
||||
!ClientData::GetRoomTemplateWmoDataID(newHouseRoomId, newRoomWmoDataId))
|
||||
return RoomAddPreflightResult::StaticDataUnavailable;
|
||||
|
||||
std::vector<ClientData::RoomComponentTopology> sourceComponents;
|
||||
std::vector<ClientData::RoomComponentTopology> newRoomComponents;
|
||||
if (!ClientData::GetRoomComponentsForWmo(sourceRoomWmoDataId, sourceComponents) ||
|
||||
!ClientData::GetRoomComponentsForWmo(newRoomWmoDataId, newRoomComponents))
|
||||
return RoomAddPreflightResult::StaticDataUnavailable;
|
||||
|
||||
auto const sourceComponent = std::find_if(sourceComponents.begin(), sourceComponents.end(),
|
||||
[targetDoorComponentId](ClientData::RoomComponentTopology const& component)
|
||||
{
|
||||
return component.ID == targetDoorComponentId;
|
||||
});
|
||||
if (sourceComponent == sourceComponents.end())
|
||||
return RoomAddPreflightResult::SourceComponentNotFound;
|
||||
|
||||
uint64 persistedRoomCount = 0;
|
||||
if (!TryGetPersistedRoomCount(sourceRoom.HouseGuid, persistedRoomCount))
|
||||
return RoomAddPreflightResult::StaticDataUnavailable;
|
||||
|
||||
// Count every persisted row, including malformed rows ignored by the read
|
||||
// path. Bad persistence must never let an ADD request bypass the allocation
|
||||
// safety cap and create an oversized client entity graph.
|
||||
if (persistedRoomCount >= SafetyMaxRoomsPerHouse)
|
||||
return RoomAddPreflightResult::RoomLimitReached;
|
||||
|
||||
preflight.SourceRoom = sourceRoom;
|
||||
preflight.SourceRoomWmoDataId = sourceRoomWmoDataId;
|
||||
preflight.NewRoomWmoDataId = newRoomWmoDataId;
|
||||
preflight.TargetDoorComponentId = sourceComponent->ID;
|
||||
preflight.NewHouseRoomId = newHouseRoomId;
|
||||
return RoomAddPreflightResult::Success;
|
||||
}
|
||||
|
||||
bool RoomAddService::PersistPreparedRoom(Player const* player, RoomState const& state) const
|
||||
{
|
||||
if (!player || !sHousingRoomPersistence.IsSafePersistedRoom(state))
|
||||
return false;
|
||||
|
||||
// Sniff/reference verified subtype-2 Room GUIDs encode HouseRoomID in arg2.
|
||||
// Refuse identities that disagree with the room template before any DB write.
|
||||
if (state.RoomGuid.GetEntry() != uint32(state.HouseRoomId))
|
||||
{
|
||||
TC_LOG_WARN("housing", "Refusing Room ADD persistence for GUID {}: entry {} does not match HouseRoomID {}",
|
||||
state.RoomGuid.ToString(), state.RoomGuid.GetEntry(), state.HouseRoomId);
|
||||
return false;
|
||||
}
|
||||
|
||||
HouseState house;
|
||||
if (sHousingMgr.LoadOwnedHouse(player, state.HouseGuid, house) != Result::Success ||
|
||||
house.HouseGuid != state.HouseGuid)
|
||||
return false;
|
||||
|
||||
int32 roomWmoDataId = 0;
|
||||
std::vector<ClientData::RoomComponentTopology> components;
|
||||
if (!sHousingRoomPersistence.IsKnownRoomTemplate(uint32(state.HouseRoomId)) ||
|
||||
!ClientData::GetRoomTemplateWmoDataID(uint32(state.HouseRoomId), roomWmoDataId) ||
|
||||
!ClientData::GetRoomComponentsForWmo(roomWmoDataId, components))
|
||||
return false;
|
||||
|
||||
uint64 persistedRoomCount = 0;
|
||||
if (!TryGetPersistedRoomCount(state.HouseGuid, persistedRoomCount) ||
|
||||
persistedRoomCount >= SafetyMaxRoomsPerHouse)
|
||||
return false;
|
||||
|
||||
QueryResult duplicate = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_room_entity WHERE RoomGuid = '{}' LIMIT 1",
|
||||
state.RoomGuid.ToString());
|
||||
if (duplicate)
|
||||
{
|
||||
TC_LOG_WARN("housing", "Refusing duplicate Housing Room GUID {}", state.RoomGuid.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
"INSERT INTO housing_room_entity "
|
||||
"(RoomGuid, HouseGuid, HouseRoomID, Flags, PositionX, PositionY, PositionZ, "
|
||||
"RotationX, RotationY, RotationZ, RotationW, Scale, AttachmentFlags, FloorIndex) "
|
||||
"VALUES ('{}', '{}', {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
|
||||
state.RoomGuid.ToString(), state.HouseGuid.ToString(), state.HouseRoomId, state.Flags,
|
||||
state.PositionX, state.PositionY, state.PositionZ,
|
||||
state.RotationX, state.RotationY, state.RotationZ, state.RotationW,
|
||||
state.Scale, uint32(state.AttachmentFlags), state.FloorIndex);
|
||||
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||
|
||||
RoomState persisted;
|
||||
if (sHousingRoomPersistence.LoadOwnedRoom(player, state.RoomGuid, persisted) == Result::Success &&
|
||||
persisted.HouseGuid == state.HouseGuid && persisted.HouseRoomId == state.HouseRoomId &&
|
||||
persisted.FloorIndex == state.FloorIndex)
|
||||
return true;
|
||||
|
||||
// If the row cannot pass the normal owned-room read path, do not leave it
|
||||
// available for future live materialization.
|
||||
CharacterDatabase.DirectPExecute(
|
||||
"DELETE FROM housing_room_entity WHERE RoomGuid = '{}' AND HouseGuid = '{}'",
|
||||
state.RoomGuid.ToString(), state.HouseGuid.ToString());
|
||||
TC_LOG_ERROR("housing", "Room ADD persistence read-back failed for {}; inserted row was removed",
|
||||
state.RoomGuid.ToString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user