first commit

This commit is contained in:
2026-06-04 20:14:47 +03:00
commit a70f7fe2b9
143 changed files with 19543 additions and 0 deletions
+13
View File
@@ -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:
+49
View File
@@ -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:
+276
View File
@@ -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:
+76
View File
@@ -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: