first commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(menuName = "Lab05/Quest Data", fileName = "QuestData")]
|
||||
public class QuestData : ScriptableObject
|
||||
{
|
||||
public string questId;
|
||||
public string questName;
|
||||
[TextArea(3, 6)] public string description;
|
||||
public List<QuestGoal> goals = new List<QuestGoal>();
|
||||
public ItemData rewardItem;
|
||||
[Min(0)] public int rewardAmount;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcd9bc81ab61a9eae896e42d1df1c2c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public enum QuestGoalType
|
||||
{
|
||||
CollectItem,
|
||||
KillEnemy,
|
||||
TalkToNPC
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class QuestGoal
|
||||
{
|
||||
public QuestGoalType goalType;
|
||||
public ItemData targetItem;
|
||||
public string enemyType;
|
||||
public string npcId;
|
||||
[Min(1)] public int requiredAmount = 1;
|
||||
|
||||
public string GetTargetKey()
|
||||
{
|
||||
switch (goalType)
|
||||
{
|
||||
case QuestGoalType.CollectItem:
|
||||
return targetItem != null ? targetItem.itemId : string.Empty;
|
||||
case QuestGoalType.KillEnemy:
|
||||
return enemyType;
|
||||
case QuestGoalType.TalkToNPC:
|
||||
return npcId;
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDisplayName()
|
||||
{
|
||||
switch (goalType)
|
||||
{
|
||||
case QuestGoalType.CollectItem:
|
||||
return targetItem != null ? targetItem.itemName : "Unknown item";
|
||||
case QuestGoalType.KillEnemy:
|
||||
return string.IsNullOrWhiteSpace(enemyType) ? "Enemy" : enemyType;
|
||||
case QuestGoalType.TalkToNPC:
|
||||
return string.IsNullOrWhiteSpace(npcId) ? "NPC" : npcId;
|
||||
default:
|
||||
return "Goal";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6edd48c91e2c16bd812db71cb6638bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,276 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class QuestManager : MonoBehaviour
|
||||
{
|
||||
public static QuestManager Instance { get; private set; }
|
||||
|
||||
[SerializeField] private List<QuestData> availableQuests = new List<QuestData>();
|
||||
[SerializeField] private List<QuestState> activeQuests = new List<QuestState>();
|
||||
[SerializeField] private List<QuestState> completedQuests = new List<QuestState>();
|
||||
[SerializeField] private List<QuestState> failedQuests = new List<QuestState>();
|
||||
|
||||
public IReadOnlyList<QuestData> AvailableQuests => availableQuests;
|
||||
public IReadOnlyList<QuestState> ActiveQuests => activeQuests;
|
||||
public IReadOnlyList<QuestState> CompletedQuests => completedQuests;
|
||||
public IReadOnlyList<QuestState> FailedQuests => failedQuests;
|
||||
|
||||
public event Action OnQuestsChanged;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void SetAvailableQuests(IEnumerable<QuestData> quests)
|
||||
{
|
||||
availableQuests.Clear();
|
||||
if (quests != null)
|
||||
{
|
||||
availableQuests.AddRange(quests);
|
||||
}
|
||||
}
|
||||
|
||||
public bool StartQuest(QuestData quest)
|
||||
{
|
||||
if (quest == null || IsQuestActive(quest) || IsQuestCompleted(quest))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!availableQuests.Contains(quest))
|
||||
{
|
||||
availableQuests.Add(quest);
|
||||
}
|
||||
|
||||
QuestState state = new QuestState(quest);
|
||||
activeQuests.Add(state);
|
||||
Notify($"Получен квест:\n{quest.questName}");
|
||||
OnQuestsChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CompleteQuest(QuestData quest)
|
||||
{
|
||||
QuestState state = activeQuests.Find(candidate => candidate.questData == quest);
|
||||
if (state == null || !state.AreAllGoalsComplete())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ConsumeRequiredItems(quest))
|
||||
{
|
||||
Notify($"Не хватает предметов для завершения:\n{quest.questName}");
|
||||
OnQuestsChanged?.Invoke();
|
||||
return false;
|
||||
}
|
||||
|
||||
state.status = QuestStatus.Completed;
|
||||
activeQuests.Remove(state);
|
||||
completedQuests.Add(state);
|
||||
|
||||
Notify($"Квест завершён:\n{quest.questName}");
|
||||
|
||||
if (quest.rewardItem != null && quest.rewardAmount > 0 && Inventory.Instance != null)
|
||||
{
|
||||
Inventory.Instance.AddItem(quest.rewardItem, quest.rewardAmount, false);
|
||||
Notify($"Получена награда:\n{quest.rewardItem.itemId} x{quest.rewardAmount}");
|
||||
}
|
||||
|
||||
OnQuestsChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ConsumeRequiredItems(QuestData quest)
|
||||
{
|
||||
if (quest == null || quest.goals == null || Inventory.Instance == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (QuestGoal goal in quest.goals)
|
||||
{
|
||||
if (goal.goalType != QuestGoalType.CollectItem || goal.targetItem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Inventory.Instance.HasItem(goal.targetItem, goal.requiredAmount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (QuestGoal goal in quest.goals)
|
||||
{
|
||||
if (goal.goalType == QuestGoalType.CollectItem && goal.targetItem != null)
|
||||
{
|
||||
Inventory.Instance.RemoveItem(goal.targetItem, goal.requiredAmount);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void FailQuest(QuestData quest)
|
||||
{
|
||||
QuestState state = activeQuests.Find(candidate => candidate.questData == quest);
|
||||
if (state == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.status = QuestStatus.Failed;
|
||||
activeQuests.Remove(state);
|
||||
failedQuests.Add(state);
|
||||
OnQuestsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void OnItemCollected(ItemData item, int amount)
|
||||
{
|
||||
if (item == null || amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProgress(QuestGoalType.CollectItem, item.itemId, amount);
|
||||
}
|
||||
|
||||
public void OnEnemyKilled(string enemyType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(enemyType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProgress(QuestGoalType.KillEnemy, enemyType, 1);
|
||||
}
|
||||
|
||||
public void OnNpcTalked(string npcId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(npcId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProgress(QuestGoalType.TalkToNPC, npcId, 1);
|
||||
}
|
||||
|
||||
public string GetGoalProgress(QuestData quest, int goalIndex)
|
||||
{
|
||||
QuestState state = GetQuestState(quest);
|
||||
if (state == null || quest == null || goalIndex < 0 || goalIndex >= quest.goals.Count)
|
||||
{
|
||||
return "0/0";
|
||||
}
|
||||
|
||||
QuestGoal goal = quest.goals[goalIndex];
|
||||
return $"{state.GetProgress(goalIndex)}/{goal.requiredAmount}";
|
||||
}
|
||||
|
||||
public float GetQuestCompletionPercent(QuestState state)
|
||||
{
|
||||
if (state == null || state.questData == null || state.questData.goals.Count == 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
float total = 0f;
|
||||
for (int i = 0; i < state.questData.goals.Count; i++)
|
||||
{
|
||||
QuestGoal goal = state.questData.goals[i];
|
||||
total += Mathf.Clamp01(state.GetProgress(i) / (float)Mathf.Max(1, goal.requiredAmount));
|
||||
}
|
||||
|
||||
return total / state.questData.goals.Count;
|
||||
}
|
||||
|
||||
public QuestState GetQuestState(QuestData quest)
|
||||
{
|
||||
QuestState state = activeQuests.Find(candidate => candidate.questData == quest);
|
||||
if (state != null)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
state = completedQuests.Find(candidate => candidate.questData == quest);
|
||||
if (state != null)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
return failedQuests.Find(candidate => candidate.questData == quest);
|
||||
}
|
||||
|
||||
private void UpdateProgress(QuestGoalType type, string targetKey, int amount)
|
||||
{
|
||||
bool changed = false;
|
||||
List<QuestData> completedNow = new List<QuestData>();
|
||||
|
||||
foreach (QuestState state in activeQuests)
|
||||
{
|
||||
QuestData quest = state.questData;
|
||||
if (quest == null || quest.goals == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < quest.goals.Count; i++)
|
||||
{
|
||||
QuestGoal goal = quest.goals[i];
|
||||
if (goal.goalType != type || goal.GetTargetKey() != targetKey || state.IsGoalComplete(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
state.AddProgress(i, amount);
|
||||
changed = true;
|
||||
Notify($"Прогресс:\n{goal.GetDisplayName()} {state.GetProgress(i)}/{goal.requiredAmount}");
|
||||
}
|
||||
|
||||
if (state.AreAllGoalsComplete())
|
||||
{
|
||||
completedNow.Add(quest);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (QuestData quest in completedNow)
|
||||
{
|
||||
CompleteQuest(quest);
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
OnQuestsChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsQuestActive(QuestData quest)
|
||||
{
|
||||
return activeQuests.Exists(candidate => candidate.questData == quest);
|
||||
}
|
||||
|
||||
private bool IsQuestCompleted(QuestData quest)
|
||||
{
|
||||
return completedQuests.Exists(candidate => candidate.questData == quest);
|
||||
}
|
||||
|
||||
private void Notify(string message)
|
||||
{
|
||||
if (NotificationUI.Instance != null)
|
||||
{
|
||||
NotificationUI.Instance.Show(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e197b9d5258f0aa02850be4f5eb2db41
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[Serializable]
|
||||
public class QuestState
|
||||
{
|
||||
public QuestData questData;
|
||||
public QuestStatus status;
|
||||
public List<int> goalProgress = new List<int>();
|
||||
|
||||
public QuestState(QuestData quest)
|
||||
{
|
||||
questData = quest;
|
||||
status = QuestStatus.Active;
|
||||
|
||||
int goalCount = quest != null && quest.goals != null ? quest.goals.Count : 0;
|
||||
for (int i = 0; i < goalCount; i++)
|
||||
{
|
||||
goalProgress.Add(0);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsGoalComplete(int index)
|
||||
{
|
||||
if (questData == null || index < 0 || index >= questData.goals.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetProgress(index) >= questData.goals[index].requiredAmount;
|
||||
}
|
||||
|
||||
public int GetProgress(int index)
|
||||
{
|
||||
if (index < 0 || index >= goalProgress.Count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return goalProgress[index];
|
||||
}
|
||||
|
||||
public void AddProgress(int index, int amount)
|
||||
{
|
||||
if (questData == null || index < 0 || index >= questData.goals.Count || amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (goalProgress.Count < questData.goals.Count)
|
||||
{
|
||||
goalProgress.Add(0);
|
||||
}
|
||||
|
||||
int required = questData.goals[index].requiredAmount;
|
||||
goalProgress[index] = Math.Min(required, goalProgress[index] + amount);
|
||||
}
|
||||
|
||||
public bool AreAllGoalsComplete()
|
||||
{
|
||||
if (questData == null || questData.goals == null || questData.goals.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < questData.goals.Count; i++)
|
||||
{
|
||||
if (!IsGoalComplete(i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e3f862929be70f95ac2a633eabc57ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
public enum QuestStatus
|
||||
{
|
||||
Active,
|
||||
Completed,
|
||||
Failed
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5ef75b1ddddcabb985c5c9d4ad4d89b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user