Core/Loot: Implemented currency loot

Port From (https://github.com/TrinityCore/TrinityCore/commit/3e28ee080a1cf3c7cd332a8d1e0808505b4ea9d4)
This commit is contained in:
Hondacrx
2024-10-09 00:18:14 -04:00
parent 57858e1bed
commit 4c3074d462
11 changed files with 494 additions and 214 deletions
+1
View File
@@ -1454,5 +1454,6 @@ namespace Framework.Constants
{
Item = 0,
Reference = 1,
Currency = 2,
}
}
+1 -1
View File
@@ -370,7 +370,7 @@ namespace Framework.Constants
CommandNpcShowlootLabel2 = 293,
CommandNpcShowlootSublabel = 294,
CommandNpcShowlootEntry2 = 295,
// 296 Free
CommandNpcShowLootCurrency = 296,
// End
CommandWanderDistance = 297,
@@ -663,10 +663,10 @@ namespace Framework.Database
PrepareStatement(CharStatements.DEL_CHAR_CUF_PROFILES, "DELETE FROM character_cuf_profiles WHERE guid = ?");
// Items that hold loot or money
PrepareStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items");
PrepareStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items");
PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?");
PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ? AND item_count = ? AND item_index = ?");
PrepareStatement(CharStatements.INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_type = ? AND item_id = ? AND item_count = ? AND item_index = ?");
PrepareStatement(CharStatements.INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.SEL_ITEMCONTAINER_MONEY, "SELECT container_id, money FROM item_loot_money");
PrepareStatement(CharStatements.DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?");
PrepareStatement(CharStatements.INS_ITEMCONTAINER_MONEY, "INSERT INTO item_loot_money (container_id, money) VALUES (?, ?)");
+2 -13
View File
@@ -611,11 +611,8 @@ namespace Game.Chat
}
[Command("currency", RBACPermissions.CommandModifyCurrency)]
static bool HandleModifyCurrencyCommand(CommandHandler handler, StringArguments args)
static bool HandleModifyCurrencyCommand(CommandHandler handler, CurrencyTypesRecord currency, int amount)
{
if (args.Empty())
return false;
Player target = handler.GetSelectedPlayerOrSelf();
if (target == null)
{
@@ -624,15 +621,7 @@ namespace Game.Chat
return false;
}
uint currencyId = args.NextUInt32();
if (!CliDB.CurrencyTypesStorage.ContainsKey(currencyId))
return false;
uint amount = args.NextUInt32();
if (amount == 0)
return false;
target.ModifyCurrency(currencyId, (int)amount, CurrencyGainSource.Cheat, CurrencyDestroyReason.Cheat);
target.ModifyCurrency(currency.Id, amount, CurrencyGainSource.Cheat, CurrencyDestroyReason.Cheat);
return true;
}
+86 -13
View File
@@ -316,23 +316,23 @@ namespace Game.Chat
return false;
}
if (!creatureTarget.IsDead())
{
handler.SendSysMessage(CypherStrings.CommandNotDeadOrNoLoot, creatureTarget.GetName());
return false;
}
Loot loot = creatureTarget._loot;
if (creatureTarget.IsDead() || loot == null || loot.IsLooted())
if ((loot == null || loot.IsLooted()) && creatureTarget.m_personalLoot.Count(p => p.Value.IsLooted()) == 0)
{
handler.SendSysMessage(CypherStrings.CommandNotDeadOrNoLoot, creatureTarget.GetName());
return false;
}
handler.SendSysMessage(CypherStrings.CommandNpcShowlootHeader, creatureTarget.GetName(), creatureTarget.GetEntry());
handler.SendSysMessage(CypherStrings.CommandNpcShowlootMoney, loot.gold / MoneyConstants.Gold, (loot.gold % MoneyConstants.Gold) / MoneyConstants.Silver, loot.gold % MoneyConstants.Silver);
if (all.Equals("all", StringComparison.OrdinalIgnoreCase)) // nonzero from strcmp <. not equal
{
handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count);
foreach (LootItem item in loot.items)
if (!item.is_looted)
_ShowLootEntry(handler, item.itemid, item.count);
}
if (loot != null)
_ShowLootContents(handler, all == "all", loot);
else
{
handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count);
@@ -340,10 +340,11 @@ namespace Game.Chat
if (!item.is_looted && !item.freeforall && item.conditions.IsEmpty())
_ShowLootEntry(handler, item.itemid, item.count);
if (!loot.GetPlayerFFAItems().Empty())
foreach (var (lootOwner, personalLoot) in creatureTarget.m_personalLoot)
{
handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel2, "FFA items per allowed player");
_IterateNotNormalLootMap(handler, loot.GetPlayerFFAItems(), loot.items);
var character = Global.CharacterCacheStorage.GetCharacterCacheByGuid(lootOwner);
handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel2, $"Personal loot for {(character != null ? character.Name : "")}");
_ShowLootContents(handler, all == "all", personalLoot);
}
}
@@ -515,7 +516,7 @@ namespace Game.Chat
return true;
}
static void _ShowLootEntry(CommandHandler handler, uint itemId, byte itemCount, bool alternateString = false)
static void _ShowLootEntry(CommandHandler handler, uint itemId, uint itemCount, bool alternateString = false)
{
string name = "Unknown item";
@@ -526,6 +527,18 @@ namespace Game.Chat
handler.SendSysMessage(alternateString ? CypherStrings.CommandNpcShowlootEntry2 : CypherStrings.CommandNpcShowlootEntry,
itemCount, ItemConst.ItemQualityColors[(int)(itemTemplate != null ? itemTemplate.GetQuality() : ItemQuality.Poor)], itemId, name, itemId);
}
static void _ShowLootCurrencyEntry(CommandHandler handler, uint currencyId, uint count, bool alternateString = false)
{
CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(currencyId);
string name = "Unknown currency";
if (currency != null)
name = currency.Name;
handler.SendSysMessage(CypherStrings.CommandNpcShowLootCurrency, alternateString ? 6 : 3 /*number of bytes from following string*/, "\u2500\u2500",
count, ItemConst.ItemQualityColors[currency != null ? currency.Quality : (int)ItemQuality.Poor], currencyId, count, name, currencyId);
}
static void _IterateNotNormalLootMap(CommandHandler handler, MultiMap<ObjectGuid, NotNormalLootItem> map, List<LootItem> items)
{
foreach (var key in map.Keys)
@@ -542,7 +555,67 @@ namespace Game.Chat
{
LootItem item = items[it.LootListId];
if (!it.is_looted && !item.is_looted)
{
switch (item.type)
{
case LootItemType.Item:
_ShowLootEntry(handler, item.itemid, item.count, true);
break;
case LootItemType.Currency:
_ShowLootCurrencyEntry(handler, item.itemid, item.count, true);
break;
}
}
}
}
}
static void _ShowLootContents(CommandHandler handler, bool all, Loot loot)
{
handler.SendSysMessage(CypherStrings.CommandNpcShowlootMoney, loot.gold / MoneyConstants.Gold, (loot.gold % MoneyConstants.Gold) / MoneyConstants.Silver, loot.gold % MoneyConstants.Silver);
if (!all)
{
handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count);
foreach (LootItem item in loot.items)
{
if (!item.is_looted)
{
switch (item.type)
{
case LootItemType.Item:
_ShowLootEntry(handler, item.itemid, item.count);
break;
case LootItemType.Currency:
_ShowLootCurrencyEntry(handler, item.itemid, item.count);
break;
}
}
}
}
else
{
handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count);
foreach (LootItem item in loot.items)
{
if (!item.is_looted && !item.freeforall && item.conditions.IsEmpty())
{
switch (item.type)
{
case LootItemType.Item:
_ShowLootEntry(handler, item.itemid, item.count);
break;
case LootItemType.Currency:
_ShowLootCurrencyEntry(handler, item.itemid, item.count);
break;
}
}
}
if (!loot.GetPlayerFFAItems().Empty())
{
handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel2, "FFA items per allowed player");
_IterateNotNormalLootMap(handler, loot.GetPlayerFFAItems(), loot.items);
}
}
}
+31 -22
View File
@@ -6066,26 +6066,18 @@ namespace Game.Entities
return;
}
switch (item.type)
{
case LootItemType.Item:
List<ItemPosCount> dest = new();
InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count);
if (msg == InventoryResult.Ok)
if (msg != InventoryResult.Ok)
{
Item newitem = StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context);
if (ffaItem != null)
{
//freeforall case, notify only one player of the removal
ffaItem.is_looted = true;
SendNotifyLootItemRemoved(loot.GetGUID(), loot.GetOwnerGUID(), lootSlot);
SendEquipError(msg, null, null, item.itemid);
return;
}
else //not freeforall, notify everyone
loot.NotifyItemRemoved(lootSlot, GetMap());
//if only one person is supposed to loot the item, then set it to looted
if (!item.freeforall)
item.is_looted = true;
--loot.unlootedCount;
Item newitem = StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context);
if (newitem != null && (newitem.GetQuality() > ItemQuality.Epic || (newitem.GetQuality() == ItemQuality.Epic && newitem.GetItemLevel(this) >= GuildConst.MinNewsItemLevel)))
{
Guild guild = GetGuild();
@@ -6102,19 +6094,36 @@ namespace Game.Entities
UpdateCriteria(CriteriaType.LootAnyItem, item.itemid, item.count);
}
else
aeResult.Add(newitem, item.count, SharedConst.GetLootTypeForClient(loot.loot_type), loot.GetDungeonEncounterId());
// LootItem is being removed (looted) from the container, delete it from the DB.
if (loot.loot_type == LootType.Item)
Global.LootItemStorage.RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item.itemid, item.count, item.LootListId);
aeResult.Add(newitem, (byte)item.count, SharedConst.GetLootTypeForClient(loot.loot_type), loot.GetDungeonEncounterId());
if (newitem != null)
ApplyItemLootedSpell(newitem, true);
else
ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(item.itemid));
break;
case LootItemType.Currency:
ModifyCurrency(item.itemid, (int)item.count, CurrencyGainSource.Loot);
break;
}
else
SendEquipError(msg, null, null, item.itemid);
if (ffaItem != null)
{
//freeforall case, notify only one player of the removal
ffaItem.is_looted = true;
SendNotifyLootItemRemoved(loot.GetGUID(), loot.GetOwnerGUID(), lootSlot);
}
else //not freeforall, notify everyone
loot.NotifyItemRemoved(lootSlot, GetMap());
//if only one person is supposed to loot the item, then set it to looted
if (!item.freeforall)
item.is_looted = true;
--loot.unlootedCount;
// LootItem is being removed (looted) from the container, delete it from the DB.
if (loot.loot_type == LootType.Item)
Global.LootItemStorage.RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item.type, item.itemid, item.count, item.LootListId);
}
public Dictionary<ObjectGuid, Loot> GetAELootView() { return m_AELootView; }
@@ -2873,6 +2873,39 @@ namespace Game.Entities
return null;
}
public bool HasQuestForCurrency(uint currencyId)
{
bool isCompletableObjective(QuestObjectiveStatusData objectiveStatus)
{
Quest qInfo = Global.ObjectMgr.GetQuestTemplate(objectiveStatus.QuestStatusPair.QuestID);
QuestObjective objective = Global.ObjectMgr.GetQuestObjective(objectiveStatus.ObjectiveId);
if (qInfo == null || objective == null || !IsQuestObjectiveCompletable(objectiveStatus.QuestStatusPair.Status.Slot, qInfo, objective))
return false;
// hide quest if player is in raid-group and quest is no raid quest
if (GetGroup() != null && GetGroup().IsRaidGroup() && !qInfo.IsAllowedInRaid(GetMap().GetDifficultyID()))
if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later
return false;
if (!IsQuestObjectiveComplete(objectiveStatus.QuestStatusPair.Status.Slot, qInfo, objective))
return true;
return false;
};
bool hasObjectiveTypeForCurrency(QuestObjectiveType type) => m_questObjectiveStatus.LookupByKey((type, currencyId)).Any(isCompletableObjective);
if (hasObjectiveTypeForCurrency(QuestObjectiveType.Currency))
return true;
if (hasObjectiveTypeForCurrency(QuestObjectiveType.HaveCurrency))
return true;
if (hasObjectiveTypeForCurrency(QuestObjectiveType.ObtainCurrency))
return true;
return false;
}
public int GetQuestObjectiveData(QuestObjective objective)
{
ushort slot = FindQuestSlot(objective.QuestID);
+7 -1
View File
@@ -427,11 +427,17 @@ namespace Game
if (req.LootListID >= loot.items.Count)
{
_player.SendLootError(req.Object, loot.GetOwnerGUID(), LootError.MasterOther);
Log.outDebug(LogFilter.Loot, $"MasterLootItem: Player {GetPlayer().GetName()} might be using a hack! (slot {req.LootListID}, size {loot.items.Count})");
return;
}
LootItem item = loot.items[req.LootListID];
if (item.type != LootItemType.Item)
{
_player.SendLootError(req.Object, loot.GetOwnerGUID(), LootError.MasterOther);
return;
}
List<ItemPosCount> dest = new();
InventoryResult msg = target.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count);
@@ -452,7 +458,7 @@ namespace Game
// now move item from loot to target inventory
Item newitem = target.StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context, item.BonusListIDs);
if (newitem != null)
aeResult.Add(newitem, item.count, loot.loot_type, loot.GetDungeonEncounterId());
aeResult.Add(newitem, (byte)item.count, loot.loot_type, loot.GetDungeonEncounterId());
else
target.ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(item.itemid));
+142 -39
View File
@@ -16,19 +16,47 @@ namespace Game.Loots
{
public class LootItem
{
public uint itemid;
public uint LootListId;
public uint randomBonusListId;
public List<uint> BonusListIDs = new();
public ItemContext context;
public ConditionsReference conditions; // additional loot condition
public List<ObjectGuid> allowedGUIDs = new();
public ObjectGuid rollWinnerGUID; // Stores the guid of person who won loot, if his bags are full only he can see the item in loot list!
public uint count;
public LootItemType type;
public bool is_looted;
public bool is_blocked;
public bool freeforall; // free for all
public bool is_underthreshold;
public bool is_counted;
public bool needs_quest; // quest drop
public bool follow_loot_rules;
public LootItem() { }
public LootItem(LootStoreItem li)
{
itemid = li.itemid;
conditions = li.conditions;
ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid);
freeforall = proto != null && proto.HasFlag(ItemFlags.MultiDrop);
follow_loot_rules = !li.needs_quest || (proto != null && proto.FlagsCu.HasAnyFlag(ItemFlagsCustom.FollowLootRules));
needs_quest = li.needs_quest;
switch (li.type)
{
case LootStoreItemType.Item:
randomBonusListId = ItemEnchantmentManager.GenerateItemRandomBonusListId(itemid);
type = LootItemType.Item;
ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid);
freeforall = proto != null && proto.HasFlag(ItemFlags.MultiDrop);
follow_loot_rules = !li.needs_quest || (proto != null && proto.HasFlag(ItemFlagsCustom.FollowLootRules));
break;
case LootStoreItemType.Currency:
type = LootItemType.Currency;
freeforall = true;
break;
default:
break;
}
}
/// <summary>
@@ -39,10 +67,35 @@ namespace Game.Loots
/// <returns></returns>
public bool AllowedForPlayer(Player player, Loot loot)
{
return AllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
switch (type)
{
case LootItemType.Item:
return ItemAllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
case LootItemType.Currency:
return CurrencyAllowedForPlayer(player, itemid, needs_quest, conditions);
default:
break;
}
return false;
}
public static bool AllowedForPlayer(Player player, Loot loot, uint itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, ConditionsReference conditions)
public static bool AllowedForPlayer(Player player, LootStoreItem lootStoreItem, bool strictUsabilityCheck)
{
switch (lootStoreItem.type)
{
case LootStoreItemType.Item:
return ItemAllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest,
!lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
strictUsabilityCheck, lootStoreItem.conditions);
case LootStoreItemType.Currency:
return CurrencyAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.needs_quest, lootStoreItem.conditions);
default:
break;
}
return false;
}
public static bool ItemAllowedForPlayer(Player player, Loot loot, uint itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, ConditionsReference conditions)
{
// DB conditions check
if (!conditions.Meets(player))
@@ -96,6 +149,30 @@ namespace Game.Loots
return true;
}
public static bool CurrencyAllowedForPlayer(Player player, uint currencyId, bool needs_quest, ConditionsReference conditions)
{
// DB conditions check
if (!conditions.Meets(player))
return false;
CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(currencyId);
if (currency == null)
return false;
// not show loot for not own team
if (currency.HasFlag(CurrencyTypesFlags.IsHordeOnly) && player.GetTeam() != Team.Horde)
return false;
if (currency.HasFlag(CurrencyTypesFlags.IsAllianceOnly) && player.GetTeam() != Team.Alliance)
return false;
// check quest requirements
if (needs_quest && !player.HasQuestForCurrency(currencyId))
return false;
return true;
}
public void AddAllowedLooter(Player player)
{
allowedGUIDs.Add(player.GetGUID());
@@ -174,23 +251,6 @@ namespace Game.Loots
}
public List<ObjectGuid> GetAllowedLooters() { return allowedGUIDs; }
public uint itemid;
public uint LootListId;
public uint randomBonusListId;
public List<uint> BonusListIDs = new();
public ItemContext context;
public ConditionsReference conditions; // additional loot condition
public List<ObjectGuid> allowedGUIDs = new();
public ObjectGuid rollWinnerGUID; // Stores the guid of person who won loot, if his bags are full only he can see the item in loot list!
public byte count;
public bool is_looted;
public bool is_blocked;
public bool freeforall; // free for all
public bool is_underthreshold;
public bool is_counted;
public bool needs_quest; // quest drop
public bool follow_loot_rules;
}
public class NotNormalLootItem
@@ -630,7 +690,7 @@ namespace Game.Loots
for (uint i = 0; i < loot.items.Count; ++i)
{
LootItem disenchantLoot = loot.LootItemInSlot(i, player);
if (disenchantLoot != null)
if (disenchantLoot != null && disenchantLoot.type == LootItemType.Item)
player.SendItemRetrievalMail(disenchantLoot.itemid, disenchantLoot.count, disenchantLoot.context);
}
}
@@ -659,6 +719,10 @@ namespace Game.Loots
// Inserts the item into the loot (called by LootTemplate processors)
public void AddItem(LootStoreItem item)
{
switch (item.type)
{
case LootStoreItemType.Item:
{
ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid);
if (proto == null)
@@ -678,6 +742,19 @@ namespace Game.Loots
items.Add(generatedLoot);
count -= proto.GetMaxStackSize();
}
break;
}
case LootStoreItemType.Currency:
{
LootItem generatedLoot = new(item);
generatedLoot.count = RandomHelper.URand(item.mincount, item.maxcount);
generatedLoot.LootListId = (uint)items.Count;
items.Add(generatedLoot);
break;
}
default:
break;
}
}
public bool AutoStore(Player player, byte bag, byte slot, bool broadcast = false, bool createdByPlayer = false)
@@ -699,6 +776,9 @@ namespace Game.Loots
if (!lootItem.rollWinnerGUID.IsEmpty() && lootItem.rollWinnerGUID != GetGUID())
continue;
switch (lootItem.type)
{
case LootItemType.Item:
List<ItemPosCount> dest = new();
InventoryResult msg = player.CanStoreNewItem(bag, slot, dest, lootItem.itemid, lootItem.count);
if (msg != InventoryResult.Ok && slot != ItemConst.NullSlot)
@@ -712,14 +792,6 @@ namespace Game.Loots
continue;
}
if (ffaitem != null)
ffaitem.is_looted = true;
if (!lootItem.freeforall)
lootItem.is_looted = true;
--unlootedCount;
Item pItem = player.StoreNewItem(dest, lootItem.itemid, true, lootItem.randomBonusListId, null, lootItem.context, lootItem.BonusListIDs);
if (pItem != null)
{
@@ -728,6 +800,19 @@ namespace Game.Loots
}
else
player.ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(lootItem.itemid));
break;
case LootItemType.Currency:
player.ModifyCurrency(lootItem.itemid, (int)lootItem.count, CurrencyGainSource.Loot);
break;
}
if (ffaitem != null)
ffaitem.is_looted = true;
if (!lootItem.freeforall)
lootItem.is_looted = true;
--unlootedCount;
}
return allLooted;
@@ -783,7 +868,7 @@ namespace Game.Loots
foreach (LootItem item in items)
{
if (!item.follow_loot_rules || item.freeforall)
if (!item.follow_loot_rules || item.freeforall || item.type != LootItemType.Item)
continue;
ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid);
@@ -1014,12 +1099,8 @@ namespace Game.Loots
return true;
var ffaItems = GetPlayerFFAItems().LookupByKey(player.GetGUID());
if (ffaItems != null)
{
bool hasFfaItem = ffaItems.Any(ffaItem => !ffaItem.is_looted);
if (hasFfaItem)
if (ffaItems != null && ffaItems.Any(ffaItem => !ffaItem.is_looted))
return true;
}
return false;
}
@@ -1046,12 +1127,34 @@ namespace Game.Loots
if (!uiType.HasValue)
continue;
switch (item.type)
{
case LootItemType.Item:
{
LootItemData lootItem = new();
lootItem.LootListID = (byte)item.LootListId;
lootItem.UIType = uiType.Value;
lootItem.Quantity = item.count;
lootItem.Loot = new(item);
packet.Items.Add(lootItem);
break;
}
case LootItemType.Currency:
{
LootCurrency lootCurrency = new();
lootCurrency.CurrencyID = item.itemid;
lootCurrency.Quantity = item.count;
lootCurrency.LootListID = (byte)item.LootListId;
lootCurrency.UIType = (byte)uiType.Value;
// fake visible quantity for SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT - handled in Player::ModifyCurrency
lootCurrency.Quantity = (uint)((float)lootCurrency.Quantity * viewer.GetTotalAuraMultiplierByMiscValue(AuraType.ModCurrencyCategoryGainPct, CliDB.CurrencyTypesStorage.LookupByKey(item.itemid).CategoryID));
packet.Currencies.Add(lootCurrency);
break;
}
default:
break;
}
}
}
+34 -31
View File
@@ -35,18 +35,19 @@ namespace Game.Loots
StoredLootContainer storedContainer = _lootItemStorage[key];
LootItem lootItem = new();
lootItem.itemid = result.Read<uint>(1);
lootItem.count = result.Read<byte>(2);
lootItem.LootListId = result.Read<uint>(3);
lootItem.follow_loot_rules = result.Read<bool>(4);
lootItem.freeforall = result.Read<bool>(5);
lootItem.is_blocked = result.Read<bool>(6);
lootItem.is_counted = result.Read<bool>(7);
lootItem.is_underthreshold = result.Read<bool>(8);
lootItem.needs_quest = result.Read<bool>(9);
lootItem.randomBonusListId = result.Read<uint>(10);
lootItem.context = (ItemContext)result.Read<byte>(11);
StringArray bonusLists = new(result.Read<string>(12), ' ');
lootItem.type = (LootItemType)result.Read<sbyte>(1);
lootItem.itemid = result.Read<uint>(2);
lootItem.count = result.Read<byte>(3);
lootItem.LootListId = result.Read<uint>(4);
lootItem.follow_loot_rules = result.Read<bool>(5);
lootItem.freeforall = result.Read<bool>(6);
lootItem.is_blocked = result.Read<bool>(7);
lootItem.is_counted = result.Read<bool>(8);
lootItem.is_underthreshold = result.Read<bool>(9);
lootItem.needs_quest = result.Read<bool>(10);
lootItem.randomBonusListId = result.Read<uint>(11);
lootItem.context = (ItemContext)result.Read<byte>(12);
StringArray bonusLists = new(result.Read<string>(13), ' ');
foreach (string str in bonusLists)
lootItem.BonusListIDs.Add(uint.Parse(str));
@@ -177,12 +178,12 @@ namespace Game.Loots
DB.Characters.CommitTransaction(trans);
}
public void RemoveStoredLootItemForContainer(ulong containerId, uint itemId, uint count, uint itemIndex)
public void RemoveStoredLootItemForContainer(ulong containerId, LootItemType type, uint itemId, uint count, uint itemIndex)
{
if (!_lootItemStorage.ContainsKey(containerId))
return;
_lootItemStorage[containerId].RemoveItem(itemId, count, itemIndex);
_lootItemStorage[containerId].RemoveItem(type, itemId, count, itemIndex);
}
public void AddNewStoredLoot(ulong containerId, Loot loot, Player player)
@@ -248,25 +249,26 @@ namespace Game.Loots
PreparedStatement stmt = CharacterDatabase.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_ITEMS);
// container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
// container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
stmt.AddValue(0, _containerId);
stmt.AddValue(1, lootItem.itemid);
stmt.AddValue(2, lootItem.count);
stmt.AddValue(3, lootItem.LootListId);
stmt.AddValue(4, lootItem.follow_loot_rules);
stmt.AddValue(5, lootItem.freeforall);
stmt.AddValue(6, lootItem.is_blocked);
stmt.AddValue(7, lootItem.is_counted);
stmt.AddValue(8, lootItem.is_underthreshold);
stmt.AddValue(9, lootItem.needs_quest);
stmt.AddValue(10, lootItem.randomBonusListId);
stmt.AddValue(11, (uint)lootItem.context);
stmt.AddValue(1, (sbyte)lootItem.type);
stmt.AddValue(2, lootItem.itemid);
stmt.AddValue(3, lootItem.count);
stmt.AddValue(4, lootItem.LootListId);
stmt.AddValue(5, lootItem.follow_loot_rules);
stmt.AddValue(6, lootItem.freeforall);
stmt.AddValue(7, lootItem.is_blocked);
stmt.AddValue(8, lootItem.is_counted);
stmt.AddValue(9, lootItem.is_underthreshold);
stmt.AddValue(10, lootItem.needs_quest);
stmt.AddValue(11, lootItem.randomBonusListId);
stmt.AddValue(12, (uint)lootItem.context);
StringBuilder bonusListIDs = new();
foreach (int bonusListID in lootItem.BonusListIDs)
bonusListIDs.Append(bonusListID + ' ');
stmt.AddValue(12, bonusListIDs.ToString());
stmt.AddValue(13, bonusListIDs.ToString());
trans.Append(stmt);
}
@@ -295,7 +297,7 @@ namespace Game.Loots
DB.Characters.Execute(stmt);
}
public void RemoveItem(uint itemId, uint count, uint itemIndex)
public void RemoveItem(LootItemType type, uint itemId, uint count, uint itemIndex)
{
var bounds = _lootItems.LookupByKey(itemId);
foreach (var itr in bounds)
@@ -310,9 +312,10 @@ namespace Game.Loots
// Deletes a single item associated with an openable item from the DB
PreparedStatement stmt = CharacterDatabase.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM);
stmt.AddValue(0, _containerId);
stmt.AddValue(1, itemId);
stmt.AddValue(2, count);
stmt.AddValue(3, itemIndex);
stmt.AddValue(1, (sbyte)type);
stmt.AddValue(2, itemId);
stmt.AddValue(3, count);
stmt.AddValue(4, itemIndex);
DB.Characters.Execute(stmt);
}
+96 -33
View File
@@ -512,6 +512,14 @@ namespace Game.Loots
}
case LootStoreItemType.Reference:
return RandomHelper.randChance(chance * (rate ? WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferenced) : 1.0f));
case LootStoreItemType.Currency:
{
CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(itemid);
float qualityModifier = currency != null && rate && QualityToRate[currency.Quality] != WorldCfg.Max ? WorldConfig.GetFloatValue(QualityToRate[currency.Quality]) : 1.0f;
return RandomHelper.randChance(chance * qualityModifier);
}
default:
break;
}
@@ -523,7 +531,7 @@ namespace Game.Loots
{
if (mincount == 0)
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: wrong mincount ({3}) - skipped", store.GetName(), entry, itemid, mincount);
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: wrong mincount ({mincount}) - skipped");
return false;
}
@@ -533,38 +541,64 @@ namespace Game.Loots
ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid);
if (proto == null)
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: item does not exist - skipped", store.GetName(), entry, itemid);
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: item does not exist - skipped");
return false;
}
if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: equal-chanced grouped entry, but group not defined - skipped", store.GetName(), entry, itemid);
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: equal-chanced grouped entry, but group not defined - skipped");
return false;
}
if (chance != 0 && chance < 0.000001f) // loot with low chance
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: low chance ({3}) - skipped",
store.GetName(), entry, itemid, chance);
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: low chance ({chance}) - skipped");
return false;
}
if (maxcount < mincount) // wrong max count
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: max count ({3}) less that min count ({4}) - skipped", store.GetName(), entry, itemid, maxcount, mincount);
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: max count ({maxcount}) less that min count ({mincount}) - skipped");
return false;
}
break;
case LootStoreItemType.Reference:
if (needs_quest)
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: quest chance will be treated as non-quest chance", store.GetName(), entry, itemid);
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: quest chance will be treated as non-quest chance");
else if (chance == 0) // no chance for the reference
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: zero chance is specified for a reference, skipped", store.GetName(), entry, itemid);
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: zero chance is specified for a reference, skipped");
return false;
}
break;
case LootStoreItemType.Currency:
{
if (!CliDB.CurrencyTypesStorage.HasRecord(itemid))
{
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: currency does not exist - skipped");
return false;
}
if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only
{
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: equal-chanced grouped entry, but group not defined - skipped");
return false;
}
if (chance != 0 && chance < 0.0001f) // loot with low chance
{
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: low chance ({chance}) - skipped");
return false;
}
if (maxcount < mincount) // wrong max count
{
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: MaxCount ({maxcount}) less that MinCount ({mincount}) - skipped");
return false;
}
break;
}
default:
Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} Item {itemid}: invalid ItemType {type}, skipped");
return false;
@@ -757,12 +791,10 @@ namespace Game.Loots
switch (item.type)
{
case LootStoreItemType.Item:
case LootStoreItemType.Currency:
// Plain entries (not a reference, not grouped)
// Chance is already checked, just add
if (personalLooter == null
|| LootItem.AllowedForPlayer(personalLooter, null, item.itemid, item.needs_quest,
!item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
true, item.conditions))
if (personalLooter == null || LootItem.AllowedForPlayer(personalLooter, item, true))
loot.AddItem(item);
break;
case LootStoreItemType.Reference:
@@ -813,13 +845,7 @@ namespace Game.Loots
{
// Plain entries (not a reference, not grouped)
// Chance is already checked, just add
var lootersForItem = getLootersForItem(looter =>
{
return LootItem.AllowedForPlayer(looter, null, item.itemid, item.needs_quest,
!item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
true, item.conditions);
});
var lootersForItem = getLootersForItem(looter => LootItem.AllowedForPlayer(looter, item, true));
if (!lootersForItem.Empty())
{
Player chosenLooter = lootersForItem.SelectRandom();
@@ -860,6 +886,16 @@ namespace Game.Loots
}
break;
}
case LootStoreItemType.Currency:
{
// Plain entries (not a reference, not grouped)
// Chance is already checked, just add
var lootersForItem = getLootersForItem(looter => LootItem.AllowedForPlayer(looter, item, true));
foreach (Player looter in lootersForItem)
personalLoot[looter].AddItem(item);
break;
}
default:
break;
}
@@ -901,9 +937,7 @@ namespace Game.Loots
switch (lootStoreItem.type)
{
case LootStoreItemType.Item:
if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest,
!lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
strictUsabilityCheck, lootStoreItem.conditions))
if (LootItem.AllowedForPlayer(player, lootStoreItem, strictUsabilityCheck))
return true; // active quest drop found
break;
case LootStoreItemType.Reference:
@@ -931,6 +965,22 @@ namespace Game.Loots
// Copies the conditions list from a template item to a LootItem
foreach (var item in Entries)
{
switch (item.type)
{
case LootStoreItemType.Item:
if (li.type != LootItemType.Item)
continue;
break;
case LootStoreItemType.Reference:
continue;
case LootStoreItemType.Currency:
if (li.type != LootItemType.Currency)
continue;
break;
default:
break;
}
if (item.itemid != li.itemid)
continue;
@@ -957,6 +1007,7 @@ namespace Game.Loots
switch (item.type)
{
case LootStoreItemType.Item:
case LootStoreItemType.Currency:
if (item.needs_quest)
return true; // quest drop found
break;
@@ -1009,6 +1060,10 @@ namespace Game.Loots
if (Referenced.HasQuestDropForPlayer(store, player, item.groupid))
return true;
break;
case LootStoreItemType.Currency:
if (player.HasQuestForCurrency(item.itemid))
return true; // active quest drop found
break;
default:
break;
}
@@ -1124,10 +1179,24 @@ namespace Game.Loots
public bool HasQuestDropForPlayer(Player player)
{
if (ExplicitlyChanced.Any(item => player.HasQuestForItem(item.itemid)))
var hasQuestForLootItem = (LootStoreItem item) =>
{
switch (item.type)
{
case LootStoreItemType.Item:
return player.HasQuestForItem(item.itemid);
case LootStoreItemType.Currency:
return player.HasQuestForCurrency(item.itemid);
default:
break;
}
return false;
};
if (ExplicitlyChanced.Any(hasQuestForLootItem))
return true;
if (EqualChanced.Any(item => player.HasQuestForItem(item.itemid)))
if (EqualChanced.Any(hasQuestForLootItem))
return true;
return false;
@@ -1239,15 +1308,11 @@ namespace Game.Loots
public bool HasDropForPlayer(Player player, bool strictUsabilityCheck)
{
foreach (LootStoreItem lootStoreItem in ExplicitlyChanced)
if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest,
!lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
strictUsabilityCheck, lootStoreItem.conditions))
if (LootItem.AllowedForPlayer(player, lootStoreItem, strictUsabilityCheck))
return true;
foreach (LootStoreItem lootStoreItem in EqualChanced)
if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest,
!lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
strictUsabilityCheck, lootStoreItem.conditions))
if (LootItem.AllowedForPlayer(player, lootStoreItem, strictUsabilityCheck))
return true;
return false;
@@ -1268,9 +1333,7 @@ namespace Game.Loots
if ((item.lootmode & _lootMode) == 0)
return true;
if (_personalLooter != null && !LootItem.AllowedForPlayer(_personalLooter, null, item.itemid, item.needs_quest,
!item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
true, item.conditions))
if (_personalLooter != null && !LootItem.AllowedForPlayer(_personalLooter, item, true))
return true;
return false;