diff --git a/Source/Framework/Constants/SharedConst.cs b/Source/Framework/Constants/SharedConst.cs index 52740c35e..73dc57529 100644 --- a/Source/Framework/Constants/SharedConst.cs +++ b/Source/Framework/Constants/SharedConst.cs @@ -70,6 +70,7 @@ namespace Framework.Constants /// public const int MaxNRLootItems = 16; public const int MaxNRQuestItems = 32; + public const int PlayerCorpseLootEntry = 1; /// /// Movement Const diff --git a/Source/Game/BattleFields/Zones/WinterGrasp.cs b/Source/Game/BattleFields/Zones/WinterGrasp.cs index 82344cd4e..34d025f7a 100644 --- a/Source/Game/BattleFields/Zones/WinterGrasp.cs +++ b/Source/Game/BattleFields/Zones/WinterGrasp.cs @@ -567,7 +567,11 @@ namespace Game.BattleFields return; if (victim.IsTypeId(TypeId.Player)) + { HandlePromotion(killer, victim); + // Allow to Skin non-released corpse + victim.AddUnitFlag(UnitFlags.Skinnable); + } // @todo Recent PvP activity worldstate } diff --git a/Source/Game/BattleFields/Zones/WinterGraspConst.cs b/Source/Game/BattleFields/Zones/WinterGraspConst.cs index 0efdd1a36..a9b573e1c 100644 --- a/Source/Game/BattleFields/Zones/WinterGraspConst.cs +++ b/Source/Game/BattleFields/Zones/WinterGraspConst.cs @@ -548,7 +548,7 @@ namespace Game.BattleFields public const uint Max = 7; } - struct WintergraspAreaIds + public struct WintergraspAreaIds { public const uint WintergraspFortress = 4575; public const uint TheSunkenRing = 4538; diff --git a/Source/Game/Entities/Corpse.cs b/Source/Game/Entities/Corpse.cs index 67ec30a76..e55dc6cf6 100644 --- a/Source/Game/Entities/Corpse.cs +++ b/Source/Game/Entities/Corpse.cs @@ -281,7 +281,6 @@ namespace Game.Entities public Loot loot = new Loot(); public Player lootRecipient; - public bool lootForBody; CorpseType m_type; long m_time; diff --git a/Source/Game/Entities/Creature/Creature.Fields.cs b/Source/Game/Entities/Creature/Creature.Fields.cs index dd3d12d44..2433a363b 100644 --- a/Source/Game/Entities/Creature/Creature.Fields.cs +++ b/Source/Game/Entities/Creature/Creature.Fields.cs @@ -93,7 +93,6 @@ namespace Game.Entities public ObjectGuid lootingGroupLowGUID; // used to find group which is looting corpse ObjectGuid m_lootRecipient; ObjectGuid m_lootRecipientGroup; - ObjectGuid _skinner; } public enum ObjectCellMoveState diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index bed0c7acd..8c10a05d6 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -940,8 +940,7 @@ namespace Game.Entities } public void ResetPickPocketRefillTimer() { _pickpocketLootRestore = 0; } public bool CanGeneratePickPocketLoot() { return _pickpocketLootRestore <= Time.UnixTime; } - public void SetSkinner(ObjectGuid guid) { _skinner = guid; } - public ObjectGuid GetSkinner() { return _skinner; } // Returns the player who skinned this creature + public ObjectGuid GetLootRecipientGUID() { return m_lootRecipient; } public Player GetLootRecipient() { @@ -958,7 +957,7 @@ namespace Game.Entities return Global.GroupMgr.GetGroupByGUID(m_lootRecipientGroup); } - public void SetLootRecipient(Unit unit) + public void SetLootRecipient(Unit unit, bool withGroup = true) { // set the player whose group should receive the right // to loot the creature after it dies @@ -980,9 +979,14 @@ namespace Game.Entities return; m_lootRecipient = player.GetGUID(); - Group group = player.GetGroup(); - if (group) - m_lootRecipientGroup = group.GetGUID(); + if (withGroup) + { + Group group = player.GetGroup(); + if (group) + m_lootRecipientGroup = group.GetGUID(); + } + else + m_lootRecipientGroup = ObjectGuid.Empty; AddDynamicFlag(UnitDynFlags.Tapped); } diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 6fbefbdad..6b7736677 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -1015,6 +1015,19 @@ namespace Game.Entities GetMap().GetZoneAndAreaId(GetPhaseShift(), out zoneid, out areaid, posX, posY, posZ); } + public bool IsInWorldPvpZone() + { + switch (GetZoneId()) + { + case 4197: // Wintergrasp + case 5095: // Tol Barad + case 6941: // Ashran + return true; + default: + return false; + } + } + public InstanceScript GetInstanceScript() { Map map = GetMap(); diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index 93ed35616..5e7002cb4 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -31,6 +31,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using Game.BattleFields; namespace Game.Entities { @@ -6164,7 +6165,8 @@ namespace Game.Entities /// public void RemovedInsignia(Player looterPlr) { - if (GetBattlegroundId() == 0) + // If player is not in battleground and not in worldpvpzone + if (GetBattlegroundId() == 0 && !IsInWorldPvpZone()) return; // If not released spirit, do it ! @@ -6262,9 +6264,12 @@ namespace Game.Entities group.UpdateLooterGuid(go); } - GameObjectTemplateAddon addon = go.GetTemplateAddon(); - if (addon != null) - loot.generateMoneyLoot(addon.mingold, addon.maxgold); + if (go.GetLootMode() > 0) + { + GameObjectTemplateAddon addon = go.GetTemplateAddon(); + if (addon != null) + loot.generateMoneyLoot(addon.mingold, addon.maxgold); + } if (loot_type == LootType.Fishing) go.getFishLoot(loot, this); @@ -6373,15 +6378,21 @@ namespace Game.Entities loot = bones.loot; - if (!bones.lootForBody) + if (loot.loot_type == LootType.None) { - bones.lootForBody = true; uint pLevel = bones.loot.gold; bones.loot.clear(); + + // For AV Achievement Battleground bg = GetBattleground(); if (bg) + { if (bg.GetTypeID(true) == BattlegroundTypeId.AV) - loot.FillLoot(1, LootStorage.Creature, this, true); + loot.FillLoot(SharedConst.PlayerCorpseLootEntry, LootStorage.Creature, this, true); + } + else if (GetZoneId() == WintergraspAreaIds.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)); @@ -6439,6 +6450,22 @@ namespace Game.Entities } else { + // exploit fix + if (!creature.HasDynamicFlag(UnitDynFlags.Lootable)) + { + SendLootError(loot.GetGUID(), guid, LootError.DidntKill); + return; + } + + // the player whose group may loot the corpse + Player recipient = creature.GetLootRecipient(); + Group recipientGroup = creature.GetLootRecipientGroup(); + if (!recipient && !recipientGroup) + { + SendLootError(loot.GetGUID(), guid, LootError.DidntKill); + return; + } + if (loot.loot_type == LootType.None) { // for creature, loot is filled when creature is killed. @@ -6463,14 +6490,16 @@ namespace Game.Entities if (loot.loot_type == LootType.Skinning) { loot_type = LootType.Skinning; - permission = creature.GetSkinner() == GetGUID() ? PermissionTypes.Owner : PermissionTypes.None; + permission = creature.GetLootRecipientGUID() == GetGUID() ? PermissionTypes.Owner : PermissionTypes.None; } else if (loot_type == LootType.Skinning) { loot.clear(); loot.FillLoot(creature.GetCreatureTemplate().SkinLootId, LootStorage.Skinning, this, true); - creature.SetSkinner(GetGUID()); permission = PermissionTypes.Owner; + + // Set new loot recipient + creature.SetLootRecipient(this, false); } // set group rights only for loot_type != LOOT_SKINNING else @@ -6554,12 +6583,12 @@ namespace Game.Entities // add 'this' player as one of the players that are looting 'loot' loot.AddLooter(GetGUID()); m_AELootView[loot.GetGUID()] = guid; + + if (loot_type == LootType.Corpse && !guid.IsItem()) + SetUnitFlags(UnitFlags.Looting); } else SendLootError(loot.GetGUID(), guid, LootError.DidntKill); - - if (loot_type == LootType.Corpse && !guid.IsItem()) - AddUnitFlag(UnitFlags.Looting); } public void SendLootError(ObjectGuid lootObj, ObjectGuid owner, LootError error) diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 9ae3f9287..1b99eaa5e 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -3664,7 +3664,7 @@ namespace Game.Entities return false; if (loot.loot_type == LootType.Skinning) - return creature.GetSkinner() == GetGUID(); + return creature.GetLootRecipientGUID() == GetGUID(); Group thisGroup = GetGroup(); if (!thisGroup) diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 6908d67b1..8fdf502e4 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -1422,7 +1422,8 @@ namespace Game.Entities if (lootid != 0) loot.FillLoot(lootid, LootStorage.Creature, looter, false, false, creature.GetLootMode()); - loot.generateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); + if (creature.GetLootMode() > 0) + loot.generateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); if (group) { diff --git a/Source/Game/Loot/LootManager.cs b/Source/Game/Loot/LootManager.cs index c21765525..5ddcb0eda 100644 --- a/Source/Game/Loot/LootManager.cs +++ b/Source/Game/Loot/LootManager.cs @@ -90,6 +90,9 @@ namespace Game.Loots foreach (var id in lootIdSetUsed) lootIdSet.Remove(id); + // 1 means loot for player corpse + lootIdSet.Remove(SharedConst.PlayerCorpseLootEntry); + // output error for any still listed (not referenced from appropriate table) ids Creature.ReportUnusedIds(lootIdSet); diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 91daa874f..7f670fe01 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -6411,18 +6411,51 @@ namespace Game.Spells // @todo shit below shouldn't be here, but it's temporary //Check targets for LOS visibility - if (losPosition != null) - return target.IsWithinLOS(losPosition.GetPositionX(), losPosition.GetPositionY(), losPosition.GetPositionZ(), ModelIgnoreFlags.M2); - else + switch (effect.Effect) { - // Get GO cast coordinates if original caster . GO - WorldObject caster = null; - if (m_originalCasterGUID.IsGameObject()) - caster = m_caster.GetMap().GetGameObject(m_originalCasterGUID); - if (!caster) - caster = m_caster; - if (target != m_caster && !target.IsWithinLOSInMap(caster, ModelIgnoreFlags.M2)) - return false; + case SpellEffectName.SkinPlayerCorpse: + { + if (m_targets.GetCorpseTargetGUID().IsEmpty()) + { + if (target.IsWithinLOSInMap(m_caster, ModelIgnoreFlags.M2) && target.HasUnitFlag(UnitFlags.Skinnable)) + return true; + + return false; + } + + Corpse corpse = ObjectAccessor.GetCorpse(m_caster, m_targets.GetCorpseTargetGUID()); + if (!corpse) + return false; + + if (target.GetGUID() != corpse.GetOwnerGUID()) + return false; + + if (!corpse.HasDynamicFlag(UnitDynFlags.Lootable)) + return false; + + if (!corpse.IsWithinLOSInMap(m_caster, ModelIgnoreFlags.M2)) + return false; + + break; + } + default: + { + if (losPosition != null) + return target.IsWithinLOS(losPosition.GetPositionX(), losPosition.GetPositionY(), losPosition.GetPositionZ(), ModelIgnoreFlags.M2); + else + { + // Get GO cast coordinates if original caster -> GO + WorldObject caster = null; + if (m_originalCasterGUID.IsGameObject()) + caster = m_caster.GetMap().GetGameObject(m_originalCasterGUID); + if (!caster) + caster = m_caster; + if (target != m_caster && !target.IsWithinLOSInMap(caster, ModelIgnoreFlags.M2)) + return false; + } + + break; + } } return true; diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 38e384d86..7191b5d2a 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -4046,9 +4046,9 @@ namespace Game.Spells SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill(); - m_caster.ToPlayer().SendLoot(creature.GetGUID(), LootType.Skinning); creature.RemoveUnitFlag(UnitFlags.Skinnable); creature.AddDynamicFlag(UnitDynFlags.Lootable); + m_caster.ToPlayer().SendLoot(creature.GetGUID(), LootType.Skinning); if (skill == SkillType.Skinning) {