Compare commits
10 commits
ab9d0a074d
...
6454c737da
| Author | SHA1 | Date | |
|---|---|---|---|
| 6454c737da | |||
| e6af1065e4 | |||
| be47269ede | |||
| a0b945afcd | |||
| 9715afee68 | |||
| 48a8d0c620 | |||
| 0629e81436 | |||
| 9fb81f22c0 | |||
| 70a5d07325 | |||
| 18987fe43d |
10 changed files with 805 additions and 102 deletions
45
data/sql/updates/db_characters/2026_05_15_00.sql
Normal file
45
data/sql/updates/db_characters/2026_05_15_00.sql
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
-- DB update 2026_04_12_00 -> 2026_05_15_00
|
||||
--
|
||||
CREATE TABLE lost_corpses (
|
||||
lost_corpse_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
player_guid BIGINT UNSIGNED NOT NULL,
|
||||
corpse_guid BIGINT UNSIGNED NULL,
|
||||
map_id SMALLINT UNSIGNED NOT NULL,
|
||||
zone_id SMALLINT UNSIGNED NOT NULL,
|
||||
position_x FLOAT NOT NULL,
|
||||
position_y FLOAT NOT NULL,
|
||||
position_z FLOAT NOT NULL,
|
||||
orientation FLOAT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
money BIGINT UNSIGNED DEFAULT 0,
|
||||
active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
|
||||
INDEX idx_player_guid (player_guid),
|
||||
INDEX idx_corpse_guid (corpse_guid),
|
||||
INDEX idx_map_zone (map_id, zone_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE lost_corpse_items (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
lost_corpse_id BIGINT UNSIGNED NOT NULL,
|
||||
item_entry INT UNSIGNED NOT NULL,
|
||||
count INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
randomPropertyId INT DEFAULT 0,
|
||||
durability INT UNSIGNED DEFAULT 0,
|
||||
enchantments TEXT,
|
||||
looted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT fk_lost_corpse
|
||||
FOREIGN KEY (lost_corpse_id)
|
||||
REFERENCES lost_corpses(lost_corpse_id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
INDEX idx_lost_corpse_id (lost_corpse_id),
|
||||
INDEX idx_item_entry (item_entry)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
ALTER TABLE lost_corpses
|
||||
ADD UNIQUE KEY uniq_lookup (player_guid, corpse_guid, created_at);
|
||||
|
||||
ALTER TABLE lost_corpses
|
||||
ADD COLUMN money_collected TINYINT(1) NOT NULL DEFAULT 0;
|
||||
92
data/sql/updates/db_world/2026_05_15_00.sql
Normal file
92
data/sql/updates/db_world/2026_05_15_00.sql
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
-- DB update 2026_04_30_03 -> 2026_05_15_00
|
||||
--
|
||||
UPDATE creature_template
|
||||
SET HealthModifier = HealthModifier * 3,
|
||||
DamageModifier = DamageModifier * 3;
|
||||
|
||||
UPDATE player_xp_for_level
|
||||
SET Experience = Experience * 1.5;
|
||||
|
||||
INSERT INTO creature_template (
|
||||
entry, name, subname,
|
||||
minlevel, maxlevel,
|
||||
faction, npcflag, unit_class, type
|
||||
) VALUES (
|
||||
90000,
|
||||
'Chromie',
|
||||
'Timewalker Guide',
|
||||
80, 80,
|
||||
35, 0, 1, 7
|
||||
);
|
||||
|
||||
INSERT INTO creature_template_model (
|
||||
CreatureID,
|
||||
CreatureDisplayID,
|
||||
DisplayScale,
|
||||
Probability
|
||||
) VALUES (
|
||||
90000,
|
||||
24877,
|
||||
1,
|
||||
1
|
||||
);
|
||||
|
||||
INSERT INTO creature (
|
||||
id1,
|
||||
map,
|
||||
position_x,
|
||||
position_y,
|
||||
position_z,
|
||||
orientation,
|
||||
spawntimesecs,
|
||||
wander_distance,
|
||||
curhealth,
|
||||
curmana,
|
||||
MovementType
|
||||
) VALUES (
|
||||
90000,
|
||||
0,
|
||||
-8844.938,
|
||||
617.47314,
|
||||
95.53623,
|
||||
5.0512466,
|
||||
120,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
INSERT INTO creature (
|
||||
id1,
|
||||
map,
|
||||
position_x,
|
||||
position_y,
|
||||
position_z,
|
||||
orientation,
|
||||
spawntimesecs,
|
||||
wander_distance,
|
||||
curhealth,
|
||||
curmana,
|
||||
MovementType
|
||||
) VALUES (
|
||||
90000,
|
||||
1,
|
||||
1546.3574,
|
||||
-4447.2905,
|
||||
12.005171,
|
||||
1.002923,
|
||||
120,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (90001, 'spell_custom_recover_corpse_items');
|
||||
|
||||
INSERT INTO trainer_spell VALUES (11, 90000, 10000, 0, 0, 0, 0, 0, 12, 0);
|
||||
INSERT INTO trainer_spell VALUES (12, 90000, 10000, 0, 0, 0, 0, 0, 12, 0);
|
||||
|
||||
INSERT INTO trainer_spell VALUES (33, 90001, 10000, 0, 0, 0, 0, 0, 12, 0);
|
||||
INSERT INTO trainer_spell VALUES (34, 90001, 10000, 0, 0, 0, 0, 0, 12, 0);
|
||||
|
|
@ -2089,7 +2089,7 @@ WaterBreath.Timer = 180000
|
|||
# 0 - Disabled
|
||||
#
|
||||
|
||||
EnableLowLevelRegenBoost = 1
|
||||
EnableLowLevelRegenBoost = 0
|
||||
|
||||
#
|
||||
# Rate.MoveSpeed.Player
|
||||
|
|
@ -2146,8 +2146,8 @@ Rate.Talent.Pet = 1
|
|||
# 1 - (Rate.Focus)
|
||||
# 1 - (Rate.Energy)
|
||||
|
||||
Rate.Health = 1
|
||||
Rate.Mana = 1
|
||||
Rate.Health = 0.5
|
||||
Rate.Mana = 0.5
|
||||
Rate.Rage.Income = 1
|
||||
Rate.Rage.Loss = 1
|
||||
Rate.RunicPower.Income = 1
|
||||
|
|
|
|||
|
|
@ -59,13 +59,38 @@ DatabaseWorkerPool<T>::DatabaseWorkerPool() :
|
|||
{
|
||||
WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe.");
|
||||
|
||||
bool isSupportClientDB = mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION;
|
||||
bool isSameClientDB = mysql_get_client_version() == MYSQL_VERSION_ID;
|
||||
#ifdef MARIADB_BASE_VERSION
|
||||
|
||||
WPFatal(isSupportClientDB, "AzerothCore does not support MySQL versions below 8.0\n\nFound version: {} / {}. Server compiled with: {}.\nSearch the wiki for ACE00043 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00043).",
|
||||
mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID);
|
||||
WPFatal(isSameClientDB, "Used MySQL library version ({} id {}) does not match the version id used to compile AzerothCore (id {}).\nSearch the wiki for ACE00046 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00046).",
|
||||
mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID);
|
||||
// MariaDB path (skip MySQL 8 strict checks)
|
||||
LOG_INFO("sql.sql", "MariaDB detected: {} (client version: {})",
|
||||
mysql_get_client_info(),
|
||||
mysql_get_client_version());
|
||||
|
||||
#else
|
||||
|
||||
// MySQL 8+ strict validation path
|
||||
const uint32 clientVersion = mysql_get_client_version();
|
||||
const uint32 compiledVersion = MYSQL_VERSION_ID;
|
||||
|
||||
bool isSupportClientDB = clientVersion >= MIN_MYSQL_CLIENT_VERSION;
|
||||
bool isSameClientDB = clientVersion == compiledVersion;
|
||||
|
||||
WPFatal(isSupportClientDB,
|
||||
"AzerothCore does not support MySQL versions below 8.0\n\n"
|
||||
"Found version: {} / {}. Server compiled with: {}.\n"
|
||||
"Search ACE00043 in Common Errors.",
|
||||
mysql_get_client_info(),
|
||||
clientVersion,
|
||||
compiledVersion);
|
||||
|
||||
WPFatal(isSameClientDB,
|
||||
"Used MySQL library version ({} id {}) does not match compiled version id ({}).\n"
|
||||
"Search ACE00046 in Common Errors.",
|
||||
mysql_get_client_info(),
|
||||
clientVersion,
|
||||
compiledVersion);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
|
@ -417,9 +442,10 @@ bool DatabaseIncompatibleVersion(std::string const mysqlVersion)
|
|||
template <class T>
|
||||
uint32 DatabaseWorkerPool<T>::OpenConnections(InternalIndex type, uint8 numConnections)
|
||||
{
|
||||
LOG_INFO("sql.sql", "OpenConnections called for type {}", type);
|
||||
|
||||
for (uint8 i = 0; i < numConnections; ++i)
|
||||
{
|
||||
// Create the connection
|
||||
auto connection = [&]
|
||||
{
|
||||
switch (type)
|
||||
|
|
@ -433,26 +459,42 @@ uint32 DatabaseWorkerPool<T>::OpenConnections(InternalIndex type, uint8 numConne
|
|||
}
|
||||
}();
|
||||
|
||||
if (uint32 error = connection->Open())
|
||||
uint32 error = connection->Open();
|
||||
if (error)
|
||||
{
|
||||
// Failed to open a connection or invalid version, abort and cleanup
|
||||
_queue->Cancel();
|
||||
_connections[type].clear();
|
||||
return error;
|
||||
}
|
||||
else if (DatabaseIncompatibleVersion(connection->GetServerInfo()))
|
||||
|
||||
bool incompatible = DatabaseIncompatibleVersion(connection->GetServerInfo());
|
||||
|
||||
#ifdef MARIADB_BASE_VERSION
|
||||
// MariaDB: do NOT treat as fatal, but also do NOT blindly trust it
|
||||
if (incompatible)
|
||||
{
|
||||
LOG_ERROR("sql.driver", "AzerothCore does not support MySQL versions below 8.0\n\nFound server version: {}. Server compiled with: {}.",
|
||||
connection->GetServerInfo(), MYSQL_VERSION_ID);
|
||||
LOG_WARN("sql.driver",
|
||||
"MariaDB detected with non-standard version response: {}",
|
||||
connection->GetServerInfo());
|
||||
}
|
||||
#else
|
||||
if (incompatible)
|
||||
{
|
||||
LOG_ERROR("sql.driver",
|
||||
"AzerothCore does not support MySQL versions below 8.0\n\n"
|
||||
"Found server version: {}. Server compiled with: {}.",
|
||||
connection->GetServerInfo(),
|
||||
MYSQL_VERSION_ID);
|
||||
|
||||
_queue->Cancel();
|
||||
_connections[type].clear();
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
|
||||
_connections[type].push_back(std::move(connection));
|
||||
}
|
||||
}
|
||||
|
||||
// Everything is fine
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,13 +129,19 @@ uint32 MySQLConnection::Open()
|
|||
|
||||
if (m_connectionInfo.ssl != "")
|
||||
{
|
||||
#if defined(MARIADB_BASE_VERSION)
|
||||
// MariaDB: no mysql_ssl_mode, skip
|
||||
#else
|
||||
// MySQL 8+
|
||||
mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED;
|
||||
|
||||
if (m_connectionInfo.ssl == "ssl")
|
||||
{
|
||||
opt_use_ssl = SSL_MODE_REQUIRED;
|
||||
}
|
||||
|
||||
mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl);
|
||||
#endif
|
||||
}
|
||||
|
||||
m_Mysql = reinterpret_cast<MySQLHandle*>(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(),
|
||||
|
|
@ -217,7 +223,13 @@ bool MySQLConnection::Execute(PreparedStatementBase* stmt)
|
|||
uint32 _s = getMSTime();
|
||||
|
||||
#if MYSQL_VERSION_ID >= 80300
|
||||
#if defined(MARIADB_BASE_VERSION)
|
||||
// MariaDB: only supports positional parameters
|
||||
if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
|
||||
#else
|
||||
// MySQL 8+: supports named parameters
|
||||
if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr))
|
||||
#endif
|
||||
#else
|
||||
if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
|
||||
#endif
|
||||
|
|
@ -269,7 +281,13 @@ bool MySQLConnection::_Query(PreparedStatementBase* stmt, MySQLPreparedStatement
|
|||
uint32 _s = getMSTime();
|
||||
|
||||
#if MYSQL_VERSION_ID >= 80300
|
||||
#if defined(MARIADB_BASE_VERSION)
|
||||
// MariaDB: only supports positional parameters
|
||||
if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
|
||||
#else
|
||||
// MySQL 8+: supports named parameters
|
||||
if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr))
|
||||
#endif
|
||||
#else
|
||||
if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@
|
|||
#include "ConditionMgr.h"
|
||||
#include "Config.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "DBCEnums.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DatabaseEnvFwd.h"
|
||||
#include "DisableMgr.h"
|
||||
#include "Formulas.h"
|
||||
#include "GameEventMgr.h"
|
||||
|
|
@ -60,6 +62,7 @@
|
|||
#include "MapMgr.h"
|
||||
#include "MiscPackets.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "OutdoorPvP.h"
|
||||
#include "OutdoorPvPMgr.h"
|
||||
|
|
@ -1876,6 +1879,9 @@ void Player::Regenerate(Powers power)
|
|||
addvalue += GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER + AsUnderlyingType(POWER_MANA)) * ManaIncreaseRate * 0.001f * m_regenTimer;
|
||||
else
|
||||
addvalue += GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER + AsUnderlyingType(POWER_MANA)) * ManaIncreaseRate * 0.001f * m_regenTimer;
|
||||
|
||||
if (!IsStandState())
|
||||
addvalue *= 2.0f;
|
||||
}
|
||||
break;
|
||||
case POWER_RAGE: // Regenerate rage
|
||||
|
|
@ -1999,7 +2005,7 @@ void Player::RegenerateHealth()
|
|||
|
||||
if (!IsStandState())
|
||||
{
|
||||
addvalue *= 1.33f;
|
||||
addvalue *= 2.0f;
|
||||
}
|
||||
|
||||
addvalue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
|
||||
|
|
@ -4326,25 +4332,10 @@ void Player::DeleteOldRecoveryItems(uint32 keepDays)
|
|||
*/
|
||||
void Player::BuildPlayerRepop()
|
||||
{
|
||||
WorldPacket data(SMSG_PRE_RESURRECT, GetPackGUID().size());
|
||||
data << GetPackGUID();
|
||||
SendDirectMessage(&data);
|
||||
if (getRace(true) == RACE_NIGHTELF)
|
||||
{
|
||||
CastSpell(this, 20584, true);
|
||||
}
|
||||
CastSpell(this, 8326, true);
|
||||
|
||||
// there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_WATER_WALK
|
||||
// there must be SMSG.STOP_MIRROR_TIMER
|
||||
|
||||
// the player cannot have a corpse already on current map, only bones which are not returned by GetCorpse
|
||||
WorldLocation corpseLocation = GetCorpseLocation();
|
||||
if (GetCorpse() && corpseLocation.GetMapId() == GetMapId())
|
||||
{
|
||||
LOG_ERROR("entities.player", "BuildPlayerRepop: player {} ({}) already has a corpse", GetName(), GetGUID().ToString());
|
||||
return;
|
||||
}
|
||||
// Override -- Teleport player back to their bind point [ElderShell]
|
||||
ResurrectPlayer(0.1f);
|
||||
// NOTE: We teleport the player in Player::RepopAtGraveyard()
|
||||
// End Override
|
||||
|
||||
// create a corpse and place it at the player's location
|
||||
Corpse* corpse = CreateCorpse();
|
||||
|
|
@ -4354,22 +4345,6 @@ void Player::BuildPlayerRepop()
|
|||
return;
|
||||
}
|
||||
GetMap()->AddToMap(corpse);
|
||||
SetHealth(1); // convert player body to ghost
|
||||
SetWaterWalking(true);
|
||||
|
||||
if (!IsImmobilizedState())
|
||||
SendMoveRoot(false);
|
||||
|
||||
RemoveUnitFlag(UNIT_FLAG_SKINNABLE); // BG - remove insignia related
|
||||
int32 corpseReclaimDelay = CalculateCorpseReclaimDelay();
|
||||
if (corpseReclaimDelay >= 0)
|
||||
{
|
||||
SendCorpseReclaimDelay(corpseReclaimDelay);
|
||||
}
|
||||
corpse->ResetGhostTime(); // to prevent cheating
|
||||
StopMirrorTimers(); // disable timers on bars
|
||||
SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_ALWAYS_STAND); // set and clear other
|
||||
sScriptMgr->OnPlayerReleasedGhost(this);
|
||||
}
|
||||
|
||||
void Player::ResurrectPlayer(float restore_percent, bool applySickness)
|
||||
|
|
@ -4498,7 +4473,7 @@ void Player::OfflineResurrect(ObjectGuid const& guid, CharacterDatabaseTransacti
|
|||
Corpse* Player::CreateCorpse()
|
||||
{
|
||||
// prevent existence 2 corpse for player
|
||||
SpawnCorpseBones();
|
||||
SpawnCorpseBones(); // TODO: We'd love to allow for several corpses in the world per player but it might break things... [ElderShell]
|
||||
|
||||
uint32 _uf, _pb, _pb2, _cfb1, _cfb2;
|
||||
|
||||
|
|
@ -4530,7 +4505,7 @@ Corpse* Player::CreateCorpse()
|
|||
corpse->SetUInt32Value(CORPSE_FIELD_BYTES_1, _cfb1);
|
||||
corpse->SetUInt32Value(CORPSE_FIELD_BYTES_2, _cfb2);
|
||||
|
||||
uint32 flags = CORPSE_FLAG_UNK2;
|
||||
uint32 flags = CORPSE_FLAG_UNK2 | CORPSE_FLAG_LOOTABLE;
|
||||
if (HasPlayerFlag(PLAYER_FLAGS_HIDE_HELM))
|
||||
flags |= CORPSE_FLAG_HIDE_HELM;
|
||||
if (HasPlayerFlag(PLAYER_FLAGS_HIDE_CLOAK))
|
||||
|
|
@ -4549,6 +4524,9 @@ Corpse* Player::CreateCorpse()
|
|||
|
||||
corpse->SetUInt32Value(CORPSE_FIELD_GUILD, GetGuildId());
|
||||
|
||||
corpse->lootRecipient = this;
|
||||
corpse->SetUInt32Value(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE);
|
||||
|
||||
uint32 iDisplayID;
|
||||
uint32 iIventoryType;
|
||||
uint32 _cfi;
|
||||
|
|
@ -4573,6 +4551,122 @@ Corpse* Player::CreateCorpse()
|
|||
if (!GetMap()->IsBattlegroundOrArena())
|
||||
corpse->SaveToDB();
|
||||
|
||||
uint64 playerGuid = GetGUID().GetCounter();
|
||||
uint64 corpseGuid = corpse->GetGUID().GetCounter();
|
||||
|
||||
// Insert corpse
|
||||
CharacterDatabase.DirectExecute(
|
||||
"INSERT INTO lost_corpses "
|
||||
"(player_guid, corpse_guid, map_id, zone_id, position_x, position_y, position_z, orientation, money) "
|
||||
"VALUES ({}, {}, {}, {}, {}, {}, {}, {}, {})",
|
||||
playerGuid,
|
||||
corpseGuid,
|
||||
GetMapId(),
|
||||
GetZoneId(),
|
||||
GetPositionX(),
|
||||
GetPositionY(),
|
||||
GetPositionZ(),
|
||||
GetOrientation(),
|
||||
GetMoney()
|
||||
);
|
||||
|
||||
QueryResult result = CharacterDatabase.Query(
|
||||
"SELECT lost_corpse_id "
|
||||
"FROM lost_corpses "
|
||||
"WHERE player_guid = {} "
|
||||
"ORDER BY lost_corpse_id DESC "
|
||||
"LIMIT 1",
|
||||
playerGuid);
|
||||
|
||||
if (!result)
|
||||
return corpse;
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
uint32 lostCorpseId = fields[0].Get<uint32>();
|
||||
|
||||
auto insertItem = [&](Item* item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
CharacterDatabase.Execute(
|
||||
"INSERT INTO lost_corpse_items "
|
||||
"(lost_corpse_id, item_entry, count, randomPropertyId, durability, enchantments, looted) "
|
||||
"VALUES ({}, {}, {}, {}, {}, '{}', 0)",
|
||||
lostCorpseId,
|
||||
item->GetEntry(),
|
||||
item->GetCount(),
|
||||
item->GetItemRandomPropertyId(),
|
||||
item->GetUInt32Value(ITEM_FIELD_DURABILITY),
|
||||
""
|
||||
);
|
||||
};
|
||||
|
||||
// EQUIPPED ITEMS
|
||||
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
|
||||
{
|
||||
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
|
||||
insertItem(item);
|
||||
}
|
||||
|
||||
// INVENTORY + BAGS
|
||||
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||
{
|
||||
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
insertItem(item);
|
||||
}
|
||||
|
||||
// BAGS
|
||||
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||
{
|
||||
if (Bag* b = GetBagByPos(bag))
|
||||
{
|
||||
insertItem(b);
|
||||
|
||||
for (uint32 slot = 0; slot < b->GetBagSize(); ++slot)
|
||||
{
|
||||
if (Item* item = b->GetItemByPos(slot))
|
||||
insertItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove items and bags from player
|
||||
for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
|
||||
{
|
||||
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
|
||||
{
|
||||
RemoveItem(INVENTORY_SLOT_BAG_0, i, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
|
||||
{
|
||||
if (Bag* bag = GetBagByPos(i))
|
||||
{
|
||||
for (uint32 slot = 0; slot < bag->GetBagSize(); ++slot)
|
||||
{
|
||||
if (Item* item = bag->GetItemByPos(slot))
|
||||
{
|
||||
bag->RemoveItem(slot, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CharacterDatabase.Execute(
|
||||
"DELETE ii FROM item_instance ii "
|
||||
"JOIN character_inventory ci ON ci.item = ii.guid "
|
||||
"WHERE ci.guid = {}",
|
||||
GetGUID().GetCounter());
|
||||
|
||||
CharacterDatabase.Execute(
|
||||
"DELETE FROM character_inventory "
|
||||
"WHERE guid = {}",
|
||||
GetGUID().GetCounter());
|
||||
|
||||
SetMoney(0);
|
||||
|
||||
return corpse;
|
||||
}
|
||||
|
||||
|
|
@ -4840,40 +4934,55 @@ void Player::RepopAtGraveyard()
|
|||
SpawnCorpseBones();
|
||||
}
|
||||
|
||||
GraveyardStruct const* ClosestGrave = nullptr;
|
||||
|
||||
// Special handle for battleground maps
|
||||
if (Battleground* bg = GetBattleground())
|
||||
ClosestGrave = bg->GetClosestGraveyard(this);
|
||||
else
|
||||
{
|
||||
if (sBattlefieldMgr->GetBattlefieldToZoneId(GetZoneId()))
|
||||
ClosestGrave = sBattlefieldMgr->GetBattlefieldToZoneId(GetZoneId())->GetClosestGraveyard(this);
|
||||
else
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(this, GetTeamId());
|
||||
}
|
||||
|
||||
// stop countdown until repop
|
||||
m_deathTimer = 0;
|
||||
|
||||
// if no grave found, stay at the current location
|
||||
// and don't show spirit healer location
|
||||
if (ClosestGrave)
|
||||
{
|
||||
TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, GetOrientation());
|
||||
if (isDead()) // not send if alive, because it used in TeleportTo()
|
||||
{
|
||||
WorldPacket data(SMSG_DEATH_RELEASE_LOC, 4 * 4); // show spirit healer position on minimap
|
||||
data << ClosestGrave->Map;
|
||||
data << ClosestGrave->x;
|
||||
data << ClosestGrave->y;
|
||||
data << ClosestGrave->z;
|
||||
SendDirectMessage(&data);
|
||||
}
|
||||
}
|
||||
else if (GetPositionZ() < GetMap()->GetMinHeight(GetPositionX(), GetPositionY()))
|
||||
TeleportTo(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, GetOrientation());
|
||||
|
||||
uint32 nextLevelXp = GetUInt32Value(PLAYER_NEXT_LEVEL_XP);
|
||||
|
||||
if (GetLevel() == DEFAULT_MAX_LEVEL)
|
||||
{
|
||||
QueryResult xpResult = WorldDatabase.Query(
|
||||
"SELECT Experience FROM player_xp_for_level WHERE Level = {} LIMIT 1",
|
||||
GetLevel() - 1
|
||||
);
|
||||
|
||||
if (xpResult)
|
||||
{
|
||||
Field* fields = xpResult->Fetch();
|
||||
nextLevelXp = fields[0].Get<uint32>();
|
||||
}
|
||||
}
|
||||
|
||||
uint32 reduction = nextLevelXp / 10;
|
||||
|
||||
uint32 currentXp = GetUInt32Value(PLAYER_XP);
|
||||
|
||||
int32 xpToRemove = currentXp - reduction;
|
||||
|
||||
if (xpToRemove < 0)
|
||||
{
|
||||
uint8 currentLevel = GetLevel();
|
||||
|
||||
if (currentLevel > 1)
|
||||
{
|
||||
GiveLevel(currentLevel - 1);
|
||||
nextLevelXp = GetUInt32Value(PLAYER_NEXT_LEVEL_XP);
|
||||
SetUInt32Value(PLAYER_XP, nextLevelXp + xpToRemove);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetUInt32Value(PLAYER_XP, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetUInt32Value(PLAYER_XP, currentXp - reduction);
|
||||
}
|
||||
|
||||
//SetUInt32Value(PLAYER_XP, currentXp > reduction ? currentXp - reduction : 0);
|
||||
|
||||
RemovePlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
|
||||
}
|
||||
|
||||
|
|
@ -7983,12 +8092,30 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type)
|
|||
{
|
||||
Corpse* bones = ObjectAccessor::GetCorpse(*this, guid);
|
||||
|
||||
if (!bones || !(loot_type == LOOT_CORPSE || loot_type == LOOT_INSIGNIA) || bones->GetType() != CORPSE_BONES || !bones->HasFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE))
|
||||
if (!bones || !(loot_type == LOOT_CORPSE || loot_type == LOOT_INSIGNIA) || !bones->HasFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE))
|
||||
{
|
||||
SendLootRelease(guid);
|
||||
return;
|
||||
}
|
||||
|
||||
permission = NONE_PERMISSION;
|
||||
|
||||
ObjectGuid corpseGUID = bones->GetGUID();
|
||||
CheckCorpseByGUID(corpseGUID);
|
||||
|
||||
if (Corpse* bones = ObjectAccessor::GetCorpse(*this, guid))
|
||||
{
|
||||
if (bones->GetOwnerGUID() != GetGUID())
|
||||
{
|
||||
SendLootError(guid, LOOT_ERROR_DIDNT_KILL);
|
||||
return;
|
||||
}
|
||||
permission = OWNER_PERMISSION;
|
||||
|
||||
LoadLostCorpseLoot(bones->GetOwnerGUID(), bones->loot);
|
||||
bones->loot.loot_type = LOOT_CORPSE;
|
||||
}
|
||||
|
||||
loot = &bones->loot;
|
||||
|
||||
if (loot->loot_type == LOOT_NONE)
|
||||
|
|
@ -8003,10 +8130,10 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type)
|
|||
bones->loot.gold = uint32(urand(50, 150) * 0.016f * pow(float(pLevel) / 5.76f, 2.5f) * sWorld->getRate(RATE_DROP_MONEY));
|
||||
}
|
||||
|
||||
if (bones->lootRecipient != this)
|
||||
permission = NONE_PERMISSION;
|
||||
else
|
||||
permission = OWNER_PERMISSION;
|
||||
//if (bones->lootRecipient != this)
|
||||
// permission = NONE_PERMISSION;
|
||||
//else
|
||||
// permission = OWNER_PERMISSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -8192,6 +8319,154 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type)
|
|||
SendLootError(guid, LOOT_ERROR_DIDNT_KILL);
|
||||
}
|
||||
|
||||
void Player::LoadLostCorpseLoot(ObjectGuid playerGuid, Loot& loot)
|
||||
{
|
||||
loot.clear();
|
||||
m_lostCorpseItems.clear();
|
||||
|
||||
if (playerGuid.IsEmpty())
|
||||
return;
|
||||
|
||||
QueryResult corpseResult = CharacterDatabase.Query(
|
||||
"SELECT lost_corpse_id, money, money_collected "
|
||||
"FROM lost_corpses "
|
||||
"WHERE player_guid = {} "
|
||||
"AND SQRT(POW(position_x - {}, 2) + POW(position_y - {}, 2) + POW(position_z - {}, 2)) <= 5 "
|
||||
"AND active = 1 "
|
||||
"ORDER BY lost_corpse_id DESC LIMIT 1",
|
||||
playerGuid.GetCounter(),
|
||||
GetPositionX(),
|
||||
GetPositionY(),
|
||||
GetPositionZ()
|
||||
);
|
||||
|
||||
if (!corpseResult)
|
||||
return;
|
||||
|
||||
Field* corpseFields = corpseResult->Fetch();
|
||||
|
||||
uint32 lostCorpseId = corpseFields[0].Get<uint32>();
|
||||
uint32 money = corpseFields[1].Get<uint32>();
|
||||
bool moneyCollected = corpseFields[2].Get<uint8>();
|
||||
|
||||
if (!moneyCollected)
|
||||
{
|
||||
loot.gold = money;
|
||||
}
|
||||
|
||||
QueryResult result = CharacterDatabase.Query(
|
||||
"SELECT id, lost_corpse_id, item_entry, count, durability "
|
||||
"FROM lost_corpse_items "
|
||||
"WHERE lost_corpse_id = {} AND looted = 0 "
|
||||
"ORDER BY id ASC",
|
||||
lostCorpseId
|
||||
);
|
||||
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
uint32 dbId = fields[0].Get<uint32>();
|
||||
uint32 lostCorpseId = fields[1].Get<uint32>();
|
||||
uint32 itemId = fields[2].Get<uint32>();
|
||||
uint32 count = fields[3].Get<uint32>();
|
||||
uint32 durability = fields[4].Get<uint32>();
|
||||
|
||||
LostCorpseItemData data;
|
||||
data.id = dbId;
|
||||
data.lostCorpseId = lostCorpseId;
|
||||
data.itemId = itemId;
|
||||
data.count = count;
|
||||
data.durability = durability;
|
||||
|
||||
m_lostCorpseItems.push_back(data);
|
||||
|
||||
LootItem item;
|
||||
item.itemid = itemId;
|
||||
item.count = count;
|
||||
|
||||
loot.items.push_back(item);
|
||||
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
void Player::CheckCorpse(uint32 lostCorpseId, ObjectGuid corpseGuid)
|
||||
{
|
||||
// Check if any items remain unlooted
|
||||
QueryResult remaining = CharacterDatabase.Query(
|
||||
"SELECT COUNT(*) FROM lost_corpse_items "
|
||||
"WHERE lost_corpse_id = {} AND looted = 0",
|
||||
lostCorpseId
|
||||
);
|
||||
|
||||
if (remaining)
|
||||
{
|
||||
uint32 count = remaining->Fetch()[0].Get<uint32>();
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
QueryResult moneyCheck = CharacterDatabase.Query(
|
||||
"SELECT money_collected, money FROM lost_corpses "
|
||||
"WHERE lost_corpse_id = {} OR money = 0",
|
||||
lostCorpseId
|
||||
);
|
||||
|
||||
if (moneyCheck)
|
||||
{
|
||||
bool moneyCollected = moneyCheck->Fetch()[0].Get<uint8>();
|
||||
uint32 money = moneyCheck->Fetch()[1].Get<uint32>();
|
||||
|
||||
if (moneyCollected || money == 0)
|
||||
{
|
||||
// No items left, deactivate corpse
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE lost_corpses SET active = 0 WHERE lost_corpse_id = {}",
|
||||
lostCorpseId
|
||||
);
|
||||
|
||||
ObjectGuid guidToUse = corpseGuid.IsEmpty() ? GetLootGUID() : corpseGuid;
|
||||
|
||||
// Remove lootability in-game
|
||||
if (Corpse* corpse = ObjectAccessor::GetCorpse(*this, guidToUse))
|
||||
{
|
||||
corpse->RemoveFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::CheckCorpseByGUID(ObjectGuid corpseGuid)
|
||||
{
|
||||
uint32 guid = corpseGuid.GetCounter();
|
||||
uint32 playerGUID = GetGUID().GetCounter();
|
||||
float player_x = GetPositionX();
|
||||
float player_y = GetPositionY();
|
||||
float player_z = GetPositionZ();
|
||||
|
||||
QueryResult result = CharacterDatabase.Query(
|
||||
"SELECT lost_corpse_id "
|
||||
"FROM lost_corpses "
|
||||
"WHERE corpse_guid = {} AND player_guid = {} "
|
||||
"AND SQRT(POW(position_x - {}, 2) + POW(position_y - {}, 2) + POW(position_z - {}, 2)) <= 5",
|
||||
guid,
|
||||
playerGUID,
|
||||
player_x,
|
||||
player_y,
|
||||
player_z
|
||||
);
|
||||
|
||||
if (result)
|
||||
{
|
||||
uint32 lostCorpseId = result->Fetch()[0].Get<uint32>();
|
||||
CheckCorpse(lostCorpseId, corpseGuid);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::SendLootError(ObjectGuid guid, LootError error)
|
||||
{
|
||||
WorldPacket data(SMSG_LOOT_RESPONSE, 10);
|
||||
|
|
@ -13606,6 +13881,37 @@ LootItem* Player::StoreLootItem(uint8 lootSlot, Loot* loot, InventoryResult& msg
|
|||
AllowedLooterSet looters = item->GetAllowedLooters();
|
||||
Item* newitem = StoreNewItem(dest, item->itemid, true, item->randomPropertyId, looters);
|
||||
|
||||
// APPLY LOST CORPSE DURABILITY
|
||||
if (newitem && lootSlot < m_lostCorpseItems.size())
|
||||
{
|
||||
LostCorpseItemData const& data = m_lostCorpseItems[lootSlot];
|
||||
|
||||
// Safety check (important if something desyncs)
|
||||
if (data.itemId == item->itemid)
|
||||
{
|
||||
newitem->SetUInt32Value(ITEM_FIELD_DURABILITY, data.durability);
|
||||
|
||||
// clamp (prevents weird DB values breaking items)
|
||||
if (newitem->GetUInt32Value(ITEM_FIELD_DURABILITY) > newitem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY))
|
||||
{
|
||||
newitem->SetUInt32Value(
|
||||
ITEM_FIELD_DURABILITY,
|
||||
newitem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY)
|
||||
);
|
||||
}
|
||||
|
||||
uint32 dbId = m_lostCorpseItems[lootSlot].id;
|
||||
uint32 corpseId = m_lostCorpseItems[lootSlot].lostCorpseId;
|
||||
|
||||
CharacterDatabase.DirectExecute(
|
||||
"UPDATE lost_corpse_items SET looted = 1 WHERE id = {}",
|
||||
dbId
|
||||
);
|
||||
|
||||
CheckCorpse(corpseId);
|
||||
}
|
||||
}
|
||||
|
||||
if (qitem)
|
||||
{
|
||||
qitem->is_looted = true;
|
||||
|
|
|
|||
|
|
@ -1082,6 +1082,17 @@ struct PendingSpellCastRequest
|
|||
|
||||
class Player : public Unit, public GridObject<Player>
|
||||
{
|
||||
struct LostCorpseItemData
|
||||
{
|
||||
uint32 id;
|
||||
uint32 lostCorpseId;
|
||||
uint32 itemId;
|
||||
uint32 count;
|
||||
uint32 durability;
|
||||
};
|
||||
|
||||
std::vector<LostCorpseItemData> m_lostCorpseItems;
|
||||
|
||||
friend class WorldSession;
|
||||
friend class CinematicMgr;
|
||||
friend void Item::AddToUpdateQueueOf(Player* player);
|
||||
|
|
@ -1090,6 +1101,9 @@ public:
|
|||
explicit Player(WorldSession* session);
|
||||
~Player() override;
|
||||
|
||||
void CheckCorpse(uint32 lostCorpseId, ObjectGuid corpseGuid = ObjectGuid::Empty);
|
||||
void CheckCorpseByGUID(ObjectGuid corpseGuid);
|
||||
|
||||
void CleanupsBeforeDelete(bool finalCleanup = true) override;
|
||||
|
||||
void AddToWorld() override;
|
||||
|
|
@ -2586,6 +2600,7 @@ public:
|
|||
[[nodiscard]] bool CanSeeTrainer(Creature const* creature) const;
|
||||
|
||||
private:
|
||||
void LoadLostCorpseLoot(ObjectGuid playerGuid, Loot& loot);
|
||||
[[nodiscard]] bool AnyVendorOptionAvailable(uint32 menuId, Creature const* creature) const;
|
||||
public:
|
||||
[[nodiscard]] uint32 GetChampioningFaction() const { return m_ChampioningFaction; }
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@
|
|||
|
||||
#include "Corpse.h"
|
||||
#include "Creature.h"
|
||||
#include "DatabaseEnvFwd.h"
|
||||
#include "GameObject.h"
|
||||
#include "Group.h"
|
||||
#include "LootItemStorage.h"
|
||||
#include "LootMgr.h"
|
||||
#include "Object.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
|
|
@ -146,7 +148,46 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/)
|
|||
loot = &bones->loot;
|
||||
shareMoney = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
player->SendLootError(guid, LOOT_ERROR_DIDNT_KILL);
|
||||
break;
|
||||
}
|
||||
|
||||
QueryResult result = CharacterDatabase.Query(
|
||||
"SELECT lost_corpse_id, money "
|
||||
"FROM lost_corpses "
|
||||
"WHERE corpse_guid = {} AND player_guid = {} "
|
||||
"AND active = 1 AND money_collected = 0 "
|
||||
"AND SQRT(POW(position_x - {}, 2) + POW(position_y - {}, 2) + POW(position_z - {}, 2)) <= 5",
|
||||
bones->GetGUID().GetCounter(),
|
||||
player->GetGUID().GetCounter(),
|
||||
player->GetPositionX(),
|
||||
player->GetPositionY(),
|
||||
player->GetPositionZ()
|
||||
);
|
||||
|
||||
if (result)
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
uint32 lostCorpseId = fields[0].Get<uint32>();
|
||||
uint32 totalMoney = fields[1].Get<uint32>();
|
||||
|
||||
loot->gold = totalMoney;
|
||||
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE lost_corpses SET money_collected = 1 "
|
||||
"WHERE lost_corpse_id = {}",
|
||||
lostCorpseId
|
||||
);
|
||||
|
||||
player->CheckCorpse(lostCorpseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
loot->gold = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HighGuid::Item:
|
||||
|
|
@ -243,7 +284,7 @@ void WorldSession::HandleLootOpcode(WorldPacket& recvData)
|
|||
recvData >> guid;
|
||||
|
||||
// Check possible cheat
|
||||
if (!GetPlayer()->IsAlive() || !guid.IsCreatureOrVehicle())
|
||||
if (!GetPlayer()->IsAlive())
|
||||
return;
|
||||
|
||||
// interrupt cast
|
||||
|
|
@ -342,12 +383,15 @@ void WorldSession::DoLootRelease(ObjectGuid lguid)
|
|||
|
||||
loot = &corpse->loot;
|
||||
|
||||
ObjectGuid corpseGUID = corpse->GetGUID();
|
||||
player->CheckCorpseByGUID(corpseGUID);
|
||||
|
||||
// Xinef: Buggs client? (Opening loot after closing)
|
||||
//if (loot->isLooted())
|
||||
{
|
||||
loot->clear();
|
||||
corpse->RemoveFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE);
|
||||
}
|
||||
//{
|
||||
// loot->clear();
|
||||
// corpse->RemoveFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE);
|
||||
//}
|
||||
}
|
||||
else if (lguid.IsItem())
|
||||
{
|
||||
|
|
|
|||
139
src/server/scripts/Spells/spell_custom.cpp
Normal file
139
src/server/scripts/Spells/spell_custom.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "ScriptMgr.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "DatabaseEnv.h"
|
||||
|
||||
enum CustomSpells
|
||||
{
|
||||
SPELL_RECOVER_CORPSE_ITEMS = 90001
|
||||
};
|
||||
|
||||
// SPELL_RECOVER_CORPSE_ITEMS, ID 90001
|
||||
class spell_custom_recover_corpse_items : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_custom_recover_corpse_items);
|
||||
|
||||
void HandleAfterCast()
|
||||
{
|
||||
sLog->outMessage("spells", LOG_LEVEL_INFO, "AfterCast triggered for spell %u by %s", GetSpellInfo()->Id, GetCaster()->GetName().c_str());
|
||||
if (GetSpellInfo()->Id != SPELL_RECOVER_CORPSE_ITEMS)
|
||||
return;
|
||||
|
||||
Player* target = GetCaster()->ToPlayer();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
uint64 playerGuid = target->GetGUID().GetCounter();
|
||||
|
||||
// Get latest active corpse
|
||||
QueryResult corpseResult = CharacterDatabase.Query(
|
||||
"SELECT lost_corpse_id, money, money_collected "
|
||||
"FROM lost_corpses "
|
||||
"WHERE player_guid = {} AND active = 1 "
|
||||
"ORDER BY created_at DESC LIMIT 1",
|
||||
playerGuid);
|
||||
|
||||
if (!corpseResult)
|
||||
return;
|
||||
|
||||
uint64 corpseId = (*corpseResult)[0].Get<uint64>();
|
||||
uint32 money = (*corpseResult)[1].Get<uint32>();
|
||||
bool moneyCollected = (*corpseResult)[2].Get<uint8>();
|
||||
|
||||
if (!moneyCollected)
|
||||
{
|
||||
bool modMoney = target->ModifyMoney(money);
|
||||
|
||||
if (!modMoney)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterDatabase.DirectExecute(
|
||||
"UPDATE lost_corpses SET money_collected = 1 "
|
||||
"WHERE lost_corpse_id = {}",
|
||||
corpseId
|
||||
);
|
||||
}
|
||||
|
||||
// Get unlooted items
|
||||
QueryResult itemsResult = CharacterDatabase.Query(
|
||||
"SELECT id, item_entry, count, durability "
|
||||
"FROM lost_corpse_items "
|
||||
"WHERE lost_corpse_id = {} AND looted = 0",
|
||||
corpseId);
|
||||
|
||||
if (!itemsResult)
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = itemsResult->Fetch();
|
||||
|
||||
uint64 rowId = fields[0].Get<uint64>();
|
||||
uint32 itemId = fields[1].Get<uint32>();
|
||||
uint32 count = fields[2].Get<uint32>();
|
||||
uint32 dur = fields[3].Get<uint32>();
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = target->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, count);
|
||||
|
||||
// Inventory full, stop immediately
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
break;
|
||||
|
||||
Item* item = target->StoreNewItem(dest, itemId, true);
|
||||
if (item)
|
||||
{
|
||||
if (ItemTemplate const* proto = item->GetTemplate())
|
||||
{
|
||||
uint32 maxDur = proto->MaxDurability;
|
||||
|
||||
if (maxDur > 0)
|
||||
{
|
||||
item->SetUInt32Value(ITEM_FIELD_DURABILITY, dur);
|
||||
item->SetUInt32Value(ITEM_FIELD_MAXDURABILITY, maxDur);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as looted only if successfully given
|
||||
CharacterDatabase.DirectExecute(
|
||||
"UPDATE lost_corpse_items SET looted = 1 WHERE id = {}",
|
||||
rowId);
|
||||
}
|
||||
|
||||
} while (itemsResult->NextRow());
|
||||
|
||||
// Check that all items were looted
|
||||
QueryResult remaining = CharacterDatabase.Query(
|
||||
"SELECT COUNT(*) FROM lost_corpse_items "
|
||||
"WHERE lost_corpse_id = {} AND looted = 0",
|
||||
corpseId
|
||||
);
|
||||
|
||||
if (remaining)
|
||||
{
|
||||
uint32 count = remaining->Fetch()[0].Get<uint32>();
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE lost_corpses SET active = 0 WHERE lost_corpse_id = {}",
|
||||
corpseId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
AfterCast += SpellCastFn(spell_custom_recover_corpse_items::HandleAfterCast);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_custom_spell_scripts()
|
||||
{
|
||||
RegisterSpellScript(spell_custom_recover_corpse_items);
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ void AddSC_warlock_spell_scripts();
|
|||
void AddSC_warrior_spell_scripts();
|
||||
void AddSC_quest_spell_scripts();
|
||||
void AddSC_item_spell_scripts();
|
||||
void AddSC_custom_spell_scripts();
|
||||
|
||||
// The name of this function should match:
|
||||
// void Add${NameOfDirectory}Scripts()
|
||||
|
|
@ -47,4 +48,5 @@ void AddSpellsScripts()
|
|||
AddSC_warrior_spell_scripts();
|
||||
AddSC_quest_spell_scripts();
|
||||
AddSC_item_spell_scripts();
|
||||
AddSC_custom_spell_scripts();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue