From fad44c06443ae760858f4b9f9314a8ff9541b54e Mon Sep 17 00:00:00 2001 From: ben_singer Date: Thu, 28 Nov 2024 16:38:19 +0000 Subject: [PATCH 1/3] Fixed issue with attributes not serializing correctly with System.Text.Json. Fixed default implementations of Examinations --- .../Assets/Regions/Hub/Rooms/Clearing.cs | 19 ++++- NetAF.Examples/Program.cs | 1 - .../Attributes/AttributeManager_Tests.cs | 10 +++ .../Assets/Attributes/Attribute_Tests.cs | 24 ------ .../Persistence/Json/JsonSave_Tests.cs | 4 +- .../Assets/AttributeSerialization_Tests.cs | 23 +++--- NetAF/Assets/Attributes/Attribute.cs | 38 +--------- NetAF/Assets/Attributes/AttributeManager.cs | 10 ++- .../Assets/Characters/NonPlayableCharacter.cs | 6 +- NetAF/Assets/Characters/PlayableCharacter.cs | 6 +- NetAF/Assets/ExaminableObject.cs | 17 ++++- NetAF/Assets/Interaction.cs | 9 +++ NetAF/Assets/Item.cs | 6 +- NetAF/Assets/Locations/Exit.cs | 6 +- NetAF/Assets/Locations/Overworld.cs | 26 +++++-- NetAF/Assets/Locations/Region.cs | 40 +++++----- NetAF/Assets/Locations/Room.cs | 66 ++++++++++------- .../FrameBuilders/ConsoleSceneFrameBuilder.cs | 12 ++- .../Assets/AttributeAndValueSerialization.cs | 74 +++++++++++++++++++ .../Assets/AttributeManagerSerialization.cs | 9 ++- .../Assets/AttributeSerialization.cs | 67 ----------------- 21 files changed, 248 insertions(+), 225 deletions(-) delete mode 100644 NetAF.Tests/Assets/Attributes/Attribute_Tests.cs create mode 100644 NetAF/Serialization/Assets/AttributeAndValueSerialization.cs delete mode 100644 NetAF/Serialization/Assets/AttributeSerialization.cs diff --git a/NetAF.Examples/Assets/Regions/Hub/Rooms/Clearing.cs b/NetAF.Examples/Assets/Regions/Hub/Rooms/Clearing.cs index 7e0def02..c5e20fd1 100644 --- a/NetAF.Examples/Assets/Regions/Hub/Rooms/Clearing.cs +++ b/NetAF.Examples/Assets/Regions/Hub/Rooms/Clearing.cs @@ -25,7 +25,16 @@ internal class Clearing : IAssetTemplate /// The asset. public Room Instantiate() { - var room = new Room(Name, Description); + var room = new Room(Name, Description, examination: request => + { + if (request.Scene.Examiner.Attributes.GetValue("Coin") == 0) + { + request.Scene.Examiner.Attributes.Add("Coin", 1); + return new Examination("Well look at that, you found a coin."); + } + + return Room.DefaultRoomExamination(request); + }); var conversation = new Conversation( [ @@ -46,7 +55,13 @@ public Room Instantiate() new("Fine, suit yourself! Squarrk!", new ToName("ModeQuestion")) ]); - room.AddCharacter(new NonPlayableCharacter(new Identifier("Parrot"), new Description("A brightly colored parrot."), conversation: conversation)); + room.AddCharacter(new NonPlayableCharacter(new Identifier("Parrot"), new Description("A brightly colored parrot."), conversation: conversation, interaction: item => + { + if (item.Identifier.Equals("Knife")) + return new Interaction(InteractionResult.TargetExpires, item, "You slash at the parrot, in a flash of red feathers it is no more! The beast is vanquished!"); + + return new(InteractionResult.NoChange, item); + })); return room; } diff --git a/NetAF.Examples/Program.cs b/NetAF.Examples/Program.cs index 2b4c5535..fec8f7a2 100644 --- a/NetAF.Examples/Program.cs +++ b/NetAF.Examples/Program.cs @@ -14,7 +14,6 @@ using NetAF.Extensions; using NetAF.Logic; using NetAF.Logic.Configuration; -using NetAF.Rendering.Console; namespace NetAF.Examples { diff --git a/NetAF.Tests/Assets/Attributes/AttributeManager_Tests.cs b/NetAF.Tests/Assets/Attributes/AttributeManager_Tests.cs index 86fd6b78..e20eea59 100644 --- a/NetAF.Tests/Assets/Attributes/AttributeManager_Tests.cs +++ b/NetAF.Tests/Assets/Attributes/AttributeManager_Tests.cs @@ -16,6 +16,16 @@ public void GivenNoAttributes_WhenGetAttributes_ThenReturnEmptyArray() Assert.AreEqual(0, result.Length); } + [TestMethod] + public void GivenNonExistentAttribute_WhenGetValue_ThenReturn0() + { + AttributeManager manager = new(); + + var result = manager.GetValue(""); + + Assert.AreEqual(0, result); + } + [TestMethod] public void GivenNoAttributes_WhenAdd_ThenOneAttribute() { diff --git a/NetAF.Tests/Assets/Attributes/Attribute_Tests.cs b/NetAF.Tests/Assets/Attributes/Attribute_Tests.cs deleted file mode 100644 index 59d34212..00000000 --- a/NetAF.Tests/Assets/Attributes/Attribute_Tests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NetAF.Assets.Attributes; -using NetAF.Serialization.Assets; - -namespace NetAF.Tests.Assets.Attributes -{ - [TestClass] - internal class Attribute_Tests - { - [TestMethod] - public void GivenFromSerialization_WhenFromAttribute_ThenAttributeRestoredCorrectly() - { - Attribute attribute = new("a", "b", 1, 10); - AttributeSerialization serialization = AttributeSerialization.FromAttribute(attribute); - - var result = Attribute.FromSerialization(serialization); - - Assert.AreEqual("a", result.Name); - Assert.AreEqual("b", result.Description); - Assert.AreEqual(1, result.Minimum); - Assert.AreEqual(10, result.Maximum); - } - } -} diff --git a/NetAF.Tests/Persistence/Json/JsonSave_Tests.cs b/NetAF.Tests/Persistence/Json/JsonSave_Tests.cs index d1aa0ce6..e67e6328 100644 --- a/NetAF.Tests/Persistence/Json/JsonSave_Tests.cs +++ b/NetAF.Tests/Persistence/Json/JsonSave_Tests.cs @@ -24,7 +24,7 @@ public void GivenSimpleGame_WhenToJson_ThenExpectedStringReturned() var game = Game.Create(new GameInfo(string.Empty, string.Empty, string.Empty), string.Empty, AssetGenerator.Retained(overworldMaker.Make(), new PlayableCharacter(string.Empty, string.Empty)), GameEndConditions.NoEnd, TestGameConfiguration.Default).Invoke(); game.Overworld.CurrentRegion.Enter(); var save = RestorePoint.Create("Test", game); - var expectedMinusDateTime = """{"Game":{"ActivePlayerIdentifier":"","Players":[{"Items":[],"IsAlive":true,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]}],"Overworld":{"Regions":[{"Rooms":[{"HasBeenVisited":true,"Items":[{"Identifier":"","IsPlayerVisible":false,"AttributeManager":{"Values":{}},"Commands":[]}],"Exits":[],"Characters":[],"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]}],"CurrentRoom":"","IsVisibleWithoutDiscovery":false,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]}],"CurrentRegion":"","Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]},"InactivePlayerLocations":[]},"Name":"Test"""; + var expectedMinusDateTime = """{"Game":{"ActivePlayerIdentifier":"","Players":[{"Items":[],"IsAlive":true,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]}],"Overworld":{"Regions":[{"Rooms":[{"HasBeenVisited":true,"Items":[{"Identifier":"","IsPlayerVisible":false,"AttributeManager":{"Values":[]},"Commands":[]}],"Exits":[],"Characters":[],"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]}],"CurrentRoom":"","IsVisibleWithoutDiscovery":false,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]}],"CurrentRegion":"","Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]},"InactivePlayerLocations":[]},"Name":"Test"""; var json = JsonSave.ToJson(save); Assert.IsTrue(json.StartsWith(expectedMinusDateTime)); @@ -33,7 +33,7 @@ public void GivenSimpleGame_WhenToJson_ThenExpectedStringReturned() [TestMethod] public void GivenSimpleJson_WhenFromJson_ThenValidSaveReturned() { - var json = """{"Game":{"ActivePlayerIdentifier":"","Players":[{"Items":[],"IsAlive":true,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]}],"Overworld":{"Regions":[{"Rooms":[{"HasBeenVisited":true,"Items":[{"Identifier":"","IsPlayerVisible":false,"AttributeManager":{"Values":{}},"Commands":[]}],"Exits":[],"Characters":[],"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]}],"CurrentRoom":"","IsVisibleWithoutDiscovery":false,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]}],"CurrentRegion":"","Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":{}},"Commands":[]},"InactivePlayerLocations":[]},"Name":"Test","CreationTime":"2024-11-16T18:55:20.0148219+00:00"}"""; + var json = """{"Game":{"ActivePlayerIdentifier":"","Players":[{"Items":[],"IsAlive":true,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]}],"Overworld":{"Regions":[{"Rooms":[{"HasBeenVisited":true,"Items":[{"Identifier":"","IsPlayerVisible":false,"AttributeManager":{"Values":[]},"Commands":[]}],"Exits":[],"Characters":[],"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]}],"CurrentRoom":"","IsVisibleWithoutDiscovery":false,"Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]}],"CurrentRegion":"","Identifier":"","IsPlayerVisible":true,"AttributeManager":{"Values":[]},"Commands":[]},"InactivePlayerLocations":[]},"Name":"Test","CreationTime":"2024-11-28T16:36:00.6660777+00:00"}"""; var result = JsonSave.FromJson(json); diff --git a/NetAF.Tests/Serialization/Assets/AttributeSerialization_Tests.cs b/NetAF.Tests/Serialization/Assets/AttributeSerialization_Tests.cs index 79494d17..32d146d1 100644 --- a/NetAF.Tests/Serialization/Assets/AttributeSerialization_Tests.cs +++ b/NetAF.Tests/Serialization/Assets/AttributeSerialization_Tests.cs @@ -5,14 +5,14 @@ namespace NetAF.Tests.Serialization.Assets { [TestClass] - public class AttributeSerialization_Tests + public class AttributeAndValueSerialization_Tests { [TestMethod] public void GivenAttributeWhenNameIsA_WhenFromAttribute_ThenNameIsA() { Attribute attribute = new("A", "B", 5, 10); - AttributeSerialization result = AttributeSerialization.FromAttribute(attribute); + AttributeAndValueSerialization result = AttributeAndValueSerialization.FromAttributeAndValue(new(attribute, 0)); Assert.AreEqual("A", result.Name); } @@ -22,7 +22,7 @@ public void GivenAttributeWhenDescriptionIsB_WhenFromAttribute_ThenDescriptionIs { Attribute attribute = new("A", "B", 5, 10); - AttributeSerialization result = AttributeSerialization.FromAttribute(attribute); + AttributeAndValueSerialization result = AttributeAndValueSerialization.FromAttributeAndValue(new(attribute, 0)); Assert.AreEqual("B", result.Description); } @@ -32,7 +32,7 @@ public void GivenAttributeWhenMinimumIs5_WhenFromAttribute_ThenMinimumIs5() { Attribute attribute = new("A", "B", 5, 10); - AttributeSerialization result = AttributeSerialization.FromAttribute(attribute); + AttributeAndValueSerialization result = AttributeAndValueSerialization.FromAttributeAndValue(new(attribute, 0)); Assert.AreEqual(5, result.Minimum); } @@ -42,24 +42,19 @@ public void GivenAttributeWhenMaximumIs10_WhenFromAttribute_ThenMaximumIs10() { Attribute attribute = new("A", "B", 5, 10); - AttributeSerialization result = AttributeSerialization.FromAttribute(attribute); + AttributeAndValueSerialization result = AttributeAndValueSerialization.FromAttributeAndValue(new(attribute, 0)); Assert.AreEqual(10, result.Maximum); } [TestMethod] - public void GivenEmptyAttribute_WhenRestoreFrom_ThenAttributeRestoredCorrectly() + public void GivenAttributeWhenValueIs3_WhenFromAttribute_ThenValueIs3() { - Attribute attribute = new("a", "b", 1, 10); - Attribute attribute2 = new(string.Empty, string.Empty, 0, 0); - AttributeSerialization serialization = AttributeSerialization.FromAttribute(attribute); + Attribute attribute = new("A", "B", 5, 10); - serialization.Restore(attribute2); + AttributeAndValueSerialization result = AttributeAndValueSerialization.FromAttributeAndValue(new(attribute, 3)); - Assert.AreEqual("a", attribute2.Name); - Assert.AreEqual("b", attribute2.Description); - Assert.AreEqual(1, attribute2.Minimum); - Assert.AreEqual(10, attribute2.Maximum); + Assert.AreEqual(3, result.Value); } } } diff --git a/NetAF/Assets/Attributes/Attribute.cs b/NetAF/Assets/Attributes/Attribute.cs index cb89de37..408c8ed0 100644 --- a/NetAF/Assets/Attributes/Attribute.cs +++ b/NetAF/Assets/Attributes/Attribute.cs @@ -1,7 +1,4 @@ -using NetAF.Serialization; -using NetAF.Serialization.Assets; - -namespace NetAF.Assets.Attributes +namespace NetAF.Assets.Attributes { /// /// Provides a description of an attribute. @@ -10,7 +7,7 @@ namespace NetAF.Assets.Attributes /// Specify the description of the attribute. /// Specify the minimum limit of the attribute. /// Specify the maximum limit of the attribute. - public class Attribute(string name, string description, int minimum, int maximum) : IRestoreFromObjectSerialization + public class Attribute(string name, string description, int minimum, int maximum) { #region Properties @@ -35,36 +32,5 @@ public class Attribute(string name, string description, int minimum, int maximum public int Maximum { get; private set; } = maximum; #endregion - - #region StaticMethods - - /// - /// Create a new Attribute from a serialization. - /// - /// The serialization to create the Attribute from. - public static Attribute FromSerialization(AttributeSerialization serialization) - { - var attribute = new Attribute(string.Empty, string.Empty, 0, 0); - attribute.RestoreFrom(serialization); - return attribute; - } - - #endregion - - #region Implementation of IRestoreFromObjectSerialization - - /// - /// Restore this object from a serialization. - /// - /// The serialization to restore from. - public void RestoreFrom(AttributeSerialization serialization) - { - Name = serialization.Name; - Description = serialization.Description; - Minimum = serialization.Minimum; - Maximum = serialization.Maximum; - } - - #endregion } } diff --git a/NetAF/Assets/Attributes/AttributeManager.cs b/NetAF/Assets/Attributes/AttributeManager.cs index 89b72829..feb3e93f 100644 --- a/NetAF/Assets/Attributes/AttributeManager.cs +++ b/NetAF/Assets/Attributes/AttributeManager.cs @@ -106,6 +106,9 @@ public int GetValue(string attributeName) /// The value. public int GetValue(Attribute attribute) { + if (attribute == null) + return 0; + return attributes.TryGetValue(attribute, out var value) ? value : 0; } @@ -220,8 +223,11 @@ public void RestoreFrom(AttributeManagerSerialization serialization) { RemoveAll(); - foreach (var value in serialization.Values) - Add(Attribute.FromSerialization(value.Key), value.Value); + foreach (var keyValuePair in serialization.Values) + { + var attribute = new Attribute(keyValuePair.Name, keyValuePair.Description, keyValuePair.Minimum, keyValuePair.Maximum); + Add(attribute, keyValuePair.Value); + } } #endregion diff --git a/NetAF/Assets/Characters/NonPlayableCharacter.cs b/NetAF/Assets/Characters/NonPlayableCharacter.cs index 7ff32657..14ac0318 100644 --- a/NetAF/Assets/Characters/NonPlayableCharacter.cs +++ b/NetAF/Assets/Characters/NonPlayableCharacter.cs @@ -40,10 +40,8 @@ public NonPlayableCharacter(Identifier identifier, IDescription description, Con Description = description; Conversation = conversation; Commands = commands ?? []; - Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); - - if (examination != null) - Examination = examination; + Interaction = interaction ?? Assets.Interaction.NoChange; + Examination = examination ?? DefaultExamination; } /// diff --git a/NetAF/Assets/Characters/PlayableCharacter.cs b/NetAF/Assets/Characters/PlayableCharacter.cs index edeb8e0b..e8283ad4 100644 --- a/NetAF/Assets/Characters/PlayableCharacter.cs +++ b/NetAF/Assets/Characters/PlayableCharacter.cs @@ -75,10 +75,8 @@ public PlayableCharacter(Identifier identifier, IDescription description, bool c CanConverse = canConverse; Items = items ?? []; Commands = commands ?? []; - Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); - - if (examination != null) - Examination = examination; + Interaction = interaction ?? Assets.Interaction.NoChange; + Examination = examination ?? DefaultExamination; } #endregion diff --git a/NetAF/Assets/ExaminableObject.cs b/NetAF/Assets/ExaminableObject.cs index 01e42a6e..403e675f 100644 --- a/NetAF/Assets/ExaminableObject.cs +++ b/NetAF/Assets/ExaminableObject.cs @@ -14,12 +14,12 @@ namespace NetAF.Assets /// public class ExaminableObject : IExaminable { - #region Properties + #region StaticProperties /// - /// Get the callback handling all examination of this object. + /// Get a default examination for an ExaminableObject. /// - public ExaminationCallback Examination { get; protected set; } = request => + public static ExaminationCallback DefaultExamination => request => { StringBuilder description = new(); @@ -60,6 +60,15 @@ public class ExaminableObject : IExaminable #endregion + #region Properties + + /// + /// Get the callback handling all examination of this object. + /// + public ExaminationCallback Examination { get; protected set; } + + #endregion + #region Implementation of IExaminable /// @@ -87,7 +96,7 @@ public class ExaminableObject : IExaminable /// /// The scene this object is being examined from. /// The examination. - public virtual Examination Examine(ExaminationScene scene) + public Examination Examine(ExaminationScene scene) { return Examination(new(this, scene)); } diff --git a/NetAF/Assets/Interaction.cs b/NetAF/Assets/Interaction.cs index d6ae1f0a..efb33e14 100644 --- a/NetAF/Assets/Interaction.cs +++ b/NetAF/Assets/Interaction.cs @@ -7,6 +7,15 @@ namespace NetAF.Assets /// public sealed class Interaction { + #region StaticProperties + + /// + /// Get a default interaction for no change. + /// + public static InteractionCallback NoChange => i => new(InteractionResult.NoChange, i); + + #endregion + #region Properties /// diff --git a/NetAF/Assets/Item.cs b/NetAF/Assets/Item.cs index b0c3d042..9fae4e07 100644 --- a/NetAF/Assets/Item.cs +++ b/NetAF/Assets/Item.cs @@ -53,10 +53,8 @@ public Item(Identifier identifier, IDescription description, bool isTakeable = f Description = description; IsTakeable = isTakeable; Commands = commands ?? []; - Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); - - if (examination != null) - Examination = examination; + Interaction = interaction ?? Assets.Interaction.NoChange; + Examination = examination ?? DefaultExamination; } #endregion diff --git a/NetAF/Assets/Locations/Exit.cs b/NetAF/Assets/Locations/Exit.cs index 5476911e..26fd218b 100644 --- a/NetAF/Assets/Locations/Exit.cs +++ b/NetAF/Assets/Locations/Exit.cs @@ -47,10 +47,8 @@ public Exit(Direction direction, bool isLocked = false, Identifier identifier = Description = description ?? GenerateDescription(); IsLocked = isLocked; Commands = commands ?? []; - Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); - - if (examination != null) - Examination = examination; + Interaction = interaction ?? Assets.Interaction.NoChange; + Examination = examination ?? DefaultExamination; } #endregion diff --git a/NetAF/Assets/Locations/Overworld.cs b/NetAF/Assets/Locations/Overworld.cs index 56efe9c6..7213563f 100644 --- a/NetAF/Assets/Locations/Overworld.cs +++ b/NetAF/Assets/Locations/Overworld.cs @@ -12,6 +12,15 @@ namespace NetAF.Assets.Locations /// public sealed class Overworld : ExaminableObject, IRestoreFromObjectSerialization { + #region StaticProperties + + /// + /// Get the default examination for an Overworld. + /// + public static ExaminationCallback DefaultOverworldExamination => ExamineThis; + + #endregion + #region Fields private Region currentRegion; @@ -61,9 +70,7 @@ public Overworld(Identifier identifier, IDescription description, CustomCommand[ Identifier = identifier; Description = description; Commands = commands ?? []; - - if (examination != null) - Examination = examination; + Examination = examination ?? DefaultOverworldExamination; } #endregion @@ -124,16 +131,19 @@ public bool Move(Region region) #endregion - #region Overrides of ExaminableObject + #region StaticMethods /// - /// Examine this object. + /// Examine this Overworld. /// - /// The scene this object is being examined from. + /// The examination request. /// The examination. - public override Examination Examine(ExaminationScene scene) + private static Examination ExamineThis(ExaminationRequest request) { - return new(Description.GetDescription()); + if (request.Examinable is not Overworld overworld) + return DefaultExamination(request); + + return new(overworld.Description.GetDescription()); } #endregion diff --git a/NetAF/Assets/Locations/Region.cs b/NetAF/Assets/Locations/Region.cs index be6780fd..5a20b8f3 100644 --- a/NetAF/Assets/Locations/Region.cs +++ b/NetAF/Assets/Locations/Region.cs @@ -14,6 +14,15 @@ namespace NetAF.Assets.Locations /// public sealed class Region : ExaminableObject, IRestoreFromObjectSerialization { + #region StaticProperties + + /// + /// Get the default examination for a Region. + /// + public static ExaminationCallback DefaultRegionExamination => ExamineThis; + + #endregion + #region Fields private Room startRoom; @@ -74,9 +83,7 @@ public Region(Identifier identifier, IDescription description, CustomCommand[] c Identifier = identifier; Description = description; Commands = commands ?? []; - - if (examination != null) - Examination = examination; + Examination = examination ?? DefaultRegionExamination; } #endregion @@ -299,6 +306,19 @@ public bool JumpToRoom(Point3D location) #region StaticMethods + /// + /// Examine this Region. + /// + /// The examination request. + /// The examination. + private static Examination ExamineThis(ExaminationRequest request) + { + if (request.Examinable is not Region region) + return DefaultExamination(request); + + return new(region.Identifier + ": " + region.Description.GetDescription()); + } + /// /// Get the next position given a current position. /// @@ -334,20 +354,6 @@ internal static void NextPosition(Point3D current, Direction direction, out Poin #endregion - #region Overrides of ExaminableObject - - /// - /// Examine this object. - /// - /// The scene this object is being examined from. - /// The examination. - public override Examination Examine(ExaminationScene scene) - { - return new(Identifier + ": " + Description.GetDescription()); - } - - #endregion - #region Implementation of IRestoreFromObjectSerialization /// diff --git a/NetAF/Assets/Locations/Room.cs b/NetAF/Assets/Locations/Room.cs index dd714efd..90334f1f 100644 --- a/NetAF/Assets/Locations/Room.cs +++ b/NetAF/Assets/Locations/Room.cs @@ -15,6 +15,15 @@ namespace NetAF.Assets.Locations /// public sealed class Room : ExaminableObject, IInteractWithItem, IItemContainer, IRestoreFromObjectSerialization { + #region StaticProperties + + /// + /// Get the default examination for a Room. + /// + public static ExaminationCallback DefaultRoomExamination => ExamineThis; + + #endregion + #region Properties /// @@ -125,10 +134,8 @@ public Room(Identifier identifier, IDescription description, IDescription introd Exits = exits ?? []; Items = items ?? []; Commands = commands ?? []; - Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); - - if (examination != null) - Examination = examination; + Interaction = interaction ?? Assets.Interaction.NoChange; + Examination = examination ?? DefaultRoomExamination; } #endregion @@ -204,28 +211,6 @@ private Interaction InteractWithItem(Item item) return Interaction.Invoke(item); } - /// - /// Handle examination this Room. - /// - /// The scene this object is being examined from. - /// The examination. - public override Examination Examine(ExaminationScene scene) - { - if (!Array.Exists(Items, i => i.IsPlayerVisible)) - return new("There is nothing to examine."); - - if (Items.Count(i => i.IsPlayerVisible) == 1) - { - var singularItem = Items.Where(i => i.IsPlayerVisible).ToArray()[0]; - return new($"There {(singularItem.Identifier.Name.IsPlural() ? "are" : "is")} {singularItem.Identifier.Name.GetObjectifier()} {singularItem.Identifier}"); - } - - var items = Items.Cast().ToArray(); - var sentence = StringUtilities.ConstructExaminablesAsSentence(items); - var firstItemName = sentence.Substring(0, sentence.Contains(", ") ? sentence.IndexOf(", ", StringComparison.Ordinal) : sentence.IndexOf(" and ", StringComparison.Ordinal)); - return new($"There {(firstItemName.IsPlural() ? "are" : "is")} {sentence.StartWithLower()}"); - } - /// /// Get if this room has a visible locked exit in a specified direction. /// @@ -459,6 +444,35 @@ public void MovedInto(Direction fromDirection) #endregion + #region StaticMethods + + /// + /// Examine this Room. + /// + /// The examination request. + /// The examination. + private static Examination ExamineThis(ExaminationRequest request) + { + if (request.Examinable is not Room room) + return DefaultExamination(request); + + if (!Array.Exists(room.Items, i => i.IsPlayerVisible)) + return new("There is nothing to examine."); + + if (room.Items.Count(i => i.IsPlayerVisible) == 1) + { + Item singularItem = room.Items.Where(i => i.IsPlayerVisible).ToArray()[0]; + return new($"There {(singularItem.Identifier.Name.IsPlural() ? "are" : "is")} {singularItem.Identifier.Name.GetObjectifier()} {singularItem.Identifier}"); + } + + var items = room.Items.Cast().ToArray(); + var sentence = StringUtilities.ConstructExaminablesAsSentence(items); + var firstItemName = sentence.Substring(0, sentence.Contains(", ") ? sentence.IndexOf(", ", StringComparison.Ordinal) : sentence.IndexOf(" and ", StringComparison.Ordinal)); + return new($"There {(firstItemName.IsPlural() ? "are" : "is")} {sentence.StartWithLower()}"); + } + + #endregion + #region IInteractWithItem Members /// diff --git a/NetAF/Rendering/Console/FrameBuilders/ConsoleSceneFrameBuilder.cs b/NetAF/Rendering/Console/FrameBuilders/ConsoleSceneFrameBuilder.cs index 6e1409f3..0cde92c9 100644 --- a/NetAF/Rendering/Console/FrameBuilders/ConsoleSceneFrameBuilder.cs +++ b/NetAF/Rendering/Console/FrameBuilders/ConsoleSceneFrameBuilder.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using NetAF.Assets; using NetAF.Assets.Characters; using NetAF.Assets.Locations; @@ -87,10 +86,15 @@ public IFrame Build(Room room, ViewPoint viewPoint, PlayableCharacter player, Co var extendedDescription = string.Empty; - if (room.Items.Any()) - extendedDescription = extendedDescription.AddSentence(room.Examine(new(player, room)).Description.EnsureFinishedSentence()); + if (room.Items.Length != 0) + { + var roomExamination = Room.DefaultRoomExamination.Invoke(new ExaminationRequest(room, new ExaminationScene(player, room))); + extendedDescription = extendedDescription.AddSentence(roomExamination.Description.EnsureFinishedSentence()); + } else + { extendedDescription = extendedDescription.AddSentence("There are no items in this area."); + } extendedDescription = extendedDescription.AddSentence(SceneHelper.CreateNPCString(room)); diff --git a/NetAF/Serialization/Assets/AttributeAndValueSerialization.cs b/NetAF/Serialization/Assets/AttributeAndValueSerialization.cs new file mode 100644 index 00000000..772a23dc --- /dev/null +++ b/NetAF/Serialization/Assets/AttributeAndValueSerialization.cs @@ -0,0 +1,74 @@ +using NetAF.Assets.Attributes; +using System.Collections.Generic; + +namespace NetAF.Serialization.Assets +{ + /// + /// Represents a serialization of a KeyValuePair where key is an Attribute and value is a int. + /// + public sealed class AttributeAndValueSerialization : IObjectSerialization> + { + #region Properties + + /// + /// Get or set the name. + /// + public string Name { get; set; } + + /// + /// Get or set the description. + /// + public string Description { get; set; } + + /// + /// Get or set the minimum. + /// + public int Minimum { get; set; } + + /// + /// Get or set the maximum. + /// + public int Maximum { get; set; } + + /// + /// Get or set the value. + /// + public int Value { get; set; } + + #endregion + + #region StaticMethods + + /// + /// Create a new serialization from a KeyValuePair where key is an Attribute and value is a int. + /// + /// The KeyValuePair to create the serialization from. + /// The serialization. + public static AttributeAndValueSerialization FromAttributeAndValue(KeyValuePair attributeAndValue) + { + return new() + { + Name = attributeAndValue.Key.Name, + Description = attributeAndValue.Key.Description, + Minimum = attributeAndValue.Key.Minimum, + Maximum = attributeAndValue.Key.Maximum, + Value = attributeAndValue.Value + }; + } + + #endregion + + #region Implementation of IObjectSerialization> + + /// + /// Restore an instance from this serialization. + /// + /// The KeyValuePair to restore. + public void Restore(KeyValuePair attributeAndValue) + { + // cannot restore as readonly + } + + #endregion + } +} diff --git a/NetAF/Serialization/Assets/AttributeManagerSerialization.cs b/NetAF/Serialization/Assets/AttributeManagerSerialization.cs index eec87f9c..252bf9ab 100644 --- a/NetAF/Serialization/Assets/AttributeManagerSerialization.cs +++ b/NetAF/Serialization/Assets/AttributeManagerSerialization.cs @@ -14,7 +14,7 @@ public sealed class AttributeManagerSerialization : IObjectSerialization /// Get or set the values. /// - public Dictionary Values { get; set; } + public List Values { get; set; } #endregion @@ -27,9 +27,14 @@ public sealed class AttributeManagerSerialization : IObjectSerializationThe serialization. public static AttributeManagerSerialization FromAttributeManager(AttributeManager attributeManager) { + List> values = []; + + foreach (var pair in attributeManager?.GetAsDictionary() ?? []) + values.Add(pair); + return new() { - Values = attributeManager?.GetAsDictionary()?.ToDictionary(x => AttributeSerialization.FromAttribute(x.Key), x => x.Value) ?? [] + Values = values?.Select(AttributeAndValueSerialization.FromAttributeAndValue).ToList() ?? [] }; } diff --git a/NetAF/Serialization/Assets/AttributeSerialization.cs b/NetAF/Serialization/Assets/AttributeSerialization.cs deleted file mode 100644 index 8c26a10c..00000000 --- a/NetAF/Serialization/Assets/AttributeSerialization.cs +++ /dev/null @@ -1,67 +0,0 @@ -using NetAF.Assets.Attributes; - -namespace NetAF.Serialization.Assets -{ - /// - /// Represents a serialization of an Attribute. - /// - public sealed class AttributeSerialization : IObjectSerialization - { - #region Properties - - /// - /// Get or set the name. - /// - public string Name { get; set; } - - /// - /// Get or set the description. - /// - public string Description { get; set; } - - /// - /// Get or set the minimum. - /// - public int Minimum { get; set; } - - /// - /// Get or set the maximum. - /// - public int Maximum { get; set; } - - #endregion - - #region StaticMethods - - /// - /// Create a new serialization from an Attribute. - /// - /// The Attribute to create the serialization from. - /// The serialization. - public static AttributeSerialization FromAttribute(Attribute attribute) - { - return new() - { - Name = attribute.Name, - Description = attribute.Description, - Minimum = attribute.Minimum, - Maximum = attribute.Maximum - }; - } - - #endregion - - #region Implementation of IObjectSerialization - - /// - /// Restore an instance from this serialization. - /// - /// The attribute to restore. - public void Restore(Attribute attribute) - { - attribute.RestoreFrom(this); - } - - #endregion - } -} From 0f0a3b3fd79707567a22f7b4cc8216e307c9d569 Mon Sep 17 00:00:00 2001 From: ben_singer Date: Thu, 28 Nov 2024 16:41:09 +0000 Subject: [PATCH 2/3] Tidying --- NetAF/Assets/Characters/NonPlayableCharacter.cs | 2 +- NetAF/Assets/Characters/PlayableCharacter.cs | 2 +- NetAF/Assets/Interaction.cs | 9 --------- NetAF/Assets/Item.cs | 2 +- NetAF/Assets/Locations/Exit.cs | 2 +- NetAF/Assets/Locations/Room.cs | 2 +- 6 files changed, 5 insertions(+), 14 deletions(-) diff --git a/NetAF/Assets/Characters/NonPlayableCharacter.cs b/NetAF/Assets/Characters/NonPlayableCharacter.cs index 14ac0318..45e8bcad 100644 --- a/NetAF/Assets/Characters/NonPlayableCharacter.cs +++ b/NetAF/Assets/Characters/NonPlayableCharacter.cs @@ -40,7 +40,7 @@ public NonPlayableCharacter(Identifier identifier, IDescription description, Con Description = description; Conversation = conversation; Commands = commands ?? []; - Interaction = interaction ?? Assets.Interaction.NoChange; + Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); Examination = examination ?? DefaultExamination; } diff --git a/NetAF/Assets/Characters/PlayableCharacter.cs b/NetAF/Assets/Characters/PlayableCharacter.cs index e8283ad4..3a989660 100644 --- a/NetAF/Assets/Characters/PlayableCharacter.cs +++ b/NetAF/Assets/Characters/PlayableCharacter.cs @@ -75,7 +75,7 @@ public PlayableCharacter(Identifier identifier, IDescription description, bool c CanConverse = canConverse; Items = items ?? []; Commands = commands ?? []; - Interaction = interaction ?? Assets.Interaction.NoChange; + Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); Examination = examination ?? DefaultExamination; } diff --git a/NetAF/Assets/Interaction.cs b/NetAF/Assets/Interaction.cs index efb33e14..d6ae1f0a 100644 --- a/NetAF/Assets/Interaction.cs +++ b/NetAF/Assets/Interaction.cs @@ -7,15 +7,6 @@ namespace NetAF.Assets /// public sealed class Interaction { - #region StaticProperties - - /// - /// Get a default interaction for no change. - /// - public static InteractionCallback NoChange => i => new(InteractionResult.NoChange, i); - - #endregion - #region Properties /// diff --git a/NetAF/Assets/Item.cs b/NetAF/Assets/Item.cs index 9fae4e07..d1429bdb 100644 --- a/NetAF/Assets/Item.cs +++ b/NetAF/Assets/Item.cs @@ -53,7 +53,7 @@ public Item(Identifier identifier, IDescription description, bool isTakeable = f Description = description; IsTakeable = isTakeable; Commands = commands ?? []; - Interaction = interaction ?? Assets.Interaction.NoChange; + Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); Examination = examination ?? DefaultExamination; } diff --git a/NetAF/Assets/Locations/Exit.cs b/NetAF/Assets/Locations/Exit.cs index 26fd218b..b22480d4 100644 --- a/NetAF/Assets/Locations/Exit.cs +++ b/NetAF/Assets/Locations/Exit.cs @@ -47,7 +47,7 @@ public Exit(Direction direction, bool isLocked = false, Identifier identifier = Description = description ?? GenerateDescription(); IsLocked = isLocked; Commands = commands ?? []; - Interaction = interaction ?? Assets.Interaction.NoChange; + Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); Examination = examination ?? DefaultExamination; } diff --git a/NetAF/Assets/Locations/Room.cs b/NetAF/Assets/Locations/Room.cs index 90334f1f..9e0dd2d4 100644 --- a/NetAF/Assets/Locations/Room.cs +++ b/NetAF/Assets/Locations/Room.cs @@ -134,7 +134,7 @@ public Room(Identifier identifier, IDescription description, IDescription introd Exits = exits ?? []; Items = items ?? []; Commands = commands ?? []; - Interaction = interaction ?? Assets.Interaction.NoChange; + Interaction = interaction ?? (i => new(InteractionResult.NoChange, i)); Examination = examination ?? DefaultRoomExamination; } From 176e033e132e17d829294109fc436a57ccf5b996 Mon Sep 17 00:00:00 2001 From: ben_singer Date: Thu, 28 Nov 2024 16:52:26 +0000 Subject: [PATCH 3/3] Improvded coverage --- .../Assets/Locations/Overworld_Tests.cs | 22 ++++++++++ NetAF.Tests/Assets/Locations/Region_Tests.cs | 22 +++++++++- NetAF.Tests/Assets/Locations/Room_Tests.cs | 40 +++++++++++++++++++ ...> AttributeAndValueSerialization_Tests.cs} | 10 +++++ 4 files changed, 93 insertions(+), 1 deletion(-) rename NetAF.Tests/Serialization/Assets/{AttributeSerialization_Tests.cs => AttributeAndValueSerialization_Tests.cs} (85%) diff --git a/NetAF.Tests/Assets/Locations/Overworld_Tests.cs b/NetAF.Tests/Assets/Locations/Overworld_Tests.cs index fc1da3f0..3f7e0493 100644 --- a/NetAF.Tests/Assets/Locations/Overworld_Tests.cs +++ b/NetAF.Tests/Assets/Locations/Overworld_Tests.cs @@ -1,5 +1,7 @@ using NetAF.Assets.Locations; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NetAF.Assets.Characters; +using NetAF.Assets; namespace NetAF.Tests.Assets.Locations { @@ -72,5 +74,25 @@ public void GivenRegionIsNotPresent_WhenMoveRegion_ThenReturnFalse() Assert.IsFalse(result); } + + [TestMethod] + public void GivenNotOverworld_WhenExamine_ThenReturnNonEmptyString() + { + var overworld = new Overworld(string.Empty, "An overworld"); + + var result = overworld.Examination(new ExaminationRequest(new PlayableCharacter("a", "b"), new ExaminationScene(null, new Room(string.Empty, string.Empty)))); + + Assert.IsTrue(result.Description.Length > 0); + } + + [TestMethod] + public void GivenOverworld_WhenExamine_ThenReturnNonEmptyString() + { + var overworld = new Overworld(string.Empty, "An overworld"); + + var result = overworld.Examination(new ExaminationRequest(overworld, new ExaminationScene(null, new Room(string.Empty, string.Empty)))); + + Assert.IsTrue(result.Description.Length > 0); + } } } diff --git a/NetAF.Tests/Assets/Locations/Region_Tests.cs b/NetAF.Tests/Assets/Locations/Region_Tests.cs index 4ddb0ae3..b0151d7b 100644 --- a/NetAF.Tests/Assets/Locations/Region_Tests.cs +++ b/NetAF.Tests/Assets/Locations/Region_Tests.cs @@ -2,6 +2,7 @@ using NetAF.Assets.Locations; using Microsoft.VisualStudio.TestTools.UnitTesting; using NetAF.Commands; +using NetAF.Assets.Characters; namespace NetAF.Tests.Assets.Locations { @@ -491,11 +492,30 @@ public void GivenCurrentRoomNullAndNoStartRoomAssigned_WhenEnter_ThenCurrentRoom public void GivenCurrentRoomNullAndNoRooms_WhenEnter_ThenCurrentRoomIsNull() { var region = new Region(string.Empty, string.Empty); - var room = new Room(string.Empty, string.Empty); region.Enter(); Assert.IsNull(region.CurrentRoom); } + + [TestMethod] + public void GivenNotRegion_WhenExamine_ThenReturnNonEmptyString() + { + var region = new Region(string.Empty, "A region"); + + var result = region.Examination(new ExaminationRequest(new PlayableCharacter("a", "b"), new ExaminationScene(null, new Room(string.Empty, string.Empty)))); + + Assert.IsTrue(result.Description.Length > 0); + } + + [TestMethod] + public void GivenRegion_WhenExamine_ThenReturnNonEmptyString() + { + var region = new Region(string.Empty, "A region"); + + var result = region.Examination(new ExaminationRequest(region, new ExaminationScene(null, new Room(string.Empty, string.Empty)))); + + Assert.IsTrue(result.Description.Length > 0); + } } } diff --git a/NetAF.Tests/Assets/Locations/Room_Tests.cs b/NetAF.Tests/Assets/Locations/Room_Tests.cs index 3fbd6d07..73b63115 100644 --- a/NetAF.Tests/Assets/Locations/Room_Tests.cs +++ b/NetAF.Tests/Assets/Locations/Room_Tests.cs @@ -243,5 +243,45 @@ public void GivenInvalid_WhenFindInteractionTarget_ThenReturnFalse() Assert.IsFalse(result); } + + [TestMethod] + public void GivenNotRoom_WhenExamine_ThenReturnNonEmptyString() + { + var room = new Room(string.Empty, "A room"); + + var result = room.Examination(new ExaminationRequest(new PlayableCharacter("a", "b"), new ExaminationScene(null, room))); + + Assert.IsTrue(result.Description.Length > 0); + } + + [TestMethod] + public void GivenNoItems_WhenExamine_ThenReturnNonEmptyString() + { + var room = new Room(string.Empty, "A room"); + + var result = room.Examination(new ExaminationRequest(room, new ExaminationScene(null, room))); + + Assert.IsTrue(result.Description.Length > 0); + } + + [TestMethod] + public void Given1Item_WhenExamine_ThenReturnNonEmptyString() + { + var room = new Room(string.Empty, "A room", items: [new Item("a", "b")]); + + var result = room.Examination(new ExaminationRequest(room, new ExaminationScene(null, room))); + + Assert.IsTrue(result.Description.Length > 0); + } + + [TestMethod] + public void Given2Items_WhenExamine_ThenReturnNonEmptyString() + { + var room = new Room(string.Empty, "A room", items: [new Item("a", "b"), new Item("c", "d")]); + + var result = room.Examination(new ExaminationRequest(room, new ExaminationScene(null, room))); + + Assert.IsTrue(result.Description.Length > 0); + } } } diff --git a/NetAF.Tests/Serialization/Assets/AttributeSerialization_Tests.cs b/NetAF.Tests/Serialization/Assets/AttributeAndValueSerialization_Tests.cs similarity index 85% rename from NetAF.Tests/Serialization/Assets/AttributeSerialization_Tests.cs rename to NetAF.Tests/Serialization/Assets/AttributeAndValueSerialization_Tests.cs index 32d146d1..4be09d2c 100644 --- a/NetAF.Tests/Serialization/Assets/AttributeSerialization_Tests.cs +++ b/NetAF.Tests/Serialization/Assets/AttributeAndValueSerialization_Tests.cs @@ -56,5 +56,15 @@ public void GivenAttributeWhenValueIs3_WhenFromAttribute_ThenValueIs3() Assert.AreEqual(3, result.Value); } + + [TestMethod] + public void GivenRestore_ThenNoExceptionThrown() + { + Assertions.NoExceptionThrown(() => + { + AttributeAndValueSerialization value = new(); + value.Restore(new System.Collections.Generic.KeyValuePair()); + }); + } } }