Core/Loot: Allocate Loot separately from objects

Port From (https://github.com/TrinityCore/TrinityCore/commit/7957e2d380e08fa831765f610c0e29d2f3e11a04)
This commit is contained in:
hondacrx
2022-09-02 14:14:04 -04:00
parent 0e4832809c
commit 7bba4bdd79
15 changed files with 160 additions and 126 deletions
+1 -1
View File
@@ -325,7 +325,7 @@ namespace Game.Chat
}
Loot loot = creatureTarget.loot;
if (!creatureTarget.IsDead() || loot.Empty())
if (!creatureTarget.IsDead() || loot == null || loot.Empty())
{
handler.SendSysMessage(CypherStrings.CommandNotDeadOrNoLoot, creatureTarget.GetName());
return false;
+4 -2
View File
@@ -38,7 +38,7 @@ namespace Game.Entities
m_updateFlag.Stationary = true;
m_corpseData = new CorpseData();
m_corpseData = new();
m_time = GameTime.GetGameTime();
}
@@ -323,9 +323,11 @@ namespace Game.Entities
public CellCoord GetCellCoord() { return _cellCoord; }
public void SetCellCoord(CellCoord cellCoord) { _cellCoord = cellCoord; }
public override Loot GetLootForPlayer(Player player) { return loot; }
public CorpseData m_corpseData;
public Loot loot = new();
public Loot loot;
public Player lootRecipient;
CorpseType m_type;
@@ -90,7 +90,7 @@ namespace Game.Entities
// vendor items
List<VendorItemCount> m_vendorItemCounts = new();
public Loot loot = new();
public Loot loot;
public uint m_groupLootTimer; // (msecs)timer used for group loot
public ObjectGuid lootingGroupLowGUID; // used to find group which is looting corpse
ObjectGuid m_lootRecipient;
+5 -11
View File
@@ -160,7 +160,7 @@ namespace Game.Entities
SetDeathState(DeathState.Dead);
RemoveAllAuras();
//DestroyForNearbyPlayers(); // old UpdateObjectVisibility()
loot.Clear();
loot = null;
uint respawnDelay = m_respawnDelay;
CreatureAI ai = GetAI();
if (ai != null)
@@ -256,10 +256,6 @@ namespace Game.Entities
if (cInfo == null)
cInfo = normalInfo;
// Initialize loot duplicate count depending on raid difficulty
if (GetMap().Is25ManRaid())
loot.maxDuplicates = 3;
SetEntry(entry); // normal entry always
m_creatureInfo = cInfo; // map mode related always
@@ -511,7 +507,7 @@ namespace Game.Entities
if (IsEngaged())
AIUpdateTick(diff);
if (m_groupLootTimer != 0 && !lootingGroupLowGUID.IsEmpty())
if (loot != null && m_groupLootTimer != 0 && !lootingGroupLowGUID.IsEmpty())
{
if (m_groupLootTimer <= diff)
{
@@ -1941,7 +1937,7 @@ namespace Game.Entities
Log.outDebug(LogFilter.Unit, "Respawning creature {0} ({1})", GetName(), GetGUID().ToString());
m_respawnTime = 0;
ResetPickPocketRefillTimer();
loot.Clear();
loot = null;
if (m_originalEntry != GetEntry())
UpdateEntry(m_originalEntry);
@@ -2578,7 +2574,7 @@ namespace Game.Entities
public void AllLootRemovedFromCorpse()
{
if (loot.loot_type != LootType.Skinning && !IsPet() && GetCreatureTemplate().SkinLootId != 0 && HasLootRecipient())
if ((loot == null || loot.loot_type != LootType.Skinning) && !IsPet() && GetCreatureTemplate().SkinLootId != 0 && HasLootRecipient())
if (LootStorage.Skinning.HaveLootFor(GetCreatureTemplate().SkinLootId))
SetUnitFlag(UnitFlags.Skinnable);
@@ -2591,7 +2587,7 @@ namespace Game.Entities
float decayRate = m_ignoreCorpseDecayRatio ? 1.0f : WorldConfig.GetFloatValue(WorldCfg.RateCorpseDecayLooted);
// corpse skinnable, but without skinning flag, and then skinned, corpse will despawn next update
if (loot.loot_type == LootType.Skinning)
if (loot != null && loot.loot_type == LootType.Skinning)
m_corpseRemoveTime = now;
else
m_corpseRemoveTime = now + (uint)(m_corpseDelay * decayRate);
@@ -3280,8 +3276,6 @@ namespace Game.Entities
// checked at creature_template loading
DefaultMovementType = (MovementGeneratorType)data.movementType;
loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, GetMapId(), data.Id, GetMap().GenerateLowGuid(HighGuid.LootObject)));
if (addToMap && !GetMap().AddToMap(this))
return false;
return true;
@@ -373,10 +373,6 @@ namespace Game.Entities
LastUsedScriptID = GetGoInfo().ScriptId;
AIM_Initialize();
// Initialize loot duplicate count depending on raid difficulty
if (map.Is25ManRaid())
loot.maxDuplicates = 3;
if (spawnid != 0)
m_spawnId = spawnid;
@@ -701,7 +697,7 @@ namespace Game.Entities
}
break;
case GameObjectTypes.Chest:
if (m_groupLootTimer != 0)
if (loot != null && m_groupLootTimer != 0)
{
if (m_groupLootTimer <= diff)
{
@@ -805,7 +801,7 @@ namespace Game.Entities
return;
}
loot.Clear();
loot = null;
// Do not delete chests or goobers that are not consumed on loot, while still allowing them to despawn when they expire if summoned
bool isSummonedAndExpired = (GetOwner() != null || GetSpellId() != 0) && m_respawnTime == 0;
@@ -3315,6 +3311,8 @@ namespace Game.Entities
bool HasLootRecipient() { return !m_lootRecipient.IsEmpty() || !m_lootRecipientGroup.IsEmpty(); }
public override Loot GetLootForPlayer(Player player) { return loot; }
public override uint GetLevelForTarget(WorldObject target)
{
Unit owner = GetOwner();
@@ -3441,7 +3439,7 @@ namespace Game.Entities
Dictionary<uint, ObjectGuid> ChairListSlots = new();
List<ObjectGuid> m_SkillupList = new();
public Loot loot = new();
public Loot loot;
public GameObjectModel m_model;
+4 -4
View File
@@ -43,8 +43,6 @@ namespace Game.Entities
uState = ItemUpdateState.New;
uQueuePos = -1;
m_lastPlayedTimeUpdate = GameTime.GetGameTime();
loot = new Loot();
}
public virtual bool Create(ulong guidlow, uint itemId, ItemContext context, Player owner)
@@ -386,7 +384,7 @@ namespace Game.Entities
}
// Delete the items if this is a container
if (!loot.IsLooted())
if (loot != null && !loot.IsLooted())
Global.LootItemStorage.RemoveStoredLootForContainer(GetGUID().GetCounter());
Dispose();
@@ -689,7 +687,7 @@ namespace Game.Entities
DeleteFromDB(trans, GetGUID().GetCounter());
// Delete the items if this is a container
if (!loot.IsLooted())
if (loot != null && !loot.IsLooted())
Global.LootItemStorage.RemoveStoredLootForContainer(GetGUID().GetCounter());
}
@@ -2668,6 +2666,8 @@ namespace Game.Entities
public ItemEffectRecord[] GetEffects() { return _bonusData.Effects[0.._bonusData.EffectCount]; }
public override Loot GetLootForPlayer(Player player) { return loot; }
//Static
public static bool ItemCanGoIntoBag(ItemTemplate pProto, ItemTemplate pBagProto)
{
+3 -1
View File
@@ -20,6 +20,7 @@ using Framework.Dynamic;
using Game.AI;
using Game.BattleFields;
using Game.DataStorage;
using Game.Loots;
using Game.Maps;
using Game.Movement;
using Game.Networking;
@@ -28,7 +29,6 @@ using Game.Scenarios;
using Game.Spells;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace Game.Entities
@@ -779,6 +779,8 @@ namespace Game.Entities
return $"{base.GetDebugInfo()}\n{GetGUID()} Entry: {GetEntry()}\nName: { GetName()}";
}
public virtual Loot GetLootForPlayer(Player player) { return null; }
public abstract void BuildValuesCreate(WorldPacket data, Player target);
public abstract void BuildValuesUpdate(WorldPacket data, Player target);
+36 -32
View File
@@ -6105,9 +6105,23 @@ namespace Game.Entities
// Now we must make bones lootable, and send player loot
bones.SetCorpseDynamicFlag(CorpseDynFlags.Lootable);
// We store the level of our player in the gold field
// We retrieve this information at Player.SendLoot()
bones.loot.gold = GetLevel();
bones.loot = new Loot();
bones.loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, GetMapId(), 0, GetMap().GenerateLowGuid(HighGuid.LootObject)));
// For AV Achievement
Battleground bg = GetBattleground();
if (bg != null)
{
if (bg.GetTypeID(true) == BattlegroundTypeId.AV)
bones.loot.FillLoot(1, LootStorage.Creature, this, true);
}
// For wintergrasp Quests
else if (GetZoneId() == (uint)AreaId.Wintergrasp)
bones.loot.FillLoot(1, LootStorage.Creature, this, true);
// It may need a better formula
// Now it works like this: lvl10: ~6copper, lvl70: ~9silver
bones.loot.gold = (uint)(RandomHelper.URand(50, 150) * 0.016f * Math.Pow((float)GetLevel() / 5.76f, 2.5f) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney));
bones.lootRecipient = looterPlr;
looterPlr.SendLoot(bones.GetGUID(), LootType.Insignia);
}
@@ -6168,12 +6182,12 @@ namespace Game.Entities
return;
}
loot = go.loot;
loot = go.GetLootForPlayer(this);
// loot was generated and respawntime has passed since then, allow to recreate loot
// to avoid bugs, this rule covers spawned gameobjects only
// Don't allow to regenerate chest loot inside instances and raids, to avoid exploits with duplicate boss loot being given for some encounters
if (go.IsSpawnedByDefault() && go.GetLootState() == LootState.Activated && !go.loot.IsLooted() && !go.GetMap().Instanceable() && go.GetLootGenerationTime() + go.GetRespawnDelay() < GameTime.GetGameTime())
if (go.IsSpawnedByDefault() && go.GetLootState() == LootState.Activated && (loot == null || loot.IsLooted()) && !go.GetMap().Instanceable() && go.GetLootGenerationTime() + go.GetRespawnDelay() < GameTime.GetGameTime())
go.SetLootState(LootState.Ready);
if (go.GetLootState() == LootState.Ready)
@@ -6189,6 +6203,13 @@ namespace Game.Entities
}
}
loot = new Loot();
loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, go.GetMapId(), 0, go.GetMap().GenerateLowGuid(HighGuid.LootObject)));
if (go.GetMap().Is25ManRaid())
loot.maxDuplicates = 3;
go.loot = loot;
if (lootid != 0)
{
loot.Clear();
@@ -6204,7 +6225,7 @@ namespace Game.Entities
go.SetLootGenerationTime();
// get next RR player (for next loot)
if (groupRules)
if (groupRules && !loot.Empty())
group.UpdateLooterGuid(go);
}
@@ -6277,14 +6298,16 @@ namespace Game.Entities
permission = PermissionTypes.Owner;
loot = item.loot;
loot = item.GetLootForPlayer(this);
// If item doesn't already have loot, attempt to load it. If that
// fails then this is first time opening, generate loot
if (!item.m_lootGenerated && !Global.LootItemStorage.LoadStoredLoot(item, this))
{
item.m_lootGenerated = true;
loot.Clear();
loot = new Loot();
loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, GetMapId(), 0, GetMap().GenerateLowGuid(HighGuid.LootObject)));
item.loot = loot;
switch (loot_type)
{
@@ -6320,29 +6343,9 @@ namespace Game.Entities
return;
}
loot = bones.loot;
loot = bones.GetLootForPlayer(this);
if (loot.loot_type == LootType.None)
{
uint pLevel = bones.loot.gold;
bones.loot.Clear();
// For AV Achievement
Battleground bg = GetBattleground();
if (bg)
{
if (bg.GetTypeID(true) == BattlegroundTypeId.AV)
loot.FillLoot(SharedConst.PlayerCorpseLootEntry, LootStorage.Creature, this, true);
}
else if (GetZoneId() == (uint)AreaId.Wintergrasp)
loot.FillLoot(SharedConst.PlayerCorpseLootEntry, LootStorage.Creature, this, true);
// It may need a better formula
// Now it works like this: lvl10: ~6copper, lvl70: ~9silver
bones.loot.gold = (uint)(RandomHelper.URand(50, 150) * 0.016f * Math.Pow(pLevel / 5.76f, 2.5f) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney));
}
if (bones.lootRecipient != this)
if (bones.lootRecipient != null && bones.lootRecipient != this)
permission = PermissionTypes.None;
else
permission = PermissionTypes.Owner;
@@ -6364,7 +6367,7 @@ namespace Game.Entities
return;
}
loot = creature.loot;
loot = creature.GetLootForPlayer(this);
if (loot_type == LootType.Pickpocketing)
{
@@ -6491,6 +6494,7 @@ namespace Game.Entities
}
// need know merged fishing/corpse loot type for achievements
if (loot != null)
loot.loot_type = loot_type;
if (permission != PermissionTypes.None)
@@ -6532,7 +6536,7 @@ namespace Game.Entities
SetUnitFlag(UnitFlags.Looting);
}
else
SendLootError(loot.GetGUID(), guid, LootError.DidntKill);
SendLootError(loot != null ? loot.GetGUID() : ObjectGuid.Empty, guid, LootError.DidntKill);
}
public void SendLootError(ObjectGuid lootObj, ObjectGuid owner, LootError error)
+2 -2
View File
@@ -3085,8 +3085,8 @@ namespace Game.Entities
if (HasPendingBind())
return false;
Loot loot = creature.loot;
if (loot.IsLooted()) // nothing to loot or everything looted.
Loot loot = creature.GetLootForPlayer(this);
if (loot == null || loot.IsLooted()) // nothing to loot or everything looted.
return false;
if (!loot.HasItemForAll() && !loot.HasItemFor(this)) // no loot in creature for this player
return false;
+12 -12
View File
@@ -772,23 +772,16 @@ namespace Game.Entities
}
}
else
{
player.SendPacket(partyKillLog);
if (creature != null)
{
LootList lootList = new();
lootList.Owner = creature.GetGUID();
lootList.LootObj = creature.loot.GetGUID();
player.SendMessageToSet(lootList, true);
}
}
if (creature)
{
creature.loot = new Loot();
Loot loot = creature.loot;
loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, creature.GetMapId(), 0, creature.GetMap().GenerateLowGuid(HighGuid.LootObject)));
if (creature.GetMap().Is25ManRaid())
loot.maxDuplicates = 3;
loot.Clear();
uint lootid = creature.GetCreatureTemplate().LootId;
if (lootid != 0)
loot.FillLoot(lootid, LootStorage.Creature, looter, false, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext());
@@ -807,6 +800,13 @@ namespace Game.Entities
if (!loot.Empty())
group.UpdateLooterGuid(creature);
}
else
{
LootList lootList = new();
lootList.Owner = creature.GetGUID();
lootList.LootObj = creature.loot.GetGUID();
player.SendMessageToSet(lootList, true);
}
}
player.RewardPlayerAndGroupAtKill(victim, false);
@@ -907,7 +907,7 @@ namespace Game.Entities
if (!creature.IsPet())
{
// must be after setDeathState which resets dynamic flags
if (!creature.loot.IsLooted())
if (creature.loot != null && !creature.loot.IsLooted())
creature.SetDynamicFlag(UnitDynFlags.Lootable);
else
creature.AllLootRemovedFromCorpse();
+68 -35
View File
@@ -53,7 +53,7 @@ namespace Game
continue;
}
loot = go.loot;
loot = go.GetLootForPlayer(player);
}
else if (lguid.IsItem())
{
@@ -65,7 +65,7 @@ namespace Game
continue;
}
loot = pItem.loot;
loot = pItem.GetLootForPlayer(player);
}
else if (lguid.IsCorpse())
{
@@ -76,20 +76,35 @@ namespace Game
continue;
}
loot = bones.loot;
loot = bones.GetLootForPlayer(player);
}
else
{
Creature creature = player.GetMap().GetCreature(lguid);
bool lootAllowed = creature && creature.IsAlive() == (player.GetClass() == Class.Rogue && creature.loot.loot_type == LootType.Pickpocketing);
if (!lootAllowed || !creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance))
if (creature == null)
{
player.SendLootError(req.Object, lguid, lootAllowed ? LootError.TooFar : LootError.DidntKill);
player.SendLootError(req.Object, lguid, LootError.NoLoot);
continue;
}
loot = creature.loot;
if (!creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance))
{
player.SendLootError(req.Object, lguid, LootError.TooFar);
continue;
}
loot = creature.GetLootForPlayer(player);
if (creature.IsAlive() != (loot != null && loot.loot_type == LootType.Pickpocketing))
{
player.SendLootError(req.Object, lguid, LootError.DidntKill);
continue;
}
}
if (loot == null)
{
player.SendLootRelease(lguid);
continue;
}
player.StoreLootItem(lguid, (byte)(req.LootListID - 1), loot, aeResult);
@@ -132,7 +147,7 @@ namespace Game
// do not check distance for GO if player is the owner of it (ex. fishing bobber)
if (go && (go.GetOwnerGUID() == player.GetGUID() || go.IsWithinDistInMap(player)))
loot = go.loot;
loot = go.GetLootForPlayer(player);
break;
}
@@ -142,7 +157,7 @@ namespace Game
if (bones && bones.IsWithinDistInMap(player, SharedConst.InteractionDistance))
{
loot = bones.loot;
loot = bones.GetLootForPlayer(player);
shareMoney = false;
}
@@ -153,7 +168,7 @@ namespace Game
Item item = player.GetItemByGuid(guid);
if (item)
{
loot = item.loot;
loot = item.GetLootForPlayer(player);
shareMoney = false;
}
break;
@@ -162,15 +177,27 @@ namespace Game
case HighGuid.Vehicle:
{
Creature creature = player.GetMap().GetCreature(guid);
bool lootAllowed = creature && creature.IsAlive() == (player.GetClass() == Class.Rogue && creature.loot.loot_type == LootType.Pickpocketing);
if (lootAllowed && creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance))
if (creature == null)
{
loot = creature.loot;
if (creature.IsAlive())
shareMoney = false;
player.SendLootError(lootView.Key, guid, LootError.NoLoot);
continue;
}
else
player.SendLootError(lootView.Key, guid, lootAllowed ? LootError.TooFar : LootError.DidntKill);
if (!creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance))
{
player.SendLootError(lootView.Key, guid, LootError.TooFar);
continue;
}
loot = creature.GetLootForPlayer(player);
if (creature.IsAlive() != (loot != null && loot.loot_type == LootType.Pickpocketing))
{
player.SendLootError(lootView.Key, guid, LootError.DidntKill);
continue;
}
if (loot != null && loot.loot_type != LootType.Corpse)
shareMoney = false;
break;
}
default:
@@ -336,7 +363,7 @@ namespace Game
if (!go || ((go.GetOwnerGUID() != player.GetGUID() && go.GetGoType() != GameObjectTypes.FishingHole) && !go.IsWithinDistInMap(player)))
return;
loot = go.loot;
loot = go.GetLootForPlayer(player);
if (go.GetGoType() == GameObjectTypes.Door)
{
@@ -374,9 +401,9 @@ namespace Game
if (!corpse || !corpse.IsWithinDistInMap(player, SharedConst.InteractionDistance))
return;
loot = corpse.loot;
loot = corpse.GetLootForPlayer(player);
if (loot.IsLooted())
if (loot != null && loot.IsLooted())
{
loot.Clear();
corpse.RemoveCorpseDynamicFlag(CorpseDynFlags.Lootable);
@@ -390,11 +417,13 @@ namespace Game
ItemTemplate proto = pItem.GetTemplate();
loot = pItem.GetLootForPlayer(player);
// destroy only 5 items from stack in case prospecting and milling
if (pItem.loot.loot_type == LootType.Prospecting || pItem.loot.loot_type == LootType.Milling)
if (loot != null && (loot.loot_type == LootType.Prospecting || loot.loot_type == LootType.Milling))
{
pItem.m_lootGenerated = false;
pItem.loot.Clear();
pItem.loot = null;
uint count = pItem.GetCount();
@@ -406,7 +435,8 @@ namespace Game
}
else
{
if (pItem.loot.IsLooted() || !proto.HasFlag(ItemFlags.HasLoot)) // Only delete item if no loot or money (unlooted loot is saved to db)
// Only delete item if no loot or money (unlooted loot is saved to db) or if it isn't an openable item
if ((loot != null && loot.IsLooted()) || !proto.HasFlag(ItemFlags.HasLoot))
player.DestroyItem(pItem.GetBagSlot(), pItem.GetSlot(), true);
}
return; // item can be looted only single player
@@ -414,21 +444,23 @@ namespace Game
else
{
Creature creature = player.GetMap().GetCreature(lguid);
bool lootAllowed = creature && creature.IsAlive() == (player.GetClass() == Class.Rogue && creature.loot.loot_type == LootType.Pickpocketing);
if (!lootAllowed || !creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance))
if (creature == null)
return;
loot = creature.loot;
if (loot.IsLooted())
if (!creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance))
return;
loot = creature.GetLootForPlayer(player);
if (creature.IsAlive() != (loot != null && loot.loot_type == LootType.Pickpocketing))
return;
if (loot == null || loot.IsLooted())
{
creature.RemoveDynamicFlag(UnitDynFlags.Lootable);
// skip pickpocketing loot for speed, skinning timer reduction is no-op in fact
if (!creature.IsAlive())
creature.AllLootRemovedFromCorpse();
loot.Clear();
}
else
{
@@ -451,6 +483,7 @@ namespace Game
}
//Player is not looking at loot list, he doesn't need to see updates on the loot list
if (loot != null)
loot.RemoveLooter(player.GetGUID());
}
@@ -498,15 +531,15 @@ namespace Game
if (!creature)
return;
loot = creature.loot;
loot = creature.GetLootForPlayer(_player);
}
else if (GetPlayer().GetLootGUID().IsGameObject())
{
GameObject pGO = GetPlayer().GetMap().GetGameObject(lootguid);
if (!pGO)
GameObject go = GetPlayer().GetMap().GetGameObject(lootguid);
if (!go)
return;
loot = pGO.loot;
loot = go.GetLootForPlayer(_player);
}
if (loot == null)
+2 -2
View File
@@ -165,9 +165,9 @@ namespace Game.Loots
public class Loot
{
public Loot(uint _gold = 0)
public Loot()
{
gold = _gold;
gold = 0;
unlootedCount = 0;
loot_type = LootType.None;
maxDuplicates = 1;
+1 -1
View File
@@ -99,7 +99,7 @@ namespace Game.Loots
public bool LoadStoredLoot(Item item, Player player)
{
Loot loot = item.loot;
Loot loot = item.GetLootForPlayer(player);
if (!_lootItemStorage.ContainsKey(item.GetGUID().GetCounter()))
return false;
+2 -1
View File
@@ -5187,7 +5187,8 @@ namespace Game.Spells
return SpellCastResult.TargetUnskinnable;
Creature creature = m_targets.GetUnitTarget().ToCreature();
if (!creature.IsCritter() && !creature.loot.IsLooted())
Loot loot = creature.GetLootForPlayer(m_caster.ToPlayer());
if (loot != null && !loot.IsLooted())
return SpellCastResult.TargetNotLooted;
SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill();