//----------------------------------------------------------------------------- // Quest.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; namespace RolePlaying.Data { /// /// A quest that the party can embark on, with goals and rewards. /// public class Quest : ContentObject #if WINDOWS , ICloneable #endif { /// /// The possible stages of a quest. /// public enum QuestStage { NotStarted, InProgress, RequirementsMet, Completed }; /// /// The current stage of this quest. /// private QuestStage stage = QuestStage.NotStarted; /// /// The current stage of this quest. /// [ContentSerializerIgnore] public QuestStage Stage { get { return stage; } set { stage = value; } } /// /// The name of the quest. /// private string name; /// /// The name of the quest. /// public string Name { get { return name; } set { name = value; } } /// /// A description of the quest. /// private string description; /// /// A description of the quest. /// public string Description { get { return description; } set { description = value; } } /// /// A message describing the objective of the quest, /// presented when the player receives the quest. /// private string objectiveMessage; /// /// A message describing the objective of the quest, /// presented when the player receives the quest. /// public string ObjectiveMessage { get { return objectiveMessage; } set { objectiveMessage = value; } } /// /// A message announcing the completion of the quest, /// presented when the player reaches the goals of the quest. /// private string completionMessage; public string CompletionMessage { get { return completionMessage; } set { completionMessage = value; } } /// /// The gear that the player must have to finish the quest. /// private List> gearRequirements = new List>(); /// /// The gear that the player must have to finish the quest. /// public List> GearRequirements { get { return gearRequirements; } set { gearRequirements = value; } } /// /// The monsters that must be killed to finish the quest. /// private List> monsterRequirements = new List>(); /// /// The monsters that must be killed to finish the quest. /// public List> MonsterRequirements { get { return monsterRequirements; } set { monsterRequirements = value; } } /// /// Returns true if all requirements for this quest have been met. /// public bool AreRequirementsMet { get { foreach (QuestRequirement gearRequirement in gearRequirements) { if (gearRequirement.CompletedCount < gearRequirement.Count) { return false; } } foreach (QuestRequirement monsterRequirement in monsterRequirements) { if (monsterRequirement.CompletedCount < monsterRequirement.Count) { return false; } } return true; } } /// /// The fixed combat encounters added to the world when this quest is active. /// private List> fixedCombatEntries = new List>(); /// /// The fixed combat encounters added to the world when this quest is active. /// public List> FixedCombatEntries { get { return fixedCombatEntries; } set { fixedCombatEntries = value; } } /// /// The chests added to thew orld when this quest is active. /// private List> chestEntries = new List>(); /// /// The chests added to thew orld when this quest is active. /// public List> ChestEntries { get { return chestEntries; } set { chestEntries = value; } } /// /// The map with the destination Npc, if any. /// private string destinationMapContentName; /// /// The map with the destination Npc, if any. /// [ContentSerializer(Optional = true)] public string DestinationMapContentName { get { return destinationMapContentName; } set { destinationMapContentName = value; } } /// /// The Npc that the party must visit to finish the quest, if any. /// private string destinationNpcContentName; /// /// The Npc that the party must visit to finish the quest, if any. /// [ContentSerializer(Optional = true)] public string DestinationNpcContentName { get { return destinationNpcContentName; } set { destinationNpcContentName = value; } } /// /// The message shown when the party is eligible to complete the quest, if any. /// private string destinationObjectiveMessage; /// /// The message shown when the party is eligible to complete the quest, if any. /// [ContentSerializer(Optional = true)] public string DestinationObjectiveMessage { get { return destinationObjectiveMessage; } set { destinationObjectiveMessage = value; } } /// /// The number of experience points given to each party member as a reward. /// private int experienceReward; /// /// The number of experience points given to each party member as a reward. /// [ContentSerializer(Optional = true)] public int ExperienceReward { get { return experienceReward; } set { experienceReward = value; } } /// /// The amount of gold given to the party as a reward. /// private int goldReward; /// /// The amount of gold given to the party as a reward. /// [ContentSerializer(Optional = true)] public int GoldReward { get { return goldReward; } set { goldReward = value; } } /// /// The content names of the gear given to the party as a reward. /// private List gearRewardContentNames = new List(); /// /// The content names of the gear given to the party as a reward. /// [ContentSerializer(Optional = true)] public List GearRewardContentNames { get { return gearRewardContentNames; } set { gearRewardContentNames = value; } } /// /// The gear given to the party as a reward. /// private List gearRewards = new List(); /// /// The gear given to the party as a reward. /// [ContentSerializerIgnore] public List GearRewards { get { return gearRewards; } set { gearRewards = value; } } /// /// Reads a Quest object from the content pipeline. /// public class QuestReader : ContentTypeReader { /// /// Reads a Quest object from the content pipeline. /// protected override Quest Read(ContentReader input, Quest existingInstance) { Quest quest = existingInstance; if (quest == null) { quest = new Quest(); } quest.AssetName = input.AssetName; quest.Name = input.ReadString(); quest.Description = input.ReadString(); quest.ObjectiveMessage = input.ReadString(); quest.CompletionMessage = input.ReadString(); quest.GearRequirements.AddRange( input.ReadObject>>()); quest.MonsterRequirements.AddRange( input.ReadObject>>()); // load the fixed combat entries Random random = new Random(); quest.FixedCombatEntries.AddRange( input.ReadObject>>()); foreach (WorldEntry fixedCombatEntry in quest.FixedCombatEntries) { fixedCombatEntry.Content = input.ContentManager.Load(Path.Combine("Maps", "FixedCombats", fixedCombatEntry.ContentName)); // clone the map sprite in the entry, as there may be many entries // per FixedCombat fixedCombatEntry.MapSprite = fixedCombatEntry.Content.Entries[0].Content.MapSprite.Clone() as AnimatingSprite; // play the idle animation fixedCombatEntry.MapSprite.PlayAnimation("Idle", fixedCombatEntry.Direction); // advance in a random amount so the animations aren't synchronized fixedCombatEntry.MapSprite.UpdateAnimation( 4f * (float)random.NextDouble()); } quest.ChestEntries.AddRange( input.ReadObject>>()); foreach (WorldEntry chestEntry in quest.ChestEntries) { chestEntry.Content = input.ContentManager.Load(Path.Combine("Maps", "Chests", chestEntry.ContentName)).Clone() as Chest; } quest.DestinationMapContentName = input.ReadString(); quest.DestinationNpcContentName = input.ReadString(); quest.DestinationObjectiveMessage = input.ReadString(); quest.experienceReward = input.ReadInt32(); quest.goldReward = input.ReadInt32(); quest.GearRewardContentNames.AddRange( input.ReadObject>()); foreach (string contentName in quest.GearRewardContentNames) { quest.GearRewards.Add(input.ContentManager.Load(Path.Combine("Gear", contentName))); } return quest; } } public object Clone() { Quest quest = new Quest(); quest.AssetName = AssetName; foreach (WorldEntry chestEntry in chestEntries) { WorldEntry worldEntry = new WorldEntry(); worldEntry.Content = chestEntry.Content.Clone() as Chest; worldEntry.ContentName = chestEntry.ContentName; worldEntry.Count = chestEntry.Count; worldEntry.Direction = chestEntry.Direction; worldEntry.MapContentName = chestEntry.MapContentName; worldEntry.MapPosition = chestEntry.MapPosition; quest.chestEntries.Add(worldEntry); } quest.completionMessage = completionMessage; quest.description = description; quest.destinationMapContentName = destinationMapContentName; quest.destinationNpcContentName = destinationNpcContentName; quest.destinationObjectiveMessage = destinationObjectiveMessage; quest.experienceReward = experienceReward; quest.fixedCombatEntries.AddRange(fixedCombatEntries); quest.gearRequirements.AddRange(gearRequirements); quest.gearRewardContentNames.AddRange(gearRewardContentNames); quest.gearRewards.AddRange(gearRewards); quest.goldReward = goldReward; quest.monsterRequirements.AddRange(monsterRequirements); quest.name = name; quest.objectiveMessage = objectiveMessage; quest.stage = stage; return quest; } internal static Quest Load(string questContentName, ContentManager contentManager) { var questElement = XmlHelper.GetAssetElementFromXML(Path.Combine("Quests", questContentName)); var quest = new Quest { AssetName = questContentName, Name = (string)questElement.Element("Name"), Description = (string)questElement.Element("Description"), ObjectiveMessage = (string)questElement.Element("ObjectiveMessage"), CompletionMessage = (string)questElement.Element("CompletionMessage"), DestinationMapContentName = (string)questElement.Element("DestinationMapContentName"), DestinationNpcContentName = (string)questElement.Element("DestinationNpcContentName"), DestinationObjectiveMessage = (string)questElement.Element("DestinationObjectiveMessage"), ExperienceReward = (int?)questElement.Element("ExperienceReward") ?? 0, GoldReward = (int?)questElement.Element("GoldReward") ?? 0, Stage = Enum.TryParse((string)questElement.Element("Stage"), out var stage) ? stage : QuestStage.NotStarted, GearRewardContentNames = questElement.Element("GearRewardContentNames")? .Elements("Item") .Select(x => (string)x) .ToList() ?? new List(), GearRequirements = questElement.Element("GearRequirements")? .Elements("Item") .Select(gearElement => { var gearContentName = (string)gearElement.Element("ContentName"); var gearCount = (int?)gearElement.Element("Count") ?? 1; var gear = Equipment.Load(Path.Combine("Gear", gearContentName), contentManager); return new QuestRequirement { ContentName = gearContentName, Count = gearCount, Content = gear }; }).ToList() ?? new List>(), MonsterRequirements = questElement.Element("MonsterRequirements")? .Elements("Item") .Select(entry => { var monsterContentName = (string)entry.Element("ContentName"); var monsterCount = (int?)entry.Element("Count") ?? 1; var monster = Monster.Load(Path.Combine("Characters", "Monsters", monsterContentName), contentManager); return new QuestRequirement { ContentName = monsterContentName, Count = monsterCount, Content = monster }; }).ToList() ?? new List>(), FixedCombatEntries = questElement.Element("FixedCombatEntries")?.Elements("Item") .Select(item => { var contentName = (string)item.Element("ContentName"); var direction = Enum.TryParse((string)item.Element("Direction"), out var dir) ? dir : default; var mapPosition = new Point( int.Parse(item.Element("MapPosition").Value.Split(' ')[0]), int.Parse(item.Element("MapPosition").Value.Split(' ')[1])); // Load the fixed combat asset XML using contentName var fixedCombat = FixedCombat.Load(Path.Combine("Maps", "FixedCombats", contentName), contentManager); AnimatingSprite animatingSprite = null; if (fixedCombat.Entries.Count > 0) { animatingSprite = fixedCombat.Entries[0].Content.MapSprite.Clone() as AnimatingSprite; } return new WorldEntry { ContentName = contentName, Content = fixedCombat, Direction = direction, MapPosition = mapPosition, MapSprite = animatingSprite, }; }).ToList() ?? new List>(), ChestEntries = questElement.Element("ChestEntries")? .Elements("Item") .Select(chestRequirement => { var contentName = (string)chestRequirement.Element("ContentName"); var direction = Enum.TryParse((string)chestRequirement.Element("Direction"), out var dir) ? dir : default; var mapPosition = new Point( int.Parse(chestRequirement.Element("MapPosition").Value.Split(' ')[0]), int.Parse(chestRequirement.Element("MapPosition").Value.Split(' ')[1])); var mapContentName = (string)chestRequirement.Element("MapContentName"); var count = (int?)chestRequirement.Element("Count") ?? 1; // Load the QuestNpc asset XML using contentName var chestAsset = XmlHelper.GetAssetElementFromXML(Path.Combine("Maps", "Chests", contentName)); var chest = Chest.Load(chestAsset, contentManager); return new WorldEntry { ContentName = contentName, Content = chest, Count = count, Direction = direction, MapContentName = mapContentName, MapPosition = mapPosition, MapSprite = null, }; }).ToList() ?? new List>(), }; // Load the gear rewards foreach (var gearContentName in quest.GearRewardContentNames) { var gear = Equipment.Load(Path.Combine("Gear", gearContentName), contentManager); quest.GearRewards.Add(gear); } return quest; } } }