diff --git a/Source/Framework/Constants/QuestConst.cs b/Source/Framework/Constants/QuestConst.cs index 7bf76d4fc..1b0c6d632 100644 --- a/Source/Framework/Constants/QuestConst.cs +++ b/Source/Framework/Constants/QuestConst.cs @@ -498,6 +498,8 @@ namespace Framework.Constants // Room For More Custom Flags DbAllowed = Repeatable | ExplorationOrEvent | AutoAccept | DfQuest | Monthly, + + SequencedObjectives = 0x20 // Internal flag computed only } public enum QuestSaveType diff --git a/Source/Game/Achievements/CriteriaHandler.cs b/Source/Game/Achievements/CriteriaHandler.cs index 3b937dd3a..9c8e2709f 100644 --- a/Source/Game/Achievements/CriteriaHandler.cs +++ b/Source/Game/Achievements/CriteriaHandler.cs @@ -1689,7 +1689,12 @@ namespace Game.Achievements if (objective == null) return false; - if (referencePlayer.GetQuestRewardStatus(objective.QuestID) || !referencePlayer.IsQuestObjectiveComplete(objective)) + Quest quest = Global.ObjectMgr.GetQuestTemplate(objective.QuestID); + if (quest == null) + return false; + + ushort slot = referencePlayer.FindQuestSlot(objective.QuestID); + if (slot >= SharedConst.MaxQuestLogSize || referencePlayer.GetQuestRewardStatus(objective.QuestID) || !referencePlayer.IsQuestObjectiveComplete(slot, quest, objective)) return false; break; } diff --git a/Source/Game/Conditions/Condition.cs b/Source/Game/Conditions/Condition.cs index 17db7eb6e..ed23c7706 100644 --- a/Source/Game/Conditions/Condition.cs +++ b/Source/Game/Conditions/Condition.cs @@ -365,7 +365,15 @@ namespace Game.Conditions if (questObj == null) break; - condMeets = (!player.GetQuestRewardStatus(questObj.QuestID) && player.IsQuestObjectiveComplete(questObj)); + Quest quest = Global.ObjectMgr.GetQuestTemplate(questObj.QuestID); + if (quest == null) + break; + + ushort slot = player.FindQuestSlot(questObj.QuestID); + if (slot >= SharedConst.MaxQuestLogSize) + break; + + condMeets = (!player.GetQuestRewardStatus(questObj.QuestID) && player.IsQuestObjectiveComplete(slot, quest, questObj)); } break; } diff --git a/Source/Game/Conditions/ConditionManager.cs b/Source/Game/Conditions/ConditionManager.cs index d1d950013..28b2c573d 100644 --- a/Source/Game/Conditions/ConditionManager.cs +++ b/Source/Game/Conditions/ConditionManager.cs @@ -2123,7 +2123,9 @@ namespace Game if (condition.QuestKillID != 0) { Quest quest = Global.ObjectMgr.GetQuestTemplate(condition.QuestKillID); - if (quest != null && player.GetQuestStatus(condition.QuestKillID) != QuestStatus.Complete) + ushort questSlot = player.FindQuestSlot(condition.QuestKillID); + + if (quest != null && player.GetQuestStatus(condition.QuestKillID) != QuestStatus.Complete && questSlot < SharedConst.MaxQuestLogSize) { results = new bool[condition.QuestKillMonster.Length]; for (var i = 0; i < results.Length; ++i) @@ -2136,7 +2138,7 @@ namespace Game var questObjective = quest.Objectives.Find(objective => objective.Type == QuestObjectiveType.Monster && objective.ObjectID == condition.QuestKillMonster[i]); if (questObjective != null) - results[i] = player.GetQuestObjectiveData(quest, questObjective.StorageIndex) >= questObjective.Amount; + results[i] = player.GetQuestSlotObjectiveData(questSlot, questObjective) >= questObjective.Amount; } } diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index 8f8a4ccb2..6bd75061a 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -709,6 +709,7 @@ namespace Game.Entities // add to quest log if (slot < SharedConst.MaxQuestLogSize && questStatusData.Status != QuestStatus.None) { + questStatusData.Slot = slot; SetQuestSlot(slot, quest_id, (uint)quest_time); // cast can't be helped if (questStatusData.Status == QuestStatus.Complete) @@ -719,14 +720,6 @@ namespace Game.Entities ++slot; } - // Resize quest objective data to proper size - int maxStorageIndex = 0; - foreach (QuestObjective obj in quest.Objectives) - if (obj.StorageIndex > maxStorageIndex) - maxStorageIndex = obj.StorageIndex; - - questStatusData.ObjectiveData = new int[maxStorageIndex + 1]; - m_QuestStatus[quest_id] = questStatusData; Log.outDebug(LogFilter.ServerLoading, "Quest status is {0} for quest {1} for player (GUID: {2})", questStatusData.Status, quest_id, GetGUID().ToString()); } @@ -747,28 +740,26 @@ namespace Game.Entities uint questID = result.Read(0); Quest quest = Global.ObjectMgr.GetQuestTemplate(questID); - ushort slot = FindQuestSlot(questID); var questStatusData = m_QuestStatus.LookupByKey(questID); - if (questStatusData != null && slot < SharedConst.MaxQuestLogSize && quest != null) + if (questStatusData != null && questStatusData.Slot < SharedConst.MaxQuestLogSize && quest != null) { - byte objectiveIndex = result.Read(1); + byte storageIndex = result.Read(1); - var objectiveItr = quest.Objectives.FirstOrDefault(objective => objective.StorageIndex == objectiveIndex); - if (objectiveIndex < questStatusData.ObjectiveData.Length && objectiveItr != null) + var objective = quest.Objectives.FirstOrDefault(objective => objective.StorageIndex == storageIndex); + if (objective != null) { int data = result.Read(2); - questStatusData.ObjectiveData[objectiveIndex] = data; - if (!objectiveItr.IsStoringFlag()) - SetQuestSlotCounter(slot, objectiveIndex, (ushort)data); + if (!objective.IsStoringFlag()) + SetQuestSlotCounter(questStatusData.Slot, storageIndex, (ushort)data); else if (data != 0) - SetQuestSlotObjectiveFlag(slot, (sbyte)objectiveIndex); + SetQuestSlotObjectiveFlag(questStatusData.Slot, (sbyte)storageIndex); } else - Log.outError(LogFilter.Player, "Player {0} ({1}) has quest {2} out of range objective index {3}.", GetName(), GetGUID().ToString(), questID, objectiveIndex); + Log.outError(LogFilter.Player, $"Player {GetName()} ({GetGUID()}) has quest {questID} out of range objective index {storageIndex}."); } else - Log.outError(LogFilter.Player, "Player {0} ({1}) does not have quest {2} but has objective data for it.", GetName(), GetGUID().ToString(), questID); + Log.outError(LogFilter.Player, $"Player {GetName()} ({GetGUID()}) does not have quest {questID} but has objective data for it."); } while (result.NextRow()); } @@ -1943,7 +1934,7 @@ namespace Game.Entities if (!isTransaction) trans = new SQLTransaction(); - PreparedStatement stmt = null; + PreparedStatement stmt; bool keepAbandoned = !Global.WorldMgr.GetCleaningFlags().HasAnyFlag(CleaningFlags.Queststatus); foreach (var save in m_QuestStatusSave) @@ -1961,13 +1952,24 @@ namespace Game.Entities trans.Append(stmt); // Save objectives - for (int i = 0; i < data.ObjectiveData.Length; ++i) + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.Key); + trans.Append(stmt); + + Quest quest = Global.ObjectMgr.GetQuestTemplate(save.Key); + + foreach (QuestObjective obj in quest.Objectives) { + int count = GetQuestSlotObjectiveData(data.Slot, obj); + if (count == 0) + continue; + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CHAR_QUESTSTATUS_OBJECTIVES); stmt.AddValue(0, GetGUID().GetCounter()); stmt.AddValue(1, save.Key); - stmt.AddValue(2, i); - stmt.AddValue(3, data.ObjectiveData[i]); + stmt.AddValue(2, obj.StorageIndex); + stmt.AddValue(3, count); trans.Append(stmt); } } diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 93430601c..633fa2eb9 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -487,7 +487,7 @@ namespace Game.Entities { if (!obj.Flags.HasAnyFlag(QuestObjectiveFlags.Optional) && !obj.Flags.HasAnyFlag(QuestObjectiveFlags.PartOfProgressBar)) { - if (!IsQuestObjectiveComplete(obj)) + if (!IsQuestObjectiveComplete(q_status.Slot, qInfo, obj)) return false; } } @@ -740,14 +740,8 @@ namespace Game.Entities // check for repeatable quests status reset questStatusData.Status = QuestStatus.Incomplete; - int maxStorageIndex = 0; - foreach (QuestObjective obj in quest.Objectives) - if (obj.StorageIndex > maxStorageIndex) - maxStorageIndex = obj.StorageIndex; - - questStatusData.ObjectiveData = new int[maxStorageIndex + 1]; - GiveQuestSourceItem(quest); + AdjustQuestObjectiveProgress(quest); foreach (QuestObjective obj in quest.Objectives) { @@ -802,10 +796,9 @@ namespace Game.Entities caster.CastSpell(this, spellInfo.Id, new CastSpellExtraArgs(TriggerCastFlags.FullMask).SetCastDifficulty(spellInfo.Difficulty)); } + questStatusData.Slot = log_slot; SetQuestSlot(log_slot, quest_id, qtime); - AdjustQuestReqItemCount(quest); - m_QuestStatusSave[quest_id] = QuestSaveType.Default; StartCriteriaTimer(CriteriaTimedTypes.Quest, quest_id); @@ -1960,14 +1953,18 @@ namespace Game.Entities if (qInfo == null) return 0; + ushort slot = FindQuestSlot(quest_id); + if (slot >= SharedConst.MaxQuestLogSize) + return 0; + foreach (QuestObjective obj in qInfo.Objectives) if (obj.ObjectID == entry) - return (ushort)GetQuestObjectiveData(qInfo, obj.StorageIndex); + return (ushort)GetQuestSlotObjectiveData(slot, obj); return 0; } - public void AdjustQuestReqItemCount(Quest quest) + public void AdjustQuestObjectiveProgress(Quest quest) { // adjust progress of quest objectives that rely on external counters, like items if (quest.HasQuestObjectiveType(QuestObjectiveType.Item)) @@ -1980,6 +1977,12 @@ namespace Game.Entities uint curItemCount = GetItemCount((uint)obj.ObjectID, true); SetQuestObjectiveData(obj, (int)Math.Min(curItemCount, reqItemCount)); } + else if (obj.Type == QuestObjectiveType.HaveCurrency) + { + uint reqCurrencyCount = (uint)obj.Amount; + uint curCurrencyCount = GetCurrency((uint)obj.ObjectID); + SetQuestObjectiveData(obj, (int)Math.Min(reqCurrencyCount, curCurrencyCount)); + } } } } @@ -2016,6 +2019,33 @@ namespace Game.Entities return m_playerData.QuestLog[slot].EndTime; } + bool GetQuestSlotObjectiveFlag(ushort slot, sbyte objectiveIndex) + { + if (objectiveIndex < SharedConst.MaxQuestCounts) + return ((m_playerData.QuestLog[slot].ObjectiveFlags) & (1 << objectiveIndex)) != 0; + return false; + } + + public int GetQuestSlotObjectiveData(ushort slot, QuestObjective objective) + { + if (objective.StorageIndex < 0) + { + Log.outError(LogFilter.Player, $"Player.GetQuestObjectiveData: Called for quest {objective.QuestID} with invalid StorageIndex {objective.StorageIndex} (objective data is not tracked)"); + return 0; + } + + if (objective.StorageIndex >= SharedConst.MaxQuestCounts) + { + Log.outError(LogFilter.Player, $"Player.GetQuestObjectiveData: Player '{GetName()}' ({GetGUID()}) quest {objective.QuestID} out of range StorageIndex {objective.StorageIndex}"); + return 0; + } + + if (!objective.IsStoringFlag()) + return GetQuestSlotCounter(slot, (byte)objective.StorageIndex); + + return GetQuestSlotObjectiveFlag(slot, objective.StorageIndex) ? 1 : 0; + } + public void SetQuestSlot(ushort slot, uint quest_id, uint timer = 0) { var questLogField = m_values.ModifyValue(m_playerData).ModifyValue(m_playerData.QuestLog, slot); @@ -2149,30 +2179,27 @@ namespace Game.Entities foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.Type != QuestObjectiveType.Item) + if (obj.Type != QuestObjectiveType.Item || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - int reqItem = obj.ObjectID; - if (reqItem == entry) + if (obj.ObjectID != entry) + continue; + + int reqItemCount = obj.Amount; + int curItemCount = GetQuestSlotObjectiveData(i, obj); + if (curItemCount < reqItemCount) { - int reqItemCount = obj.Amount; - int curItemCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curItemCount < reqItemCount) - { - int newItemCount = (int)Math.Min(curItemCount + count, reqItemCount); - SetQuestObjectiveData(obj, newItemCount); - - //SendQuestUpdateAddItem(qInfo, j, additemcount); - // FIXME: verify if there's any packet sent updating item - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - return; + int newItemCount = (int)Math.Min(curItemCount + count, reqItemCount); + SetQuestObjectiveData(obj, newItemCount); } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + break; } } + UpdateForQuestWorldObjects(); } @@ -2190,29 +2217,29 @@ namespace Game.Entities foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.Type != QuestObjectiveType.Item) + if (obj.Type != QuestObjectiveType.Item || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - int reqItem = obj.ObjectID; - if (reqItem == entry) + if (obj.ObjectID != entry) + continue; + + uint reqItemCount = (uint)obj.Amount; + int curItemCount = GetQuestSlotObjectiveData(i, obj); + + if (curItemCount >= reqItemCount) // we may have more than what the status shows + curItemCount = (int)GetItemCount(entry, false); + + int newItemCount = (int)((count > curItemCount) ? 0 : curItemCount - count); + + if (newItemCount < reqItemCount) { - uint reqItemCount = (uint)obj.Amount; - int curItemCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - - if (curItemCount >= reqItemCount) // we may have more than what the status shows - curItemCount = (int)GetItemCount(entry, false); - - int newItemCount = (int)((count > curItemCount) ? 0 : curItemCount - count); - - if (newItemCount < reqItemCount) - { - SetQuestObjectiveData(obj, newItemCount); - IncompleteQuest(questid); - } - return; + SetQuestObjectiveData(obj, newItemCount); + IncompleteQuest(questid); } + break; } } + UpdateForQuestWorldObjects(); } @@ -2243,6 +2270,7 @@ namespace Game.Entities StartCriteriaTimer(CriteriaTimedTypes.Creature, real_entry); // MUST BE CALLED FIRST UpdateCriteria(CriteriaTypes.KillCreature, real_entry, addKillCount, 0, killed); + bool completedObjectiveInSequencedQuest = false; for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) { uint questid = GetQuestSlotQuestId(i); @@ -2253,40 +2281,47 @@ namespace Game.Entities if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.Monster)) continue; - // just if !ingroup || !noraidgroup || raidgroup - QuestStatusData q_status = m_QuestStatus[questid]; - if (q_status.Status == QuestStatus.Incomplete && (GetGroup() == null || !GetGroup().IsRaidGroup() || qInfo.IsAllowedInRaid(GetMap().GetDifficultyID()))) + if (m_QuestStatus[questid].Status != QuestStatus.Incomplete) + continue; + + if (GetGroup() && GetGroup().IsRaidGroup() && qInfo.IsAllowedInRaid(GetMap().GetDifficultyID())) + continue; + + foreach (QuestObjective obj in qInfo.Objectives) { - foreach (QuestObjective obj in qInfo.Objectives) + if (obj.Type != QuestObjectiveType.Monster || !IsQuestObjectiveCompletable(i, qInfo, obj)) + continue; + + if (obj.ObjectID != entry) + continue; + + uint reqKillCount = (uint)obj.Amount; + int curKillCount = GetQuestSlotObjectiveData(i, obj); + if (curKillCount < reqKillCount) { - if (obj.Type != QuestObjectiveType.Monster) - continue; - - int reqkill = obj.ObjectID; - if (reqkill == real_entry) - { - int curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curKillCount < obj.Amount) - { - SetQuestObjectiveData(obj, curKillCount + addKillCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curKillCount + addKillCount)); - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). - break; - } + SetQuestObjectiveData(obj, curKillCount + addKillCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curKillCount + addKillCount)); + if (!completedObjectiveInSequencedQuest && qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives) && IsQuestObjectiveComplete(i, qInfo, obj)) + completedObjectiveInSequencedQuest = true; } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } public void KilledPlayerCredit() { ushort addKillCount = 1; + bool completedObjectiveInSequencedQuest = false; for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) { uint questid = GetQuestSlotQuestId(i); @@ -2303,14 +2338,16 @@ namespace Game.Entities { foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.Type != QuestObjectiveType.PlayerKills) + if (obj.Type != QuestObjectiveType.PlayerKills || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - int curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + int curKillCount = GetQuestSlotObjectiveData(i, obj); if (curKillCount < obj.Amount) { SetQuestObjectiveData(obj, curKillCount + addKillCount); SendQuestUpdateAddPlayer(qInfo, (uint)(curKillCount + addKillCount)); + if (!completedObjectiveInSequencedQuest && qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives) && IsQuestObjectiveComplete(i, qInfo, obj)) + completedObjectiveInSequencedQuest = true; } if (CanCompleteQuest(questid)) @@ -2321,11 +2358,15 @@ namespace Game.Entities } } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } public void KillCreditGO(uint entry, ObjectGuid guid = default) { ushort addCastCount = 1; + bool completedObjectiveInSequencedQuest = false; for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) { uint questid = GetQuestSlotQuestId(i); @@ -2333,39 +2374,46 @@ namespace Game.Entities continue; Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); - if (qInfo == null) + if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.GameObject)) continue; - QuestStatusData q_status = m_QuestStatus[questid]; + if (m_QuestStatus[questid].Status != QuestStatus.Incomplete) + continue; - if (q_status.Status == QuestStatus.Incomplete) + if (GetGroup() && GetGroup().IsRaidGroup() && qInfo.IsAllowedInRaid(GetMap().GetDifficultyID())) + continue; + + foreach (QuestObjective obj in qInfo.Objectives) { - foreach (QuestObjective obj in qInfo.Objectives) + if (obj.Type != QuestObjectiveType.GameObject || !IsQuestObjectiveCompletable(i, qInfo, obj)) + continue; + + int reqTarget = obj.ObjectID; + + // other not this creature/GO related objectives + if (obj.ObjectID != entry) + continue; + + uint reqCastCount = (uint)obj.Amount; + int curCastCount = GetQuestSlotObjectiveData(i, obj); + if (curCastCount < reqCastCount) { - if (obj.Type != QuestObjectiveType.GameObject) - continue; - - int reqTarget = obj.ObjectID; - - // other not this creature/GO related objectives - if (reqTarget != entry) - continue; - - int curCastCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curCastCount < obj.Amount) - { - SetQuestObjectiveData(obj, curCastCount + addCastCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curCastCount + addCastCount)); - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). - break; + SetQuestObjectiveData(obj, curCastCount + addCastCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curCastCount + addCastCount)); + if (!completedObjectiveInSequencedQuest && qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives) && IsQuestObjectiveComplete(i, qInfo, obj)) + completedObjectiveInSequencedQuest = true; } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } public void KillCreditCriteriaTreeObjective(QuestObjective questObjective) @@ -2373,19 +2421,24 @@ namespace Game.Entities if (questObjective.Type != QuestObjectiveType.CriteriaTree) return; - if (GetQuestStatus(questObjective.QuestID) == QuestStatus.Incomplete) + Quest quest = Global.ObjectMgr.GetQuestTemplate(questObjective.QuestID); + if (!IsQuestObjectiveComplete(FindQuestSlot(questObjective.QuestID), quest, questObjective)) { SetQuestObjectiveData(questObjective, 1); SendQuestUpdateAddCreditSimple(questObjective); if (CanCompleteQuest(questObjective.QuestID)) CompleteQuest(questObjective.QuestID); + + if (quest.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives)) + UpdateForQuestWorldObjects(); } } public void TalkedToCreature(uint entry, ObjectGuid guid) { ushort addTalkCount = 1; + bool completedObjectiveInSequencedQuest = false; for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) { uint questid = GetQuestSlotQuestId(i); @@ -2393,63 +2446,74 @@ namespace Game.Entities continue; Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); - if (qInfo == null) + if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.TalkTo)) continue; - QuestStatusData q_status = m_QuestStatus[questid]; - - if (q_status.Status == QuestStatus.Incomplete) - { - foreach (QuestObjective obj in qInfo.Objectives) - { - if (obj.Type != QuestObjectiveType.TalkTo) - continue; - - int reqTarget = obj.ObjectID; - if (reqTarget == entry) - { - int curTalkCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curTalkCount < obj.Amount) - { - SetQuestObjectiveData(obj, curTalkCount + addTalkCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curTalkCount + addTalkCount)); - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // Quest can't have more than one objective for the same creature (code optimisation) - break; - } - } - } - } - } - - public void MoneyChanged(ulong value) - { - for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) - { - uint questid = GetQuestSlotQuestId(i); - if (questid == 0) + if (m_QuestStatus[questid].Status != QuestStatus.Incomplete) continue; - Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); - if (qInfo == null) + if (GetGroup() && GetGroup().IsRaidGroup() && qInfo.IsAllowedInRaid(GetMap().GetDifficultyID())) continue; foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.Type != QuestObjectiveType.Money) + if (obj.Type != QuestObjectiveType.TalkTo || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - QuestStatusData q_status = m_QuestStatus[questid]; - if (q_status.Status == QuestStatus.Incomplete) + if (obj.ObjectID != entry) + continue; + + uint reqTalkCount = (uint)obj.Amount; + int curTalkCount = GetQuestSlotObjectiveData(i, obj); + if (curTalkCount < reqTalkCount) + { + SetQuestObjectiveData(obj, curTalkCount + addTalkCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curTalkCount + addTalkCount)); + if (!completedObjectiveInSequencedQuest && qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives) && IsQuestObjectiveComplete(i, qInfo, obj)) + completedObjectiveInSequencedQuest = true; + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // Quest can't have more than one objective for the same creature (code optimisation) + break; + } + } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); + } + + public void MoneyChanged(ulong value) + { + bool completedObjectiveInSequencedQuest = false; + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.Money)) + continue; + + QuestStatusData q_status = m_QuestStatus[questid]; + + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.Money || !IsQuestObjectiveCompletable(i, qInfo, obj)) + continue; + + if (!IsQuestObjectiveComplete(i, qInfo, obj)) { if ((long)value >= obj.Amount) { if (CanCompleteQuest(questid)) CompleteQuest(questid); + + if (!completedObjectiveInSequencedQuest && qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives)) + completedObjectiveInSequencedQuest = true; } } else if (q_status.Status == QuestStatus.Complete) @@ -2459,10 +2523,14 @@ namespace Game.Entities } } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } - public void ReputationChanged(FactionRecord FactionRecord) + public void ReputationChanged(FactionRecord FactionRecord, int change) { + bool completedObjectiveInSequencedQuest = false; for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) { uint questid = GetQuestSlotQuestId(i); @@ -2473,47 +2541,80 @@ namespace Game.Entities { QuestStatusData q_status = m_QuestStatus[questid]; + if (!qInfo.HasQuestObjectiveType(QuestObjectiveType.MinReputation) && !qInfo.HasQuestObjectiveType(QuestObjectiveType.MaxReputation) + && (!qInfo.HasQuestObjectiveType(QuestObjectiveType.IncreaseReputation) || q_status.Status != QuestStatus.Incomplete)) + continue; + foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.ObjectID != FactionRecord.Id) + if (obj.ObjectID != FactionRecord.Id || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - if (obj.Type == QuestObjectiveType.MinReputation) + switch (obj.Type) { - if (q_status.Status == QuestStatus.Incomplete) - { - if (GetReputationMgr().GetReputation(FactionRecord) >= obj.Amount) + case QuestObjectiveType.MinReputation: + if (!IsQuestObjectiveComplete(i, qInfo, obj)) + { + if (GetReputationMgr().GetReputation(FactionRecord) + change >= obj.Amount) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + if (qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives)) + completedObjectiveInSequencedQuest = true; + } + } + else if (q_status.Status == QuestStatus.Complete) + { + if (GetReputationMgr().GetReputation(FactionRecord) + change < obj.Amount) + IncompleteQuest(questid); + } + break; + case QuestObjectiveType.MaxReputation: + if (!IsQuestObjectiveComplete(i, qInfo, obj)) + { + if (GetReputationMgr().GetReputation(FactionRecord) + change <= obj.Amount) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + if (qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives)) + completedObjectiveInSequencedQuest = true; + } + } + else if (q_status.Status == QuestStatus.Complete) + { + if (GetReputationMgr().GetReputation(FactionRecord) + change > obj.Amount) + IncompleteQuest(questid); + } + break; + case QuestObjectiveType.IncreaseReputation: + int currentProgress = GetQuestSlotObjectiveData(i, obj); + if (change > 0 && currentProgress < obj.Amount) + { + SetQuestObjectiveData(obj, currentProgress + change); + SendQuestUpdateAddCredit(qInfo, ObjectGuid.Empty, obj, (uint)(currentProgress + change)); + if (CanCompleteQuest(questid)) CompleteQuest(questid); - } - else if (q_status.Status == QuestStatus.Complete) - { - if (GetReputationMgr().GetReputation(FactionRecord) < obj.Amount) - IncompleteQuest(questid); - } - } - else if (obj.Type == QuestObjectiveType.MaxReputation) - { - if (q_status.Status == QuestStatus.Incomplete) - { - if (GetReputationMgr().GetReputation(FactionRecord) <= obj.Amount) - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - } - else if (q_status.Status == QuestStatus.Complete) - { - if (GetReputationMgr().GetReputation(FactionRecord) > obj.Amount) - IncompleteQuest(questid); - } + + if (!completedObjectiveInSequencedQuest && qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives) && IsQuestObjectiveComplete(i, qInfo, obj)) + completedObjectiveInSequencedQuest = true; + } + break; } } } } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } void CurrencyChanged(uint currencyId, int change) { + bool completedObjectiveInSequencedQuest = false; for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) { uint questid = GetQuestSlotQuestId(i); @@ -2524,41 +2625,78 @@ namespace Game.Entities if (qInfo == null) continue; + QuestStatusData q_status = m_QuestStatus[questid]; + + if (!qInfo.HasQuestObjectiveType(QuestObjectiveType.Currency) && !qInfo.HasQuestObjectiveType(QuestObjectiveType.HaveCurrency) + && (!qInfo.HasQuestObjectiveType(QuestObjectiveType.ObtainCurrency) || q_status.Status != QuestStatus.Incomplete)) + continue; + foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.ObjectID != currencyId) + if (obj.ObjectID != currencyId || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - QuestStatusData q_status = m_QuestStatus[questid]; - if (obj.Type == QuestObjectiveType.Currency || obj.Type == QuestObjectiveType.HaveCurrency) + switch (obj.Type) { - long value = GetCurrency(currencyId); - if (obj.Type == QuestObjectiveType.HaveCurrency) - SetQuestObjectiveData(obj, (int)Math.Min(value, obj.Amount)); - - if (q_status.Status == QuestStatus.Incomplete) - { - if (value >= obj.Amount) + case QuestObjectiveType.Currency: + if (!IsQuestObjectiveComplete(i, qInfo, obj)) { + if (GetCurrency(currencyId) + change >= obj.Amount) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + if (qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives)) + completedObjectiveInSequencedQuest = true; + } + } + else if (q_status.Status == QuestStatus.Complete) + { + if (GetCurrency(currencyId) + change < obj.Amount) + IncompleteQuest(questid); + } + break; + + case QuestObjectiveType.HaveCurrency: + int newProgress = (int)Math.Min(GetCurrency(currencyId) + change, obj.Amount); + SetQuestObjectiveData(obj, newProgress); + if (!IsQuestObjectiveComplete(i, qInfo, obj)) + { + if (newProgress >= obj.Amount) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + if (qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives)) + completedObjectiveInSequencedQuest = true; + } + } + else if (q_status.Status == QuestStatus.Complete) + { + if (newProgress < obj.Amount) + IncompleteQuest(questid); + } + break; + case QuestObjectiveType.ObtainCurrency: + int currentProgress = GetQuestSlotObjectiveData(i, obj); + if (change > 0 && currentProgress < obj.Amount) + { + SetQuestObjectiveData(obj, currentProgress + change); + SendQuestUpdateAddCredit(qInfo, ObjectGuid.Empty, obj, (uint)(currentProgress + change)); + if (CanCompleteQuest(questid)) CompleteQuest(questid); + + if (!completedObjectiveInSequencedQuest && qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives) && IsQuestObjectiveComplete(i, qInfo, obj)) + completedObjectiveInSequencedQuest = true; } - } - else if (q_status.Status == QuestStatus.Complete) - { - if (value < obj.Amount) - IncompleteQuest(questid); - } - } - else if (obj.Type == QuestObjectiveType.ObtainCurrency && change > 0) // currency losses are not accounted for in this objective type - { - long currentProgress = GetQuestObjectiveData(qInfo, obj.StorageIndex); - SetQuestObjectiveData(obj, (int)Math.Max(Math.Min(currentProgress + change, obj.Amount), 0)); - if (CanCompleteQuest(questid)) - CompleteQuest(questid); + break; } } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } public bool HasQuestForItem(uint itemid) @@ -2588,7 +2726,7 @@ namespace Game.Entities // This part for ReqItem drop foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.Type == QuestObjectiveType.Item && itemid == obj.ObjectID && GetQuestObjectiveData(qInfo, obj.StorageIndex) < obj.Amount) + if (obj.Type == QuestObjectiveType.Item && IsQuestObjectiveCompletable(i, qInfo, obj) && itemid == obj.ObjectID && GetQuestSlotObjectiveData(i, obj) < obj.Amount) return true; } // This part - for ReqSource @@ -2618,55 +2756,128 @@ namespace Game.Entities return false; } - public int GetQuestObjectiveData(Quest quest, sbyte storageIndex) + public int GetQuestObjectiveData(QuestObjective objective) { - if (storageIndex < 0) - Log.outError(LogFilter.Player, "GetQuestObjectiveData: called for quest {0} with invalid StorageIndex {1} (objective data is not tracked)", quest.Id, storageIndex); + ushort slot = FindQuestSlot(objective.QuestID); + if (slot >= SharedConst.MaxQuestLogSize) + return 0; - var status = m_QuestStatus.LookupByKey(quest.Id); + return GetQuestSlotObjectiveData(slot, objective); + } + + public void SetQuestObjectiveData(QuestObjective objective, int data) + { + if (objective.StorageIndex < 0) + { + Log.outError(LogFilter.Player, $"Player.SetQuestObjectiveData: called for quest {objective.QuestID} with invalid StorageIndex {objective.StorageIndex} (objective data is not tracked)"); + return; + } + + var status = m_QuestStatus.LookupByKey(objective.QuestID); if (status == null) - return 0; - - if (storageIndex >= status.ObjectiveData.Length) { - Log.outError(LogFilter.Player, "GetQuestObjectiveData: player {0} ({1}) quest {2} out of range StorageIndex {3}", GetName(), GetGUID().ToString(), quest.Id, storageIndex); - return 0; + Log.outError(LogFilter.Player, $"Player.SetQuestObjectiveData: player '{GetName()}' ({GetGUID()}) doesn't have quest status data (QuestID: {objective.QuestID})"); + return; + } + if (objective.StorageIndex >= SharedConst.MaxQuestCounts) + { + Log.outError(LogFilter.Player, $"Player.SetQuestObjectiveData: player '{GetName()}' ({GetGUID()}) quest {objective.QuestID} out of range StorageIndex {objective.StorageIndex}"); + return; } - return status.ObjectiveData[storageIndex]; - } + if (status.Slot >= SharedConst.MaxQuestLogSize) + return; - bool IsQuestObjectiveProgressComplete(Quest quest) - { - float progress = 0; - foreach (QuestObjective obj in quest.Objectives) - { - if (obj.Flags.HasAnyFlag(QuestObjectiveFlags.PartOfProgressBar)) - { - progress += GetQuestObjectiveData(quest, obj.StorageIndex) * obj.ProgressBarWeight; - if (progress >= 100) - return true; - } - } - return false; - } + // No change + int oldData = GetQuestSlotObjectiveData(status.Slot, objective); + if (oldData == data) + return; - public bool IsQuestObjectiveComplete(QuestObjective objective) - { Quest quest = Global.ObjectMgr.GetQuestTemplate(objective.QuestID); - //ASSERT(quest); + if (quest != null) + Global.ScriptMgr.OnQuestObjectiveChange(this, quest, objective, oldData, data); + // Add to save + m_QuestStatusSave[objective.QuestID] = QuestSaveType.Default; + + // Update quest fields + if (!objective.IsStoringFlag()) + SetQuestSlotCounter(status.Slot, (byte)objective.StorageIndex, (ushort)data); + else if (data != 0) + SetQuestSlotObjectiveFlag(status.Slot, objective.StorageIndex); + else + RemoveQuestSlotObjectiveFlag(status.Slot, objective.StorageIndex); + } + + public bool IsQuestObjectiveCompletable(ushort slot, Quest quest, QuestObjective objective) + { + Cypher.Assert(objective.QuestID == quest.Id); + + if (objective.Flags.HasAnyFlag(QuestObjectiveFlags.PartOfProgressBar)) + { + // delegate check to actual progress bar objective + var progressBarObjective = quest.Objectives.Find(otherObjective => otherObjective.Type == QuestObjectiveType.ProgressBar && !otherObjective.Flags.HasAnyFlag(QuestObjectiveFlags.PartOfProgressBar)); + if (progressBarObjective == null) + return false; + + return IsQuestObjectiveCompletable(slot, quest, progressBarObjective) && !IsQuestObjectiveComplete(slot, quest, progressBarObjective); + } + + int objectiveIndex = quest.Objectives.IndexOf(objective); + if (objectiveIndex == -1) + return true; + + // check sequenced objectives + int previousIndex = objectiveIndex - 1; + bool objectiveSequenceSatisfied = true; + bool previousSequencedObjectiveComplete = false; + int previousSequencedObjectiveIndex = -1; + do + { + QuestObjective previousObjective = quest.Objectives[previousIndex]; + if (previousObjective.Flags.HasAnyFlag(QuestObjectiveFlags.Sequenced)) + { + previousSequencedObjectiveIndex = previousIndex; + previousSequencedObjectiveComplete = IsQuestObjectiveComplete(slot, quest, previousObjective); + break; + } + + if (objectiveSequenceSatisfied) + objectiveSequenceSatisfied = IsQuestObjectiveComplete(slot, quest, previousObjective) || previousObjective.Flags.HasAnyFlag(QuestObjectiveFlags.Optional | QuestObjectiveFlags.PartOfProgressBar); + + --previousIndex; + } while (previousIndex >= 0); + + if (objective.Flags.HasAnyFlag(QuestObjectiveFlags.Sequenced)) + { + if (previousSequencedObjectiveIndex == -1) + return objectiveSequenceSatisfied; + if (!previousSequencedObjectiveComplete || !objectiveSequenceSatisfied) + return false; + } + else if (!previousSequencedObjectiveComplete && previousSequencedObjectiveIndex != -1) + { + if (!IsQuestObjectiveCompletable(slot, quest, quest.Objectives[previousSequencedObjectiveIndex])) + return false; + } + + return true; + } + + public bool IsQuestObjectiveComplete(ushort slot, Quest quest, QuestObjective objective) + { switch (objective.Type) { case QuestObjectiveType.Monster: case QuestObjectiveType.Item: case QuestObjectiveType.GameObject: - case QuestObjectiveType.PlayerKills: case QuestObjectiveType.TalkTo: + case QuestObjectiveType.PlayerKills: case QuestObjectiveType.WinPvpPetBattles: case QuestObjectiveType.HaveCurrency: case QuestObjectiveType.ObtainCurrency: - if (GetQuestObjectiveData(quest, objective.StorageIndex) < objective.Amount) + case QuestObjectiveType.IncreaseReputation: + if (GetQuestSlotObjectiveData(slot, objective) < objective.Amount) return false; break; case QuestObjectiveType.MinReputation: @@ -2682,8 +2893,12 @@ namespace Game.Entities return false; break; case QuestObjectiveType.AreaTrigger: + case QuestObjectiveType.WinPetBattleAgainstNpc: + case QuestObjectiveType.DefeatBattlePet: case QuestObjectiveType.CriteriaTree: - if (GetQuestObjectiveData(quest, objective.StorageIndex) == 0) + case QuestObjectiveType.AreaTriggerEnter: + case QuestObjectiveType.AreaTriggerExit: + if (GetQuestSlotObjectiveData(slot, objective) == 0) return false; break; case QuestObjectiveType.LearnSpell: @@ -2695,7 +2910,7 @@ namespace Game.Entities return false; break; case QuestObjectiveType.ProgressBar: - if (!IsQuestObjectiveProgressComplete(quest)) + if (!IsQuestObjectiveProgressBarComplete(slot, quest)) return false; break; default: @@ -2707,53 +2922,20 @@ namespace Game.Entities return true; } - public void SetQuestObjectiveData(QuestObjective objective, int data) + public bool IsQuestObjectiveProgressBarComplete(ushort slot, Quest quest) { - if (objective.StorageIndex < 0) + float progress = 0.0f; + foreach (QuestObjective obj in quest.Objectives) { - Log.outError(LogFilter.Player, "SetQuestObjectiveData: called for quest {0} with invalid StorageIndex {1} (objective data is not tracked)", objective.QuestID, objective.StorageIndex); - return; + if (obj.Flags.HasAnyFlag(QuestObjectiveFlags.PartOfProgressBar)) + { + progress += GetQuestSlotObjectiveData(slot, obj) * obj.ProgressBarWeight; + if (progress >= 100.0f) + return true; + } } - var status = m_QuestStatus.LookupByKey(objective.QuestID); - if (status == null) - { - Log.outError(LogFilter.Player, "SetQuestObjectiveData: player {0} ({1}) doesn't have quest status data for quest {2}", GetName(), GetGUID().ToString(), objective.QuestID); - return; - } - - if (objective.StorageIndex >= status.ObjectiveData.Length) - { - Log.outError(LogFilter.Player, "SetQuestObjectiveData: player {0} ({1}) quest {2} out of range StorageIndex {3}", GetName(), GetGUID().ToString(), objective.QuestID, objective.StorageIndex); - return; - } - - // No change - int oldData = status.ObjectiveData[objective.StorageIndex]; - if (oldData == data) - return; - - Quest quest = Global.ObjectMgr.GetQuestTemplate(objective.QuestID); - if (quest != null) - Global.ScriptMgr.OnQuestObjectiveChange(this, quest, objective, oldData, data); - - // Set data - status.ObjectiveData[objective.StorageIndex] = data; - - // Add to save - m_QuestStatusSave[objective.QuestID] = QuestSaveType.Default; - - // Update quest fields - ushort log_slot = FindQuestSlot(objective.QuestID); - if (log_slot < SharedConst.MaxQuestLogSize) - { - if (!objective.IsStoringFlag()) - SetQuestSlotCounter(log_slot, (byte)objective.StorageIndex, (ushort)status.ObjectiveData[objective.StorageIndex]); - else if (data != 0) - SetQuestSlotObjectiveFlag(log_slot, objective.StorageIndex); - else - RemoveQuestSlotObjectiveFlag(log_slot, objective.StorageIndex); - } + return false; } public void SendQuestComplete(Quest quest) @@ -2977,7 +3159,10 @@ namespace Game.Entities if (obj.Type != QuestObjectiveType.GameObject) //skip non GO case continue; - if (GOId == obj.ObjectID && GetQuestObjectiveData(qInfo, obj.StorageIndex) < obj.Amount) + if (!IsQuestObjectiveCompletable(i, qInfo, obj)) + continue; + + if (GOId == obj.ObjectID && GetQuestSlotObjectiveData(i, obj) < obj.Amount) return true; } } diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 8f22b3eba..31aa38a55 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -1204,6 +1204,8 @@ namespace Game.Entities if (playerCurrency.state != PlayerCurrencyState.New) playerCurrency.state = PlayerCurrencyState.Changed; + CurrencyChanged((uint)id, count); + playerCurrency.Quantity = (uint)newTotalCount; playerCurrency.WeeklyQuantity = (uint)newWeekCount; playerCurrency.TrackedQuantity = (uint)newTrackedCount; @@ -1211,8 +1213,6 @@ namespace Game.Entities if (count > 0) UpdateCriteria(CriteriaTypes.Currency, (uint)id, (uint)count); - CurrencyChanged((uint)id, count); - _currencyStorage[(uint)id] = playerCurrency; SetCurrency packet = new(); @@ -5076,8 +5076,8 @@ namespace Game.Entities } public void SetMoney(ulong value) { - SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.Coinage), value); MoneyChanged((uint)value); + SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.Coinage), value); UpdateCriteria(CriteriaTypes.HighestGoldValueOwned); } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index fe4a6de34..c8aa951ee 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -7083,20 +7083,23 @@ namespace Game { case QuestObjectiveType.Item: if (GetItemTemplate((uint)obj.ObjectID) == null) - Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing item entry {2}, quest can't be done.", qinfo.Id, obj.Id, obj.ObjectID); + Log.outError(LogFilter.Sql, $"Quest {qinfo.Id} objective {obj.Id} has non existing item entry {obj.ObjectID}, quest can't be done."); break; case QuestObjectiveType.Monster: if (GetCreatureTemplate((uint)obj.ObjectID) == null) - Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing creature entry {2}, quest can't be done.", qinfo.Id, obj.Id, obj.ObjectID); + Log.outError(LogFilter.Sql, $"Quest {qinfo.Id} objective {obj.Id} has non existing creature entry {obj.ObjectID}, quest can't be done."); break; case QuestObjectiveType.GameObject: if (GetGameObjectTemplate((uint)obj.ObjectID) == null) - Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing gameobject entry {2}, quest can't be done.", qinfo.Id, obj.Id, obj.ObjectID); + Log.outError(LogFilter.Sql, $"Quest {qinfo.Id} objective {obj.Id} has non existing gameobject entry {obj.ObjectID}, quest can't be done."); break; case QuestObjectiveType.TalkTo: + if (Global.ObjectMgr.GetCreatureTemplate((uint)obj.ObjectID) == null) + Log.outError(LogFilter.Sql, $"Quest {qinfo.Id} objective {obj.Id} has non existing creature entry {obj.ObjectID}, quest can't be done."); break; case QuestObjectiveType.MinReputation: case QuestObjectiveType.MaxReputation: + case QuestObjectiveType.IncreaseReputation: if (!CliDB.FactionStorage.ContainsKey((uint)obj.ObjectID)) Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing faction id {2}", qinfo.Id, obj.Id, obj.ObjectID); break; @@ -7130,6 +7133,11 @@ namespace Game break; case QuestObjectiveType.AreaTrigger: if (!CliDB.AreaTriggerStorage.ContainsKey((uint)obj.ObjectID) && obj.ObjectID != -1) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing AreaTrigger.db2 id {2}", qinfo.Id, obj.Id, obj.ObjectID); + break; + case QuestObjectiveType.AreaTriggerEnter: + case QuestObjectiveType.AreaTriggerExit: + if (Global.AreaTriggerDataStorage.GetAreaTriggerTemplate(new AreaTriggerId((uint)obj.ObjectID, false)) == null && Global.AreaTriggerDataStorage.GetAreaTriggerTemplate(new AreaTriggerId((uint)obj.ObjectID, true)) != null) Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing areatrigger id {2}", qinfo.Id, obj.Id, obj.ObjectID); break; case QuestObjectiveType.Money: @@ -7139,6 +7147,9 @@ namespace Game Log.outError(LogFilter.Sql, "Quest {0} objective {1} has unhandled type {2}", qinfo.Id, obj.Id, obj.Type); break; } + + if (obj.Flags.HasAnyFlag(QuestObjectiveFlags.Sequenced)) + qinfo.SetSpecialFlag(QuestSpecialFlags.SequencedObjectives); } for (var j = 0; j < SharedConst.QuestItemDropCount; j++) diff --git a/Source/Game/Handlers/MiscHandler.cs b/Source/Game/Handlers/MiscHandler.cs index f56ce461e..a063591c6 100644 --- a/Source/Game/Handlers/MiscHandler.cs +++ b/Source/Game/Handlers/MiscHandler.cs @@ -204,17 +204,21 @@ namespace Game List quests = Global.ObjectMgr.GetQuestsForAreaTrigger(packet.AreaTriggerID); if (quests != null) { + bool completedObjectiveInSequencedQuest = false; foreach (uint questId in quests) { Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questId); - if (qInfo != null && player.GetQuestStatus(questId) == QuestStatus.Incomplete) + ushort slot = player.FindQuestSlot(questId); + if (qInfo != null && slot < SharedConst.MaxQuestLogSize && player.GetQuestStatus(questId) == QuestStatus.Incomplete) { foreach (QuestObjective obj in qInfo.Objectives) { - if (obj.Type == QuestObjectiveType.AreaTrigger && !player.IsQuestObjectiveComplete(obj)) + if (obj.Type == QuestObjectiveType.AreaTrigger && !player.IsQuestObjectiveComplete(slot, qInfo, obj)) { player.SetQuestObjectiveData(obj, 1); player.SendQuestUpdateAddCreditSimple(obj); + if (qInfo.HasSpecialFlag(QuestSpecialFlags.SequencedObjectives)) + completedObjectiveInSequencedQuest = true; break; } } @@ -223,6 +227,9 @@ namespace Game player.CompleteQuest(questId); } } + + if (completedObjectiveInSequencedQuest) + player.UpdateForQuestWorldObjects(); } } diff --git a/Source/Game/Quest/Quest.cs b/Source/Game/Quest/Quest.cs index 74d7cafb6..1197ed7e3 100644 --- a/Source/Game/Quest/Quest.cs +++ b/Source/Game/Quest/Quest.cs @@ -728,9 +728,9 @@ namespace Game public class QuestStatusData { + public ushort Slot = SharedConst.MaxQuestLogSize; public QuestStatus Status; public uint Timer; - public int[] ObjectiveData; } public class QuestGreeting diff --git a/Source/Game/Quest/QuestObjectiveCriteriaManager.cs b/Source/Game/Quest/QuestObjectiveCriteriaManager.cs index 247df2dc2..01401f4d1 100644 --- a/Source/Game/Quest/QuestObjectiveCriteriaManager.cs +++ b/Source/Game/Quest/QuestObjectiveCriteriaManager.cs @@ -291,6 +291,13 @@ namespace Game return false; } + ushort slot = _owner.FindQuestSlot(objective.QuestID); + if (slot >= SharedConst.MaxQuestLogSize || !_owner.IsQuestObjectiveCompletable(slot, quest, objective)) + { + Log.outTrace(LogFilter.Achievement, $"QuestObjectiveCriteriaMgr.CanUpdateCriteriaTree: (Id: {criteria.Id} Type {criteria.Entry.Type} Quest Objective {objective.Id}) Objective not completable"); + return false; + } + return base.CanUpdateCriteriaTree(criteria, tree, referencePlayer); } diff --git a/Source/Game/Reputation/ReputationManager.cs b/Source/Game/Reputation/ReputationManager.cs index 6e76871c9..c9b85eb2d 100644 --- a/Source/Game/Reputation/ReputationManager.cs +++ b/Source/Game/Reputation/ReputationManager.cs @@ -371,7 +371,11 @@ namespace Game ReputationRank old_rank = ReputationToRank(factionState.Standing + BaseRep); ReputationRank new_rank = ReputationToRank(standing); - factionState.Standing = standing - BaseRep; + int newStanding = standing - BaseRep; + + _player.ReputationChanged(factionEntry, newStanding - factionState.Standing); + + factionState.Standing = newStanding; factionState.needSend = true; factionState.needSave = true; @@ -385,7 +389,7 @@ namespace Game UpdateRankCounters(old_rank, new_rank); - _player.ReputationChanged(factionEntry); + _player.UpdateCriteria(CriteriaTypes.KnownFactions, factionEntry.Id); _player.UpdateCriteria(CriteriaTypes.GainReputation, factionEntry.Id); _player.UpdateCriteria(CriteriaTypes.GainExaltedReputation, factionEntry.Id); diff --git a/Source/Scripts/Smart/SmartAI.cs b/Source/Scripts/Smart/SmartAI.cs index adb5489f0..ab8ee41ff 100644 --- a/Source/Scripts/Smart/SmartAI.cs +++ b/Source/Scripts/Smart/SmartAI.cs @@ -120,7 +120,8 @@ namespace Scripts.Smart // Called when a quest objective data change public override void OnQuestObjectiveChange(Player player, Quest quest, QuestObjective objective, int oldAmount, int newAmount) { - if (player.IsQuestObjectiveComplete(objective)) + ushort slot = player.FindQuestSlot(quest.Id); + if (slot < SharedConst.MaxQuestLogSize && player.IsQuestObjectiveComplete(slot, quest, objective)) { SmartScript smartScript = new SmartScript(); smartScript.OnInitialize(quest);