/* * Copyright (C) 2012-2018 CypherCore * * 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 3 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 . */ using Framework.Constants; using Game.DataStorage; using Game.Entities; using Game.Groups; using Game.Guilds; using Game.Loots; using Game.Maps; using Game.Network; using Game.Network.Packets; using System; using System.Collections.Generic; namespace Game { public partial class WorldSession { [WorldPacketHandler(ClientOpcodes.LootItem)] void HandleAutostoreLootItem(LootItemPkt packet) { Player player = GetPlayer(); AELootResult aeResult = player.GetAELootView().Count > 1 ? new AELootResult() : null; /// @todo Implement looting by LootObject guid foreach (LootRequest req in packet.Loot) { Loot loot = null; ObjectGuid lguid = player.GetLootWorldObjectGUID(req.Object); if (lguid.IsGameObject()) { GameObject go = player.GetMap().GetGameObject(lguid); // not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO if (!go || ((go.GetOwnerGUID() != player.GetGUID() && go.GetGoType() != GameObjectTypes.FishingHole) && !go.IsWithinDistInMap(player, SharedConst.InteractionDistance))) { player.SendLootRelease(lguid); continue; } loot = go.loot; } else if (lguid.IsItem()) { Item pItem = player.GetItemByGuid(lguid); if (!pItem) { player.SendLootRelease(lguid); continue; } loot = pItem.loot; } else if (lguid.IsCorpse()) { Corpse bones = ObjectAccessor.GetCorpse(player, lguid); if (!bones) { player.SendLootRelease(lguid); continue; } loot = bones.loot; } 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)) { player.SendLootError(req.Object, lguid, lootAllowed ? LootError.TooFar : LootError.DidntKill); continue; } loot = creature.loot; } player.StoreLootItem((byte)(req.LootListID - 1), loot, aeResult); // If player is removing the last LootItem, delete the empty container. if (loot.isLooted() && lguid.IsItem()) player.GetSession().DoLootRelease(lguid); } if (aeResult != null) { foreach (var resultValue in aeResult.GetByOrder()) { player.SendNewItem(resultValue.item, resultValue.count, false, false, true); player.UpdateCriteria(CriteriaTypes.LootItem, resultValue.item.GetEntry(), resultValue.count); player.UpdateCriteria(CriteriaTypes.LootType, resultValue.item.GetEntry(), resultValue.count, (ulong)resultValue.lootType); player.UpdateCriteria(CriteriaTypes.LootEpicItem, resultValue.item.GetEntry(), resultValue.count); } } } [WorldPacketHandler(ClientOpcodes.LootMoney)] void HandleLootMoney(LootMoney lootMoney) { Player player = GetPlayer(); foreach (var lootView in player.GetAELootView()) { ObjectGuid guid = lootView.Value; Loot loot = null; bool shareMoney = true; switch (guid.GetHigh()) { case HighGuid.GameObject: { GameObject go = player.GetMap().GetGameObject(guid); // 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, SharedConst.InteractionDistance)))) loot = go.loot; break; } case HighGuid.Corpse: // remove insignia ONLY in BG { Corpse bones = ObjectAccessor.GetCorpse(player, guid); if (bones && bones.IsWithinDistInMap(player, SharedConst.InteractionDistance)) { loot = bones.loot; shareMoney = false; } break; } case HighGuid.Item: { Item item = player.GetItemByGuid(guid); if (item) { loot = item.loot; shareMoney = false; } break; } case HighGuid.Creature: 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)) { loot = creature.loot; if (creature.IsAlive()) shareMoney = false; } else player.SendLootError(lootView.Key, guid, lootAllowed ? LootError.TooFar : LootError.DidntKill); break; } default: continue; // unlootable type } if (loot == null) continue; loot.NotifyMoneyRemoved(); if (shareMoney && player.GetGroup() != null) //item, pickpocket and players can be looted only single player { Group group = player.GetGroup(); List playersNear = new List(); for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) { Player member = refe.GetSource(); if (!member) continue; if (player.IsAtGroupRewardDistance(member)) playersNear.Add(member); } uint goldPerPlayer = (uint)(loot.gold / playersNear.Count); foreach (var pl in playersNear) { pl.ModifyMoney(goldPerPlayer); pl.UpdateCriteria(CriteriaTypes.LootMoney, goldPerPlayer); Guild guild = Global.GuildMgr.GetGuildById(pl.GetGuildId()); if (guild) { uint guildGold = MathFunctions.CalculatePct(goldPerPlayer, pl.GetTotalAuraModifier(AuraType.DepositBonusMoneyInGuildBankOnLoot)); if (guildGold != 0) guild.HandleMemberDepositMoney(this, guildGold, true); } LootMoneyNotify packet = new LootMoneyNotify(); packet.Money = goldPerPlayer; packet.SoleLooter = playersNear.Count <= 1 ? true : false; pl.SendPacket(packet); } } else { player.ModifyMoney(loot.gold); player.UpdateCriteria(CriteriaTypes.LootMoney, loot.gold); Guild guild = Global.GuildMgr.GetGuildById(player.GetGuildId()); if (guild) { uint guildGold = MathFunctions.CalculatePct(loot.gold, player.GetTotalAuraModifier(AuraType.DepositBonusMoneyInGuildBankOnLoot)); if (guildGold != 0) guild.HandleMemberDepositMoney(this, guildGold, true); } LootMoneyNotify packet = new LootMoneyNotify(); packet.Money = loot.gold; packet.SoleLooter = true; // "You loot..." SendPacket(packet); } loot.gold = 0; // Delete the money loot record from the DB if (!loot.containerID.IsEmpty()) loot.DeleteLootMoneyFromContainerItemDB(); // Delete container if empty if (loot.isLooted() && guid.IsItem()) player.GetSession().DoLootRelease(guid); } } class AELootCreatureCheck : ICheck { public static float LootDistance = 30.0f; public AELootCreatureCheck(Player looter, ObjectGuid mainLootTarget) { _looter = looter; _mainLootTarget = mainLootTarget; } public bool Invoke(Creature creature) { if (creature.IsAlive()) return false; if (creature.GetGUID() == _mainLootTarget) return false; if (!_looter.IsWithinDist(creature, LootDistance)) return false; return _looter.isAllowedToLoot(creature); } Player _looter; ObjectGuid _mainLootTarget; } [WorldPacketHandler(ClientOpcodes.LootUnit)] void HandleLoot(LootUnit packet) { // Check possible cheat if (!GetPlayer().IsAlive() || !packet.Unit.IsCreatureOrVehicle()) return; List corpses = new List(); AELootCreatureCheck check = new AELootCreatureCheck(_player, packet.Unit); CreatureListSearcher searcher = new CreatureListSearcher(_player, corpses, check); Cell.VisitGridObjects(_player, searcher, AELootCreatureCheck.LootDistance); if (!corpses.Empty()) SendPacket(new AELootTargets((uint)corpses.Count + 1)); GetPlayer().SendLoot(packet.Unit, LootType.Corpse); if (!corpses.Empty()) { // main target SendPacket(new AELootTargetsAck()); foreach (Creature creature in corpses) { GetPlayer().SendLoot(creature.GetGUID(), LootType.Corpse, true); SendPacket(new AELootTargetsAck()); } } // interrupt cast if (GetPlayer().IsNonMeleeSpellCast(false)) GetPlayer().InterruptNonMeleeSpells(false); } [WorldPacketHandler(ClientOpcodes.LootRelease)] void HandleLootRelease(LootRelease packet) { // cheaters can modify lguid to prevent correct apply loot release code and re-loot // use internal stored guid if (GetPlayer().HasLootWorldObjectGUID(packet.Unit)) DoLootRelease(packet.Unit); } public void DoLootRelease(ObjectGuid lguid) { Player player = GetPlayer(); Loot loot; if (player.GetLootGUID() == lguid) player.SetLootGUID(ObjectGuid.Empty); player.SendLootRelease(lguid); player.RemoveFlag(UnitFields.Flags, UnitFlags.Looting); if (!player.IsInWorld) return; if (lguid.IsGameObject()) { GameObject go = player.GetMap().GetGameObject(lguid); // not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO if (!go || ((go.GetOwnerGUID() != player.GetGUID() && go.GetGoType() != GameObjectTypes.FishingHole) && !go.IsWithinDistInMap(player, SharedConst.InteractionDistance))) return; loot = go.loot; if (go.GetGoType() == GameObjectTypes.Door) { // locked doors are opened with spelleffect openlock, prevent remove its as looted go.UseDoorOrButton(); } else if (loot.isLooted() || go.GetGoType() == GameObjectTypes.FishingNode) { if (go.GetGoType() == GameObjectTypes.FishingHole) { // The fishing hole used once more go.AddUse(); // if the max usage is reached, will be despawned in next tick if (go.GetUseCount() >= go.m_goValue.FishingHole.MaxOpens) go.SetLootState(LootState.JustDeactivated); else go.SetLootState(LootState.Ready); } else go.SetLootState(LootState.JustDeactivated); loot.clear(); } else { // not fully looted object go.SetLootState(LootState.Activated, player); // if the round robin player release, reset it. if (player.GetGUID() == loot.roundRobinPlayer) loot.roundRobinPlayer.Clear(); } } else if (lguid.IsCorpse()) // ONLY remove insignia at BG { Corpse corpse = ObjectAccessor.GetCorpse(player, lguid); if (!corpse || !corpse.IsWithinDistInMap(player, SharedConst.InteractionDistance)) return; loot = corpse.loot; if (loot.isLooted()) { loot.clear(); corpse.RemoveFlag(CorpseFields.DynamicFlags, 0x0001); } } else if (lguid.IsItem()) { Item pItem = player.GetItemByGuid(lguid); if (!pItem) return; ItemTemplate proto = pItem.GetTemplate(); // destroy only 5 items from stack in case prospecting and milling if (proto.GetFlags().HasAnyFlag(ItemFlags.IsProspectable | ItemFlags.IsMillable)) { pItem.m_lootGenerated = false; pItem.loot.clear(); uint count = pItem.GetCount(); // >=5 checked in spell code, but will work for cheating cases also with removing from another stacks. if (count > 5) count = 5; player.DestroyItemCount(pItem, ref count, true); } else { if (pItem.loot.isLooted() || !proto.GetFlags().HasAnyFlag(ItemFlags.HasLoot)) // Only delete item if no loot or money (unlooted loot is saved to db) player.DestroyItem(pItem.GetBagSlot(), pItem.GetSlot(), true); } return; // item can be looted only single 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)) return; loot = creature.loot; if (loot.isLooted()) { creature.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable); // skip pickpocketing loot for speed, skinning timer reduction is no-op in fact if (!creature.IsAlive()) creature.AllLootRemovedFromCorpse(); loot.clear(); } else { // if the round robin player release, reset it. if (player.GetGUID() == loot.roundRobinPlayer) { loot.roundRobinPlayer.Clear(); Group group = player.GetGroup(); if (group) { if (group.GetLootMethod() != LootMethod.MasterLoot) group.SendLooter(creature, null); } // force dynflag update to update looter and lootable info creature.ForceValuesUpdateAtIndex(ObjectFields.DynamicFlags); } } } //Player is not looking at loot list, he doesn't need to see updates on the loot list loot.RemoveLooter(player.GetGUID()); player.RemoveAELootedObject(loot.GetGUID()); } void DoLootReleaseAll() { Dictionary lootView = _player.GetAELootView(); foreach (var lootPair in lootView) DoLootRelease(lootPair.Value); } //[WorldPacketHandler(ClientOpcodes.MasterLootItem)] void HandleLootMasterGive(WorldPacket packet) { ObjectGuid lootguid = packet.ReadPackedGuid(); byte slotid = packet.ReadUInt8(); ObjectGuid target_playerguid = packet.ReadPackedGuid(); if (GetPlayer().GetGroup() == null || GetPlayer().GetGroup().GetLooterGuid() != GetPlayer().GetGUID() || GetPlayer().GetGroup().GetLootMethod() != LootMethod.MasterLoot) { GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.DidntKill); return; } Player target = Global.ObjAccessor.FindPlayer(target_playerguid); if (!target) { GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.PlayerNotFound); return; } Log.outDebug(LogFilter.Network, "HandleLootMasterGive (CMSG_LOOT_MASTER_GIVE, 0x02A3) Target = [{0}].", target.GetName()); if (GetPlayer().GetLootGUID() != lootguid) { GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.DidntKill); return; } if (!GetPlayer().IsInRaidWith(target) || !GetPlayer().IsInMap(target)) { GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterOther); Log.outInfo(LogFilter.Loot, "MasterLootItem: Player {0} tried to give an item to ineligible player {1}!", GetPlayer().GetName(), target.GetName()); return; } Loot loot = null; if (GetPlayer().GetLootGUID().IsCreatureOrVehicle()) { Creature creature = GetPlayer().GetMap().GetCreature(lootguid); if (creature == null) return; loot = creature.loot; } else if (GetPlayer().GetLootGUID().IsGameObject()) { GameObject pGO = GetPlayer().GetMap().GetGameObject(lootguid); if (pGO == null) return; loot = pGO.loot; } if (loot == null) return; if (slotid >= loot.items.Count + loot.quest_items.Count) { Log.outDebug(LogFilter.Loot, "MasterLootItem: Player {0} might be using a hack! (slot {1}, size {2})", GetPlayer().GetName(), slotid, loot.items.Count); return; } LootItem item = slotid >= loot.items.Count ? loot.quest_items[slotid - loot.items.Count] : loot.items[slotid]; List dest = new List(); ; InventoryResult msg = target.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); if (item.follow_loot_rules && !item.AllowedForPlayer(target)) msg = InventoryResult.CantEquipEver; if (msg != InventoryResult.Ok) { if (msg == InventoryResult.ItemMaxCount) GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterUniqueItem); else if (msg == InventoryResult.InvFull) GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterInvFull); else GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterOther); target.SendEquipError(msg, null, null, item.itemid); return; } // not move item from loot to target inventory Item newitem = target.StoreNewItem(dest, item.itemid, true, item.randomPropertyId, item.GetAllowedLooters(), item.context, item.BonusListIDs); target.SendNewItem(newitem, item.count, false, false, true); target.UpdateCriteria(CriteriaTypes.LootItem, item.itemid, item.count); target.UpdateCriteria(CriteriaTypes.LootType, item.itemid, item.count, (ulong)loot.loot_type); target.UpdateCriteria(CriteriaTypes.LootEpicItem, item.itemid, item.count); // mark as looted item.count = 0; item.is_looted = true; loot.NotifyItemRemoved(slotid); --loot.unlootedCount; } [WorldPacketHandler(ClientOpcodes.SetLootSpecialization)] void HandleSetLootSpecialization(SetLootSpecialization packet) { if (packet.SpecID != 0) { ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(packet.SpecID); if (chrSpec != null) { if (chrSpec.ClassID == (uint)GetPlayer().GetClass()) GetPlayer().SetLootSpecId(packet.SpecID); } } else GetPlayer().SetLootSpecId(0); } } }