├── ParquetClassLibrary ├── PublicAPI.Shipped.txt ├── ParquetIcon.ico ├── Properties │ ├── ParquetIcon.ico │ └── Parquet Logo Large.png ├── Beings │ ├── BeingStatus.cs │ ├── IMutableCritterModel.cs │ ├── IMutablePronounGroup.cs │ ├── IMutableBeingModel.cs │ ├── CritterModel.cs │ └── IMutableCharacterModel.cs ├── Parquets │ ├── ParquetStatus.cs │ ├── GatherEffect.cs │ ├── CollectEffect.cs │ ├── EntryType.cs │ ├── IMutableFloorModel.cs │ ├── IMutableCollectibleModel.cs │ ├── IMutableBlockModel.cs │ ├── IMutableFurnishingModel.cs │ ├── IMutableParquetModel.cs │ ├── FloorModel.cs │ └── CollectibleModel.cs ├── Scripts │ ├── RunState.cs │ ├── IMutableScriptModel.cs │ ├── IMutableInteractionModel.cs │ ├── Commands.cs │ └── ScriptModel.cs ├── IDeeplyCloneable.cs ├── Items │ ├── ModificationTool.cs │ ├── GatheringTool.cs │ ├── ItemType.cs │ ├── IMutableItemModel.cs │ └── InventoryConfiguration.cs ├── ILogger.cs ├── IntExtensions.cs ├── Crafts │ ├── IMutableCraftingRecipe.cs │ └── CraftConfiguration.cs ├── IReadOnlyGrid.cs ├── IVisibleData.cs ├── Rooms │ ├── ReadOnlyRoomCollectionExtensions.cs │ ├── IMutableRoomRecipe.cs │ └── RoomConfiguration.cs ├── IMutableModelCollection.cs ├── Biomes │ ├── IMutableBiomeRecipe.cs │ └── BiomeConfiguration.cs ├── IGrid.cs ├── Games │ └── IMutableGameModel.cs ├── Regions │ ├── IMutableRegionModel.cs │ └── ChunkTopography.cs ├── LoggerDefault.cs ├── LibraryState.cs ├── LogLevel.cs ├── GlobalSuppressions.cs ├── AssemblyInfo.cs ├── IReadOnlyListOfTagsExtensions.cs ├── IReadOnlyCollectionOfTagsExtensions.cs ├── IMutableModel.cs ├── IDictionaryExtensions.cs ├── Delimiters.cs ├── EmptyTolerantEnumConverter.cs ├── SeriesConverter.cs └── ParquetClassLibrary.csproj ├── ExampleData ├── InteractionStatuses.csv ├── ScriptStatuses.csv ├── InventoryConfiguration.csv ├── GameStatuses.csv ├── CraftConfiguration.csv ├── RoomConfiguration.csv ├── BiomeConfiguration.csv ├── CharacterStatuses.csv ├── ScriptModels.csv ├── CraftingRecipes.csv ├── GameModels.csv ├── PronounGroups.csv ├── CritterModels.csv ├── RoomRecipes.csv ├── InteractionModels.csv ├── RegionModels.csv ├── ItemModels.csv ├── FurnishingModels.csv ├── FloorModels.csv ├── CharacterModels.csv ├── CollectibleModels.csv ├── BlockModels.csv └── BiomeRecipes.csv ├── ParquetRunner ├── ParquetIcon.ico ├── AssemblyInfoRunner.cs └── ParquetRunner.csproj ├── Documentation ├── Algorithms │ ├── Extrema.png │ ├── Scanning.png │ ├── 4-Connected.png │ ├── Just-Outside.png │ ├── Valid_Rooms.png │ └── Invalid_Rooms.png ├── Parquet_Project_Logo.png ├── Home.md ├── How_Elevation_Works_In_Parquet.md ├── 3-How_Parquets_Work.md ├── Lessons_Learned_Living_Document.md ├── 1-Terminology.md ├── 2-How_Parquet_Handles_Game_Objects.md └── Parquet_CSharp_Style_Guide.md ├── ParquetUnitTests ├── ParquetIcon.ico ├── Regions │ ├── ChunkDetailUnitTest.cs │ ├── RegionStatusUnitTestExtensions.cs │ └── RegionModelUnitTest.cs ├── RecipeElementUnitTest.cs ├── GlobalSuppressions.cs ├── Parquets │ ├── FloorModelUnitTest.cs │ ├── CollectibleModelUnitTest.cs │ ├── ParquetPackUnitTest.cs │ ├── BlockModelUnitTest.cs │ └── FurnishingModelUnitTest.cs ├── AllUnitTest.cs ├── Beings │ ├── CharacterModelUnitTest.cs │ └── CritterModelUnitTest.cs ├── AssemblyInfoUnitTest.cs ├── Crafts │ └── CraftingRecipeUnitTest.cs ├── ITypeConverterTest.cs ├── Point2DUnitTest.cs ├── Items │ ├── InventorySlotUnitTest.cs │ ├── ItemModelUnitTest.cs │ └── InventoryCollectionUnitTest.cs ├── RangeUnitTest.cs ├── Rooms │ └── RoomRecipeUnitTest.cs └── ParquetUnitTests.csproj ├── CONTRIBUTING.md ├── LICENSE.txt ├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── README.md └── Parquet.sln /ParquetClassLibrary/PublicAPI.Shipped.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ExampleData/InteractionStatuses.csv: -------------------------------------------------------------------------------- 1 | Key,State,StepCounter 2 | -------------------------------------------------------------------------------- /ExampleData/ScriptStatuses.csv: -------------------------------------------------------------------------------- 1 | Key,State,ProgramCounter 2 | -------------------------------------------------------------------------------- /ExampleData/InventoryConfiguration.csv: -------------------------------------------------------------------------------- 1 | DefaultCapacity 2 | 16 3 | -------------------------------------------------------------------------------- /ExampleData/GameStatuses.csv: -------------------------------------------------------------------------------- 1 | Key,PlayerCharacterID,CurrentScriptID 2 | -------------------------------------------------------------------------------- /ExampleData/CraftConfiguration.csv: -------------------------------------------------------------------------------- 1 | IngredientCount,ProductCount 2 | 1–5,1–5 3 | -------------------------------------------------------------------------------- /ExampleData/RoomConfiguration.csv: -------------------------------------------------------------------------------- 1 | MinWalkableSpaces,MaxWalkableSpaces 2 | 4,121 3 | -------------------------------------------------------------------------------- /ExampleData/BiomeConfiguration.csv: -------------------------------------------------------------------------------- 1 | LandThresholdFactor,LiquidThresholdFactor,RoomThresholdFactor 2 | 1.25,0.25,0.67 3 | -------------------------------------------------------------------------------- /ExampleData/CharacterStatuses.csv: -------------------------------------------------------------------------------- 1 | Key,Position,SpawnAt,RoomAssignment,CurrentBehaviorID,BiomeTimeRemaining,Inventory 2 | -------------------------------------------------------------------------------- /ParquetRunner/ParquetIcon.ico: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9c9b243e39c6d4933c94db387904a8f793158d74c12506ed7923223bac432145 3 | size 16798 4 | -------------------------------------------------------------------------------- /Documentation/Algorithms/Extrema.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0fbcce5f8ec3e909a3b4fcdc1dca4c1aff0344550852c909b346ef62f60d73fd 3 | size 1822 4 | -------------------------------------------------------------------------------- /Documentation/Algorithms/Scanning.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d240c923a616637e947c512d4f28d114ac53c1ff2c08f583e4772f27ddadd19c 3 | size 2445 4 | -------------------------------------------------------------------------------- /ParquetClassLibrary/ParquetIcon.ico: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9c9b243e39c6d4933c94db387904a8f793158d74c12506ed7923223bac432145 3 | size 16798 4 | -------------------------------------------------------------------------------- /ParquetUnitTests/ParquetIcon.ico: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9c9b243e39c6d4933c94db387904a8f793158d74c12506ed7923223bac432145 3 | size 16798 4 | -------------------------------------------------------------------------------- /Documentation/Algorithms/4-Connected.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:37999f968c372a70580b128aa9e7cf8035aa8ea013177739650555c501e27482 3 | size 2723 4 | -------------------------------------------------------------------------------- /Documentation/Algorithms/Just-Outside.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d27ba0508f266897c9cd2cc049331aaa6312f742ba96d82b19962c927ba6a9ab 3 | size 3487 4 | -------------------------------------------------------------------------------- /Documentation/Algorithms/Valid_Rooms.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e5c4fad30180a752ca4a4714933410b6a546d322bede47b82504088ed84b8e42 3 | size 17436 4 | -------------------------------------------------------------------------------- /Documentation/Parquet_Project_Logo.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f0ee6cfa83116050aaaaf087c4ffcd8e64ac3a2161d6bdfb7c281e4bc7d6a1f4 3 | size 3800 4 | -------------------------------------------------------------------------------- /Documentation/Algorithms/Invalid_Rooms.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a4ccabdf2cd180761b64def2de4a856ef21059e6aedb68bb1316abf667f13676 3 | size 20033 4 | -------------------------------------------------------------------------------- /ExampleData/ScriptModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,Nodes 2 | -120000,Script Test,Test,Test,Test-Tag, 3 | 120000,Dotdot Startup,,,,C·MISSY·DOTDOT❟S·DOTDOT·Howdy!❟=··Greeting 4 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Properties/ParquetIcon.ico: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9c9b243e39c6d4933c94db387904a8f793158d74c12506ed7923223bac432145 3 | size 16798 4 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Properties/Parquet Logo Large.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f0ee6cfa83116050aaaaf087c4ffcd8e64ac3a2161d6bdfb7c281e4bc7d6a1f4 3 | size 3800 4 | -------------------------------------------------------------------------------- /ExampleData/CraftingRecipes.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,Products,Ingredients 2 | -80000,Crafting Recipe Test,Test,Test,,1·Test Tag,1·Test Tag 3 | 80000,Ultra-Bread,Delicious and ultra-versatile.,,loaf,1·Bread,1·Flour 4 | -------------------------------------------------------------------------------- /ExampleData/GameModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,IsEpisode,EpisodeTitle,EpisodeNumber,PlayerCharacterID,FirstScriptID 2 | -1,Game Test,Test,Test,,False,,0,None,None 3 | 1,First Game,,This tests the graphic loading.,,False,,0,60001,120000 4 | -------------------------------------------------------------------------------- /ExampleData/PronounGroups.csv: -------------------------------------------------------------------------------- 1 | Subjective,Objective,Determiner,Possessive,Reflexive 2 | e,em,eir,eirs,emself 3 | fae,faer,faer,faers,faerself 4 | she,her,her,hers,herself 5 | they,them,their,theirs,themself 6 | xe,xem,xyr,xyrs,xemself 7 | ze,zer,zir,zirs,zirself 8 | -------------------------------------------------------------------------------- /ExampleData/CritterModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,NativeBiomeID,PrimaryBehaviorID,AvoidsIDs,SeeksIDs 2 | -50000,Test Critter,Test,Test,,None,120000,, 3 | 50000,Ladybug,Dainty and red!,,,70004,120005,, 4 | 50001,Blue Milkweed Beetle,Dark and dutiful!,,,70003,120001,, 5 | -------------------------------------------------------------------------------- /ExampleData/RoomRecipes.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,MinimumWalkableSpaces,OptionallyRequiredFurnishings,OptionallyRequiredWalkableFloors,OptionallyRequiredPerimeterBlocks 2 | -90000,Room Recipe Test,Test,Test,,20,1·Test Tag,1·Test Tag,1·Test Tag 3 | 90000,Room,A basic space.,n/a,,4,,, 4 | -------------------------------------------------------------------------------- /ParquetRunner/AssemblyInfoRunner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | // Make no promises to maintain public services. 5 | [assembly: ComVisible(false)] 6 | 7 | // Runner is an API consumer, not an API provider. 8 | [assembly: CLSCompliant(false)] 9 | 10 | -------------------------------------------------------------------------------- /ExampleData/InteractionModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,PrerequisitesIDs,StepsIDs,OutcomesIDs 2 | -130000,Interaction Test,Test,Test,,,, 3 | 130000,Scarf Talk,prompt for getting scarves,"It's mighty cold out; take this with you, dearie.",,,, 4 | 130001,Find A Scarf,The quest to find a scarf!,,,,, 5 | -------------------------------------------------------------------------------- /ExampleData/RegionModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,BackgroundColor,RegionToTheNorth,RegionToTheEast,RegionToTheSouth,RegionToTheWest,RegionAbove,RegionBelow 2 | -110000,Map Test,Test,Test,,#000000,-110000,-110000,-110000,-110000,-110000,-110000 3 | 110000,Simple Map,,,,#FFFFFF,None,None,None,None,None,None 4 | -------------------------------------------------------------------------------- /ExampleData/ItemModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,Subtype,Worth,Rarity,StackMax,EffectWhileHeldID,EffectWhenUsedID,ParquetID 2 | -140000,Item Test,Test,Test,,Consumable,10,3,99,None,-120000,None 3 | 140000,Red Apple,Red and tasty!,,,Consumable,2,1,30,120000,120001,None 4 | 140001,Green Apple,Green for baking!,,Frizzle's Fave,Consumable,2,1,30,None,None,20001 5 | -------------------------------------------------------------------------------- /ExampleData/FurnishingModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,ItemID,AddsToBiome,AddsToRoom,IsWalkable,Entry,IsEnclosing,IsFlammable,IsOpenable 2 | -30000,3 Furnishing Test Parquet,Test,Test,,-140003,,,True,Up,True,False,False 3 | 30000,Chair,,,,140003,,Usable Surface,True,None,False,True,False 4 | 30001,Table,,,,140004,,Seating,False,None,True,True,False 5 | 30002,Wooden Door,,,,140005,,,True,Room,True,True,True 6 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Beings/BeingStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Beings 2 | { 3 | /// 4 | /// Tracks the status of a during play. 5 | /// Instances of this class are mutable during play. 6 | /// 7 | public abstract class BeingStatus : Status 8 | where T : BeingModel 9 | { 10 | // Currently, everything needed for tracking BeingModels is provided by Status. 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ExampleData/FloorModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,ItemID,AddsToBiome,AddsToRoom,ModTool,TrenchName 2 | -10000,1 Floor Test Parquet,Test,Test,,-140000,,,None,dark hole 3 | 10000,grass,,,,None,,,Shovel,dirty pit 4 | 10001,weeds,,,,140001,Swampy,,Shovel,muddy pit 5 | 10002,sand,,,,None,Deserted,,Shovel,sandy pit 6 | 10003,snow,,,,None,Frozen,,Shovel,snowy pit 7 | 10004,stone floor,,,,None,,Stone,Hammer,stony trench 8 | 10005,red brick,,,,None,,Brick,Hammer,red brick channel 9 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/ParquetStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Parquets 2 | { 3 | /// 4 | /// Tracks the status of a during play. 5 | /// Instances of this class are mutable during play. 6 | /// 7 | public abstract class ParquetStatus : Status 8 | where T : ParquetModel 9 | { 10 | // Currently, everything needed for tracking ParquetModels is provided by Status. 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ExampleData/CharacterModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,NativeBiomeID,PrimaryBehaviorID,AvoidsIDs,SeeksIDs,PronounKey,StoryCharacterID,StartingLocation,StartingQuestIDs,StartingDialogueID,StartingInventory 2 | -60000,Test§NPC,Test,Test,Test-Tag,None,120000,,,xe/xem,TEST_NPC,None·0–0,,None, 3 | 60000,Missy§Titan,Sharp and sweet.,Come on in!,,70004,120001,,,she/her,MISSY_TITAN,None·0–0,130000,None, 4 | 60001,DotDot§Dot,Loves to understand how things work.,,,70010,120002,,,they/them,DOTDOT,130001·2–2,,None,140000·5❟140001·2 5 | -------------------------------------------------------------------------------- /ParquetUnitTests/Regions/ChunkDetailUnitTest.cs: -------------------------------------------------------------------------------- 1 | using Parquet.Regions; 2 | using Xunit; 3 | 4 | namespace ParquetUnitTests.Maps 5 | { 6 | /// 7 | /// Unit tests . 8 | /// 9 | public class ChunkDetailUnitTest 10 | { 11 | [Fact] 12 | internal void DefaultChunkTypeIsEmptyTest() 13 | { 14 | var defaultChunk = new ChunkDetail(); 15 | 16 | Assert.Equal(ChunkDetail.None, defaultChunk); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ExampleData/CollectibleModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,ItemID,AddsToBiome,AddsToRoom,CollectionEffect,EffectAmount 2 | -40000,4 Collectible Test Parquet,Test,Test,Test-Tag,-140001,,,None,0 3 | 40000,Flower,,,,140000,,,Item,0 4 | 40001,Pine Cone,,,,140001,Forested,,Item,0 5 | 40002,Seeds,,,,140002,Swampy,,Item,0 6 | 40003,Bones,,,,140003,Deserted,Grave,Item,0 7 | 40004,Ice Crystal,,,,140004,Frozen,,Item,0 8 | 40005,Copper Ore,,,,140005,,,Item,0 9 | 40006,Star,,,,None,,,BiomeTime,10 10 | 40007,Water Bottle,,,,None,,,BiomeTime,10 11 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Scripts/RunState.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Scripts 2 | { 3 | /// 4 | /// Status of a in an . 5 | /// 6 | public enum RunState 7 | { 8 | /// This script has not yet begun execution. 9 | Unstarted = 0, 10 | /// This script is currently executing. 11 | InProgress, 12 | /// This script is completed execution. 13 | Completed, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/GatherEffect.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Parquets 2 | { 3 | /// 4 | /// IDs for effects that can happen when a character gathers a . 5 | /// 6 | public enum GatheringEffect 7 | { 8 | /// Gathering this parquet has no effect. 9 | None = 0, 10 | /// Awards the a given . 11 | Item, 12 | /// Replaces this parquet with a . 13 | Collectible, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IDeeplyCloneable.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet 2 | { 3 | /// 4 | /// Classes implementing this interface provide the means to duplicate an instance of that class via a deep copy. 5 | /// 6 | /// The class to be deeply cloned. 7 | public interface IDeeplyCloneable 8 | { 9 | /// 10 | /// Creates a new instance that is a deep copy of the current instance. 11 | /// 12 | /// A new instance with the same characteristics as the current instance. 13 | T DeepClone(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/CollectEffect.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Parquets 2 | { 3 | /// 4 | /// IDs for effects that can happen when a character encounters a Collectible. 5 | /// 6 | public enum CollectingEffect 7 | { 8 | /// Collecting this parquet has no effect. 9 | None = 0, 10 | /// Awards the a given . 11 | Item, 12 | /// Allows the to remain safely in the longer. 13 | BiomeTime, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Thank you for your interest in contributing to Parquet! 3 | 4 | This project is released as-is, with no gurantee of future maintenance. 5 | Work may continue as time allows. 6 | 7 | This project may be forked in accordance with the license terms 8 | You may also report issues or suggest improvements, but please 9 | respect that these may not be applied or acted on. 10 | 11 | Because this project is a means for the creator, Paige, to expand their 12 | own knowledge and experience, contributions at present are invite-only. 13 | 14 | However, you're always welcome fork the library and/or use its tools 15 | in your own projects, provided you respect the licensing. 16 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/EntryType.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Parquets 2 | { 3 | /// 4 | /// Whether and how a communicates to an adjacent . 5 | /// 6 | public enum EntryType 7 | { 8 | /// This furnishing does not communicate to another map or room. 9 | None = 0, 10 | /// This furnishing communicates between rooms on the same map. 11 | Room, 12 | /// This furnishing communicates to the map above. 13 | Up, 14 | /// This furnishing communicates to the map below. 15 | Down, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Documentation/Home.md: -------------------------------------------------------------------------------- 1 | Parquet Project Logo 2 | 3 | Parquet targets the following features: 4 | 5 | 1) A peaceful 2D overworld map that may be explored. 6 | 2) Simple free-form building mechanics allowing player characters to create homes in the world. 7 | 3) Resource collection mechanics allowing players to upgrade their homes and tools. 8 | 4) A simple crafting system allowing players to unlock new tools and building materials. 9 | 5) Interactive NPCs. 10 | 6) A quest system encouraging players to build particular building types. 11 | 7) Dialogue and narrative delivery. 12 | 13 | This is a work in progress; for now, please read all the pages listed and the [Project Read Me](https://github.com/mxashlynn/Parquet/blob/master/README.md). 14 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Items/ModificationTool.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Items 2 | { 3 | /// 4 | /// IDs for tools that Characters can use to modify s and s. 5 | /// 6 | /// 7 | /// The tool subtypes are hard-coded, but individual s themselves are configured in CSV files. 8 | /// 9 | public enum ModificationTool 10 | { 11 | /// This parquet cannot be modified. 12 | None = 0, 13 | /// This parquet can be modified with a shovel-like tool. 14 | Shovel, 15 | /// This parquet can be modified with a hammer-like tool. 16 | Hammer, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Scripts/IMutableScriptModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Scripts 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// By design, subtypes of should never themselves use . 10 | /// IMutableScriptModel is for use only by external types that require read/write access to model properties. 11 | /// 12 | public interface IMutableScriptModel : IMutableModel 13 | { 14 | /// 15 | /// A series of imperative, procedural commands. 16 | /// 17 | public ICollection Nodes { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ParquetClassLibrary/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Parquet 4 | { 5 | /// 6 | /// Represents a type used to perform logging. 7 | /// Typically this type is provided by the game or tool that uses Parquet. 8 | /// 9 | /// 10 | /// Inspired by Microsoft.Extensions.Logging.ILogger but simpler. 11 | /// 12 | public interface ILogger 13 | { 14 | /// 15 | /// Writes a log entry. 16 | /// 17 | /// The severity of the event being logged. 18 | /// A message summarizing the event being logged. 19 | /// The exception related to this event, if any. 20 | void Log(LogLevel logLevel, string message, Exception exception); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ParquetUnitTests/RecipeElementUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Xunit; 4 | 5 | namespace ParquetUnitTests 6 | { 7 | /// 8 | /// Unit tests . 9 | /// 10 | public class RecipeElementUnitTest 11 | { 12 | [Fact] 13 | internal void ZeroAmountsFailTest() 14 | { 15 | static void TestCodeZero() 16 | { 17 | var _ = new RecipeElement(0, "test"); 18 | } 19 | 20 | Assert.Throws(TestCodeZero); 21 | } 22 | 23 | [Fact] 24 | internal void NegativeeAountsFailTest() 25 | { 26 | static void TestCodeNegative() 27 | { 28 | var _ = new RecipeElement(-1, "test"); 29 | } 30 | 31 | Assert.Throws(TestCodeNegative); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Beings/IMutableCritterModel.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Beings 2 | { 3 | /// 4 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 5 | /// 6 | /// 7 | /// By design, subtypes of should never themselves use . 8 | /// IMutableCritterModel is for use only by external types that require read/write access to model properties. 9 | /// 10 | public interface IMutableCritterModel : IMutableBeingModel 11 | { 12 | // This class is intentionally left empty. 13 | // Currently, everything needed for editing CritterModels is provided by IBeingModelEdit. 14 | // However, that may change in the future. This class exists to simplify external code 15 | // and to allow for future expansion of CritterModel. 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/IMutableFloorModel.cs: -------------------------------------------------------------------------------- 1 | using Parquet.Items; 2 | 3 | namespace Parquet.Parquets 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// By design, subtypes of should never themselves use . 10 | /// IMutableFloorModel is for use only by external types that require read/write access to model properties. 11 | /// 12 | public interface IMutableFloorModel : IMutableParquetModel 13 | { 14 | /// The tool used to dig out or fill in the floor. 15 | public ModificationTool ModTool { get; set; } 16 | 17 | /// Player-facing name of the parquet, used when it has been dug out. 18 | public string TrenchName { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Items/GatheringTool.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Items 2 | { 3 | /// 4 | /// IDs for tools that Characters can use to gather s. 5 | /// 6 | /// 7 | /// The tool subtypes are hard-coded, but individual s themselves are configured in CSV files. 8 | /// 9 | public enum GatheringTool 10 | { 11 | /// This parquet cannot be gathered. 12 | None = 0, 13 | /// This parquet can be gathered by using a pick-like tool. 14 | Pick, 15 | /// This parquet can be gathered by using a axe-like tool. 16 | Axe, 17 | /// This parquet can be gathered by using a shovel-like tool. 18 | Shovel, 19 | /// This parquet can be gathered by using a bucket-like tool. 20 | Bucket, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ExampleData/BlockModels.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,ItemID,AddsToBiome,AddsToRoom,GatherTool,GatherEffect,CollectibleID,IsFlammable,IsLiquid,MaxToughness 2 | -20000,2 Block Test Parquet,"This descriptiong tests ""escaped"" double quotes and commas, ya know?",Test,Test-Tag,-140002,,,None,None,-40000,False,False,10 3 | 20000,Bush,A short shrub.,This is just a test.,,None,,,Axe,Collectible,40000,True,False,1 4 | 20001,Tree,A large woody plant.,,,None,Forested❟Wood,None❟Natural,Axe,Collectible,40001,True,False,4 5 | 20003,Sandstone Boulder,A major block of stone made from accreted sand.,,,None,Deserted,,Pick,None,None,False,False,10 6 | 20004,Ice Boulder,A huge chunk of frozen water.,,,None,Frozen,,Pick,None,None,False,False,8 7 | 20005,Grey Brick,Standard blocks for building a wall.,,,None,,Brick,Pick,None,None,False,False,8 8 | 20006,Fresh Water,"Come, wash your face!",,,140001,,,Bucket,Item,None,False,True,1 9 | 20007,Lava,Molten silicates from the Earth's mantle.,,,140002,Volcanic,,Bucket,Item,None,False,True,1 10 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IntExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet 2 | { 3 | /// 4 | /// Provides extension methods to the built in integer type. 5 | /// 6 | internal static class IntExtensions 7 | { 8 | /// Ensures an integer falls within the given range. 9 | /// Integer to normalize. 10 | /// The lowest valid value for the integer. 11 | /// The highest valid value for the integer. 12 | /// The integer, normalized. 13 | public static int Normalize(this int value, int lowerBound, int upperBound) 14 | { 15 | if (value < lowerBound) 16 | { 17 | value = lowerBound; 18 | } 19 | else if (value > upperBound) 20 | { 21 | value = upperBound; 22 | } 23 | 24 | return value; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Beings/IMutablePronounGroup.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Beings 2 | { 3 | /// 4 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 5 | /// 6 | public interface IMutablePronounGroup 7 | { 8 | /// Personal pronoun used as the subject of a verb. 9 | public string Subjective { get; set; } 10 | 11 | /// Personal pronoun used as the indirect object of a preposition or verb. 12 | public string Objective { get; set; } 13 | 14 | /// Personal pronoun used to attribute possession. 15 | public string Determiner { get; set; } 16 | 17 | /// Personal pronoun used to indicate a relationship. 18 | public string Possessive { get; set; } 19 | 20 | /// Personal pronoun used to indicate the user. 21 | public string Reflexive { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Crafts/IMutableCraftingRecipe.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Crafts 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// By design, subtypes of should never themselves use . 10 | /// IMutableCraftingRecipe is for use only by external types that require read/write access to model properties. 11 | /// 12 | public interface IMutableCraftingRecipe : IMutableModel 13 | { 14 | /// The types and amounts of s created by following this recipe. 15 | public ICollection Products { get; } 16 | 17 | /// All materials and their quantities needed to follow this recipe once. 18 | public ICollection Ingredients { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Beings/IMutableBeingModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Beings 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | public interface IMutableBeingModel : IMutableModel 9 | { 10 | /// The of the in which this character is at home. 11 | public ModelID NativeBiomeID { get; set; } 12 | 13 | /// The of the governing the way this being acts. 14 | public ModelID PrimaryBehaviorID { get; set; } 15 | 16 | /// Types of parquets this avoids, if any. 17 | public ICollection AvoidsIDs { get; } 18 | 19 | /// Types of parquets this seeks out, if any. 20 | public ICollection SeeksIDs { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/IMutableCollectibleModel.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Parquets 2 | { 3 | /// 4 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 5 | /// 6 | /// 7 | /// By design, subtypes of should never themselves use . 8 | /// IMutableCollectibleModel is for use only by external types that require read/write access to model properties. 9 | /// 10 | public interface IMutableCollectibleModel : IMutableParquetModel 11 | { 12 | /// The effect generated when a character encounters this Collectible. 13 | public CollectingEffect CollectionEffect { get; set; } 14 | 15 | /// 16 | /// The scale in points of the effect. 17 | /// For example, how much to alter a stat if the is set to alter a stat. 18 | /// 19 | public int EffectAmount { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IReadOnlyGrid.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CsvHelper.TypeConversion; 3 | 4 | namespace Parquet 5 | { 6 | /// 7 | /// A two-dimensional collection that functions much like an immutable array. 8 | /// 9 | /// For serialization, implementing classes need to guarantee stable iteration order. 10 | /// The type collected. 11 | public interface IReadOnlyGrid : IReadOnlyCollection, IDeeplyCloneable> 12 | where TElement : ITypeConverter 13 | { 14 | /// Gets the number of elements in the Y dimension of the . 15 | public int Rows { get; } 16 | 17 | /// Gets the number of elements in the X dimension of the . 18 | public int Columns { get; } 19 | 20 | /// Read-only access to any object in the grid. 21 | public TElement this[int y, int x] { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Paige Ashlynn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ParquetUnitTests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", 9 | Justification = "Prefer keeping unit tests in Arrange-Act-Assert form.", 10 | Scope = "namespaceanddescendants", Target = "~N:ParquetUnitTests")] 11 | 12 | [assembly: SuppressMessage("Style", "IDE0017:Simplify object initialization", 13 | Justification = "Prefer keeping unit tests in Arrange-Act-Assert form.", 14 | Scope = "namespaceanddescendants", Target = "~N:ParquetUnitTests")] 15 | 16 | [assembly: SuppressMessage("Performance", "CA1814:Prefer jagged arrays over multidimensional", 17 | Justification = "Parquet requires multidimensional arrays.", 18 | Scope = "namespaceanddescendants", Target = "~N:ParquetUnitTests")] 19 | -------------------------------------------------------------------------------- /ParquetUnitTests/Parquets/FloorModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Parquet.Parquets; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests.Parquets 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class FloorModelUnitTest 12 | { 13 | #region Test Values 14 | /// Identifier used when creating a new floor. 15 | private static readonly ModelID newFloorID = TestModels.TestFloor.ID - 1; 16 | #endregion 17 | 18 | [Fact] 19 | internal void ValidFloorIDsArePermittedTest() 20 | { 21 | var testFloor = new FloorModel(newFloorID, "will be created", "", ""); 22 | 23 | Assert.NotNull(testFloor); 24 | } 25 | 26 | [Fact] 27 | internal void InvalidFloorIDsRaiseExceptionTest() 28 | { 29 | var badFloorID = TestModels.TestBlock.ID; 30 | 31 | void TestCode() 32 | { 33 | var _ = new CollectibleModel(badFloorID, "will fail", "", ""); 34 | } 35 | 36 | Assert.Throws(TestCode); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ParquetUnitTests/AllUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Parquet; 3 | using Xunit; 4 | 5 | namespace ParquetUnitTests 6 | { 7 | /// 8 | /// Unit tests . 9 | /// 10 | public class AllUnitTest 11 | { 12 | #region Values for Tests 13 | /// 14 | /// The highest defined in 15 | /// except for .Maximum. 16 | /// 17 | internal static readonly ModelID MaximumIDRangeUpperLimit = typeof(All).GetFields() 18 | .Where(fieldInfo => fieldInfo.FieldType.IsGenericType 19 | && fieldInfo.FieldType == typeof(Range) 20 | && fieldInfo.Name != nameof(All.ItemIDs)) 21 | .Select(fieldInfo => fieldInfo.GetValue(null)) 22 | .Cast>() 23 | .Select(range => range.Maximum) 24 | .Max(); 25 | #endregion 26 | 27 | [Fact] 28 | internal void ItemIDMinimumIsGreaterThanMaximumDefinedRangeUpperBoundTest() 29 | { 30 | Assert.True(All.ItemIDs.Minimum > MaximumIDRangeUpperLimit); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Scripts/IMutableInteractionModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Scripts 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// By design, subtypes of should never themselves use . 10 | /// IMutableInteractionModel is for use only by external types that require read/write access to model properties. 11 | /// 12 | public interface IMutableInteractionModel : IMutableModel 13 | { 14 | /// 15 | /// Describes the criteria for beginning this interaction. 16 | /// 17 | public ICollection PrerequisitesIDs { get; } 18 | 19 | /// 20 | /// Everything this interaction entails. 21 | /// 22 | public ICollection StepsIDs { get; } 23 | 24 | /// 25 | /// Describes the results of finishing this interaction. 26 | /// 27 | public ICollection OutcomesIDs { get; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IVisibleData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Parquet 4 | { 5 | /// 6 | /// Indicates an external view onto associated data should be updated. 7 | /// 8 | /// The instance that raised the event. 9 | /// Additional event data. 10 | /// true if the operation succeeded; otherwise, false. 11 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", 12 | Justification = "False positive.")] 13 | public delegate bool DataChangedEventHandler(object sender, EventArgs eventData); 14 | 15 | /// 16 | /// Raises events when an external view onto associated data should be updated. 17 | /// 18 | public interface IVisibleData 19 | { 20 | /// 21 | /// Raised when an external view onto associated data should be updated. 22 | /// 23 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1003:Use generic event handler instances", 24 | Justification = "False positive.")] 25 | public event DataChangedEventHandler VisibleDataChanged; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ExampleData/BiomeRecipes.csv: -------------------------------------------------------------------------------- 1 | ID,Name,Description,Comment,Tags,Tier,IsRoomBased,IsLiquidBased,ParquetCriteria,EntryRequirements 2 | -70000,Biome Test,Test,Test,,1,False,True,None, 3 | 70000,Alpine,,,,4,False,False,None,ForestKey❟CavernKey❟Tier2Key❟Tier3Key❟SeasideKey❟TundraKey❟AlpineKey 4 | 70001,Cavern,,,,3,False,False,None,ForestKey❟CavernKey❟Tier2Key❟Tier3Key 5 | 70002,Desert,,,,2,False,False,Deserted,SeasideKey❟DesertKey❟Tier2Key 6 | 70003,Field,,,,0,False,False,None, 7 | 70004,Forest,,,,1,False,False,Forested,ForestKey 8 | 70005,Heavens,Heavenly!,sky,"What a nice place, but don't miss your step!",5,False,True,Heavenly,HeavenlyKey 9 | 70006,Inferno,Infernal!,pit,"What a horrendous place, so don't miss your step!",5,False,True,Volcanic,InfernalKey 10 | 70007,Ruins,,,,4,False,False,Ruinous,ForestKey❟CavernKey❟Tier2Key❟Tier3Key❟ForestKey❟SwampKey❟RuinsKey 11 | 70008,Seaside,,,,1,False,True,Coastal,SeasideKey 12 | 70009,Swamp,,,,2,False,False,Swampy,ForestKey❟SwampKey❟Tier2Key 13 | 70010,Town,,,,0,True,False,Urban, 14 | 70011,Tundra,,,,2,False,False,Frozen,SeasideKey❟TundraKey❟Tier2Key 15 | 70012,Volcano,,,,4,False,True,Volcanic,ForestKey❟CavernKey❟Tier2Key❟Tier3Key❟SeasideKey❟DesertKey❟VolcanoKey 16 | 70013,Prism,,,,4,False,False,Crystaline,ForestKey❟CavernKey❟Tier2Key❟Tier3Key❟SeasideKey❟TundraKey❟AlpineKey 17 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Items/ItemType.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Items 2 | { 3 | /// 4 | /// Represents the different categories of s that may be carried and used. 5 | /// 6 | /// 7 | /// The subtypes are hard-coded, but individual items themselves are configured in CSV files. 8 | /// 9 | public enum ItemType 10 | { 11 | /// This item corresponds to no particular category. 12 | Other = 0, 13 | /// This item may be used only once. 14 | Consumable, 15 | /// This item may be worn, carried, or otherwise employed in an ongoing fashion. 16 | Equipment, 17 | /// This item unlocks a mechanic, domain, or door. 18 | KeyItem, 19 | /// This item may be used in crafting a recipe. 20 | Material, 21 | /// This item may contain other items. 22 | Storage, 23 | /// This item may be used multiple times to gather parquets. 24 | ToolForGathering, 25 | /// This item may be used multiple times to alter parquets. 26 | ToolForModification, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Rooms/ReadOnlyRoomCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Parquet.Rooms 5 | { 6 | /// 7 | /// Convenience extensions to . 8 | /// 9 | public static class ReadOnlyRoomCollectionExtensions 10 | { 11 | /// 12 | /// Returns the at the given position, if there is one. 13 | /// 14 | /// The current collection of s. 15 | /// An in-bounds position to search for a . 16 | /// The specified if found; otherwise, null. 17 | public static Room GetRoomAtOrNull(this IReadOnlyCollection rooms, Point2D position) 18 | => rooms?.FirstOrDefault(room => room.ContainsPosition(position)) ?? null; 19 | 20 | /// 21 | /// Returns a that represents the current . 22 | /// 23 | /// The representation. 24 | public static string ToString(this IReadOnlyCollection rooms) 25 | => $"{rooms?.Count ?? 0} {nameof(Rooms)}"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IMutableModelCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet 4 | { 5 | /// 6 | /// Facilitates updating elements of a from design tools 7 | /// while maintaining a read-only facade during play. 8 | /// 9 | /// The type collected, typically a concrete descendant of . 10 | public interface IMutableModelCollection : ICollection, IVisibleData 11 | where TModel : Model 12 | { 13 | /// 14 | /// Removes the associated with the given from the collection. 15 | /// 16 | /// The ID for a valid, defined contained in this collection. 17 | public bool Remove(ModelID id); 18 | 19 | /// 20 | /// Replaces a contained with the given whose 21 | /// is identical to that of the model being replaced. 22 | /// 23 | /// A valid, defined contained in this collection. 24 | public void Replace(TModel model); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ParquetUnitTests/Beings/CharacterModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Parquet.Beings; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests.Beings 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class CharacterModelUnitTest 12 | { 13 | #region Test Values 14 | /// Identifier used when creating a new block. 15 | private static readonly ModelID newCharacterID = TestModels.TestCharacter.ID - 1; 16 | #endregion 17 | 18 | [Fact] 19 | internal void ValidCharacterIDsArePermittedTest() 20 | { 21 | var newCharacter = new CharacterModel(newCharacterID, "Character", "will be created", "", null, All.BiomeRecipeIDs.Minimum, All.ScriptIDs.Minimum); 22 | 23 | Assert.NotNull(newCharacter); 24 | } 25 | 26 | [Fact] 27 | internal void InvalidCharacterIDsRaiseExceptionTest() 28 | { 29 | var badCharacterID = TestModels.TestBlock.ID - 1; 30 | 31 | void TestCode() 32 | { 33 | var _ = new CharacterModel(badCharacterID, "Character", "will fail", "", null, All.BiomeRecipeIDs.Minimum, All.ScriptIDs.Minimum); 34 | } 35 | 36 | Assert.Throws(TestCode); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ParquetUnitTests/Beings/CritterModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Parquet.Beings; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests.Beings 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class CritterModelUnitTest 12 | { 13 | #region Test Values 14 | /// Identifier used when creating a new block. 15 | private static readonly ModelID newCritterID = TestModels.TestCritter.ID - 1; 16 | #endregion 17 | 18 | [Fact] 19 | internal void ValidCritterIDsArePermittedTest() 20 | { 21 | var newCritter = new CritterModel(newCritterID, "will be created", "", "", null, 22 | All.BiomeRecipeIDs.Minimum, All.ScriptIDs.Minimum); 23 | 24 | Assert.NotNull(newCritter); 25 | } 26 | 27 | [Fact] 28 | internal void InvalidCritterIDsRaiseExceptionTest() 29 | { 30 | var badCritterID = TestModels.TestBlock.ID - 1; 31 | 32 | void TestCode() 33 | { 34 | var _ = new CritterModel(badCritterID, "will fail", "", "", null, 35 | All.BiomeRecipeIDs.Minimum, All.ScriptIDs.Minimum); 36 | } 37 | 38 | Assert.Throws(TestCode); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Documentation/How_Elevation_Works_In_Parquet.md: -------------------------------------------------------------------------------- 1 | # Elevation 2 | 3 | Parquet is a 2D game library, so the concept of elevation exists only in rudimentary form. 4 | 5 | Any given Map Region may be assigned to one of three possible elevations: above, level with, or below ground. Level is the default. 6 | 7 | Map Regions are connected to one another via Exit Points. Exit Points are arbitrary and do not consider elevation (or adjacency of any kind). 8 | 9 | However, a Map Region with Above Ground elevation may have another Map Region designated below it. Likewise, a Below Ground Map Region may have another region above it. And a Level Ground region may have both. 10 | 11 | A chart showing the relationships between map regions of different elevations. 12 | 13 | # Liquid Blocks Flow Down 14 | 15 | Liquid Blocks that flow onto an empty space in a Map Region that has another region below it will flow down to that lower elevation Map Region automatically. 16 | 17 | # Beings Travel Up & Down 18 | 19 | Similarly, Critters and Characters that walk onto an empty space in a Map Region with another region below will fall through to the lower region. 20 | 21 | Finally, a Character may build an item like a staircase or ladder at an arbitrary point in a Map Region that has another Map Region designated Above it in order to climb up. 22 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Biomes/IMutableBiomeRecipe.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Biomes 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | public interface IMutableBiomeRecipe : IMutableModel 9 | { 10 | /// 11 | /// A rating indicating where in the progression this falls. 12 | /// Must be non-negative. Higher values indicate later Biomes. 13 | /// 14 | public int Tier { get; set; } 15 | 16 | /// Determines whether or not this is defined in terms of s. 17 | public bool IsRoomBased { get; set; } 18 | 19 | /// Determines whether or not this is defined in terms of liquid parquets. 20 | public bool IsLiquidBased { get; set; } 21 | 22 | /// Describes the parquets that make up this . 23 | public ModelTag ParquetCriteria { get; set; } 24 | 25 | /// Describes the s a needs to safely access this biome. 26 | public ICollection EntryRequirements { get; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IGrid.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CsvHelper.TypeConversion; 3 | 4 | namespace Parquet 5 | { 6 | /// 7 | /// A two-dimensional collection that functions much like an array. 8 | /// Instances of this class are mutable during play. 9 | /// 10 | /// For serialization, implementing classes need to guarantee stable iteration order. 11 | /// The type collected. 12 | public interface IGrid : IEnumerable, IDeeplyCloneable> 13 | where TElement : ITypeConverter 14 | { 15 | /// Separates s within this . 16 | public string GridDelimiter { get; } 17 | 18 | /// Gets the number of elements in the Y dimension of the . 19 | public int Rows { get; } 20 | 21 | /// Gets the number of elements in the X dimension of the . 22 | public int Columns { get; } 23 | 24 | /// Gets the total number of elements contained in the . 25 | public int Count { get; } 26 | 27 | /// Access to any object in the grid. 28 | public ref TElement this[int y, int x] { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/IMutableBlockModel.cs: -------------------------------------------------------------------------------- 1 | using Parquet.Items; 2 | 3 | namespace Parquet.Parquets 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// By design, subtypes of should never themselves use . 10 | /// IMutableBlockModel is for use only by external types that require read/write access to model properties. 11 | /// 12 | public interface IMutableBlockModel : IMutableParquetModel 13 | { 14 | /// The tool used to remove the block. 15 | public GatheringTool GatherTool { get; set; } 16 | 17 | /// The effect generated when a character gathers this Block. 18 | public GatheringEffect GatherEffect { get; set; } 19 | 20 | /// The Collectible spawned when a character gathers this Block. 21 | public ModelID CollectibleID { get; set; } 22 | 23 | /// Whether or not the block is flammable. 24 | public bool IsFlammable { get; set; } 25 | 26 | /// Whether or not the block is a liquid. 27 | public bool IsLiquid { get; set; } 28 | 29 | /// The block's native toughness. 30 | public int MaxToughness { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Games/IMutableGameModel.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Games 2 | { 3 | /// 4 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 5 | /// 6 | /// 7 | /// By design, subtypes of should never themselves use . 8 | /// IMutableGameModel is for use only by external types that require read/write access to model properties. 9 | /// 10 | public interface IMutableGameModel : IMutableModel 11 | { 12 | /// If true this game is part of a longer sequence of games. 13 | public bool IsEpisode { get; set; } 14 | 15 | /// Subtitle, if any. This will be used as the title of the episode if is true. 16 | public string EpisodeTitle { get; set; } 17 | 18 | /// Number of this episode in its sequence, if any. 19 | public int EpisodeNumber { get; set; } 20 | 21 | /// The of the that the player controls at the outset. 22 | public ModelID PlayerCharacterID { get; set; } 23 | 24 | /// The of the to run when play begins. 25 | public ModelID FirstScriptID { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub Language Detection 2 | Design/* linguist-documentation 3 | 4 | # Normalize Line Endings to LineFeed 5 | *.cs text=auto 6 | *.csv text=auto 7 | *.htm text=auto 8 | *.html text=auto 9 | *.md text=auto 10 | *.text text=auto 11 | *.txt text=auto 12 | *.xml text=auto 13 | LICENSE text=auto 14 | 15 | # LFS 16 | *.7z filter=lfs diff=lfs merge=lfs -text 17 | *.apk filter=lfs diff=lfs merge=lfs -text 18 | *.app filter=lfs diff=lfs merge=lfs -text 19 | *.bmp filter=lfs diff=lfs merge=lfs -text 20 | *.dll filter=lfs diff=lfs merge=lfs -text 21 | *.docx filter=lfs diff=lfs merge=lfs -text 22 | *.exe filter=lfs diff=lfs merge=lfs -text 23 | *.gif filter=lfs diff=lfs merge=lfs -text 24 | *.ico filter=lfs diff=lfs merge=lfs -text 25 | *.jpeg filter=lfs diff=lfs merge=lfs -text 26 | *.jpg filter=lfs diff=lfs merge=lfs -text 27 | *.kra filter=lfs diff=lfs merge=lfs -text 28 | *.mod filter=lfs diff=lfs merge=lfs -text 29 | *.mp3 filter=lfs diff=lfs merge=lfs -text 30 | *.ogg filter=lfs diff=lfs merge=lfs -text 31 | *.otf filter=lfs diff=lfs merge=lfs -text 32 | *.pages filter=lfs diff=lfs merge=lfs -text 33 | *.pcx filter=lfs diff=lfs merge=lfs -text 34 | *.png filter=lfs diff=lfs merge=lfs -text 35 | *.rar filter=lfs diff=lfs merge=lfs -text 36 | *.svg filter=lfs diff=lfs merge=lfs -text 37 | *.ttf filter=lfs diff=lfs merge=lfs -text 38 | *.uge filter=lfs diff=lfs merge=lfs -text 39 | *.wav filter=lfs diff=lfs merge=lfs -text 40 | *.xnb filter=lfs diff=lfs merge=lfs -text 41 | *.zip filter=lfs diff=lfs merge=lfs -text 42 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Rooms/IMutableRoomRecipe.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Rooms 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// By design, subtypes of should never themselves use . 10 | /// IMutableRoomRecipe is for use only by external types that require read/write access to model properties. 11 | /// 12 | public interface IMutableRoomRecipe : IMutableModel 13 | { 14 | /// Minimum number of open spaces needed for this to register. 15 | public int MinimumWalkableSpaces { get; set; } 16 | 17 | /// A list of categories this requires. 18 | public ICollection OptionallyRequiredFurnishings { get; } 19 | 20 | /// An optional list of categories this requires. 21 | public ICollection OptionallyRequiredWalkableFloors { get; } 22 | 23 | /// An optional list of categories this requires as walls. 24 | public ICollection OptionallyRequiredPerimeterBlocks { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ParquetUnitTests/AssemblyInfoUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Parquet; 4 | using Xunit; 5 | 6 | // Make no promises to maintain internal services. 7 | [assembly: ComVisible(false)] 8 | 9 | // ParquetUnitTests are API consumers, not API providers. 10 | [assembly: CLSCompliant(false)] 11 | 12 | namespace ParquetUnitTests 13 | { 14 | /// 15 | /// Unit tests . 16 | /// 17 | public class AssemblyInfoUnitTest 18 | { 19 | #region Values for Tests 20 | /// This is the canonical invalid library version string used in serialization tests. 21 | private const string invalidLibVersion = "0.0.0.0"; 22 | 23 | /// This is the string used if the library version cannot be loaded. 24 | private const string errorLibVersion = "?.?.?.?"; 25 | #endregion 26 | 27 | [Fact] 28 | internal void SupportedLibraryVersionIsDefinedTest() 29 | { 30 | Assert.False(string.IsNullOrEmpty(AssemblyInfo.LibraryVersion)); 31 | } 32 | 33 | [Fact] 34 | internal void SupportedLibraryVersionIsNotInvalidTest() 35 | { 36 | Assert.NotEqual(invalidLibVersion, AssemblyInfo.LibraryVersion); 37 | } 38 | 39 | [Fact] 40 | internal void SupportedLibraryVersionWasFoundTest() 41 | { 42 | Assert.NotEqual(errorLibVersion, AssemblyInfo.LibraryVersion); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Regions/IMutableRegionModel.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Regions 2 | { 3 | /// 4 | /// Facilitates editing of characteristics from design tools while maintaining a read-only face during play. 5 | /// 6 | public interface IMutableRegionModel : IMutableModel 7 | { 8 | /// A color to display in any empty areas of the region. 9 | string BackgroundColor { get; set; } 10 | 11 | /// The of the region to the north of this one. 12 | ModelID RegionToTheNorthID { get; set; } 13 | 14 | /// The of the region to the east of this one. 15 | ModelID RegionToTheEastID { get; set; } 16 | 17 | /// The of the region to the south of this one. 18 | ModelID RegionToTheSouthID { get; set; } 19 | 20 | /// The of the region to the west of this one. 21 | ModelID RegionToTheWestID { get; set; } 22 | 23 | /// The of the region above this one. 24 | ModelID RegionAboveID { get; set; } 25 | 26 | /// The of the region below this one. 27 | ModelID RegionBelowID { get; set; } 28 | 29 | /// Instructions on how to procedurally generate this region. 30 | MapChunkGrid MapChunks { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/IMutableFurnishingModel.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Parquets 2 | { 3 | /// 4 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 5 | /// 6 | /// 7 | /// By design, subtypes of should never themselves use . 8 | /// IMutableFurnishingModel is for use only by external types that require read/write access to model properties. 9 | /// 10 | public interface IMutableFurnishingModel : IMutableParquetModel 11 | { 12 | /// If true this may be walked on. 13 | public bool IsWalkable { get; set; } 14 | 15 | /// Indicates if and how this serves as an entry to a or . 16 | public EntryType Entry { get; set; } 17 | 18 | /// If true this serves as part of a perimeter of a . 19 | public bool IsEnclosing { get; set; } 20 | 21 | /// If true the may catch fire. 22 | public bool IsFlammable { get; set; } 23 | 24 | /// If true this may be opened and closed. 25 | public bool IsOpenable { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ParquetClassLibrary/LoggerDefault.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Parquet 4 | { 5 | /// 6 | /// Performs logging. 7 | /// 8 | /// 9 | /// This is very bare bones and is intended to be replaced by a more appropriate logger provided by the application. 10 | /// 11 | /// 12 | internal class LoggerDefault : ILogger 13 | { 14 | /// 15 | /// Writes a log entry. 16 | /// 17 | /// The severity of the event being logged. 18 | /// A message summarizing the event being logged. 19 | /// The exception related to this event, if any. 20 | public void Log(LogLevel logLevel, string message = null, Exception exception = null) 21 | { 22 | if (logLevel == LogLevel.Debug && !LibraryState.IsDebugMode) 23 | { 24 | // Ignore debug logs when not running in debug mode. 25 | return; 26 | } 27 | 28 | var destination = logLevel == LogLevel.Info 29 | ? Console.Out 30 | : Console.Error; 31 | var nonNullMessage = !string.IsNullOrEmpty(message) 32 | ? message 33 | : exception is not null 34 | ? exception.Message 35 | : $"Parquet {nameof(LoggerDefault)} Trace at {DateTime.Now}."; 36 | 37 | destination.WriteLine(nonNullMessage); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ParquetClassLibrary/LibraryState.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet 2 | { 3 | /// 4 | /// Represents how the library was built and how it is currently being used. 5 | /// 6 | public static class LibraryState 7 | { 8 | #pragma warning disable IDE0079 // Remove unnecessary suppression -- conditional compilation. 9 | #pragma warning disable RS0016 // Add public types and members to the declared API -- conditional compilation. 10 | /// 11 | /// true if the library was built with the symbol DEBUG defined; false otherwise. 12 | /// 13 | public const bool IsDebugMode = 14 | #if DEBUG 15 | true; 16 | #else 17 | false; 18 | #endif 19 | #pragma warning restore RS0016 // Add public types and members to the declared API -- conditional compilation. 20 | #pragma warning restore IDE0079 // Remove unnecessary suppression -- conditional compilation. 21 | 22 | /// 23 | /// Set this to true if the library is being used by a game; 24 | /// set it to false if it is being used by an editor or tool. 25 | /// 26 | /// 27 | /// s are designed to be immutable during play 28 | /// but mutable at design time.
29 | /// By preventing model mutation when this is set to true, Parquet 30 | /// removes some of the responsibility to respect this distinction from the 31 | /// game developer. 32 | ///
33 | public static bool IsPlayMode { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/IMutableParquetModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Parquets 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// By design, subtypes of should never themselves use . 10 | /// IMutableParquetModel is for use only by external types that require read/write access to model properties. 11 | /// 12 | public interface IMutableParquetModel : IMutableModel 13 | { 14 | /// 15 | /// The of the awarded to the player when a character gathers or collects this parquet. 16 | /// 17 | public ModelID ItemID { get; set; } 18 | 19 | /// 20 | /// Describes the (s) that this parquet helps form. 21 | /// Guaranteed to never be null. 22 | /// 23 | public ICollection AddsToBiome { get; } 24 | 25 | /// 26 | /// A property of the parquet that can, for example, be used by s. 27 | /// Guaranteed to never be null. 28 | /// 29 | /// 30 | /// Allows the creation of classes of constructs, for example "wooden", "golden", "rustic", or "fancy" rooms. 31 | /// 32 | public ICollection AddsToRoom { get; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ParquetClassLibrary/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet 2 | { 3 | /// 4 | /// Defines logging severity levels. 5 | /// 6 | public enum LogLevel 7 | { 8 | /// 9 | /// Events of interest during development only. 10 | /// Used while implementing and testing. 11 | /// Library developers should see these events. 12 | /// 13 | Debug = 0, 14 | 15 | /// 16 | /// Normal and expected events that track application flow. 17 | /// Used to diagnose issues after release. 18 | /// Game developers should see these events. 19 | /// 20 | Info = 1, 21 | 22 | /// 23 | /// Unexpected events that do not interrupt normal operation. 24 | /// Used to diagnose issues after release. 25 | /// Game developers should see these events. 26 | /// 27 | Warning = 2, 28 | 29 | /// 30 | /// Abnormal events that interrupt operation, but from which the application can recover. 31 | /// Used to report a failure in the current activity, not an application-wide failure. 32 | /// Game players should see these events. 33 | /// 34 | Error = 3, 35 | 36 | /// 37 | /// Fatal events from which the application cannot recover. 38 | /// Used to report an application or system failures that requires immediate attention. 39 | /// Game players should see these events. 40 | /// These events typically require that the game and library be restarted. 41 | /// 42 | Fatal = 4, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ParquetUnitTests/Regions/RegionStatusUnitTestExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // TODO [TESTING] This needs to use RegionStatus instead of RegionModel. 3 | /* 4 | namespace ParquetUnitTests.Maps 5 | { 6 | /// 7 | /// Provides extension methods for the used in unit testing. 8 | /// 9 | internal static class RegionStatusUnitTestExtensions 10 | { 11 | /// Fills the region with a test pattern. 12 | internal static RegionModel FillTestPattern(this RegionModel regionModel) 13 | { 14 | for (var x = 0; x < RegionStatus.ParquetsPerRegionDimension; x++) 15 | { 16 | for (var y = 0; y < RegionStatus.ParquetsPerRegionDimension; y++) 17 | { 18 | regionModel.ParquetDefinitions[y, x].FloorID = TestModels.TestFloor.ID; 19 | } 20 | 21 | regionModel.ParquetDefinitions[0, x].BlockID = TestModels.TestBlock.ID; 22 | regionModel.ParquetDefinitions[RegionModel.ParquetsPerRegionDimension, 1].BlockID = TestModels.TestBlock.ID; 23 | } 24 | for (var y = 0; y < RegionStatus.ParquetsPerRegionDimension; y++) 25 | { 26 | regionModel.ParquetDefinitions[y, 0].BlockID = TestModels.TestBlock.ID; 27 | regionModel.ParquetDefinitions[y, RegionModel.ParquetsPerRegionDimension - 1].BlockID = TestModels.TestBlock.ID; 28 | } 29 | regionModel.ParquetDefinitions[2, 1].FurnishingID = TestModels.TestFurnishing.ID; 30 | regionModel.ParquetDefinitions[3, 3].CollectibleID = TestModels.TestCollectible.ID; 31 | 32 | return regionModel; 33 | } 34 | } 35 | } 36 | */ 37 | -------------------------------------------------------------------------------- /ParquetClassLibrary/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes. Project-level suppressions have no target. 4 | 5 | [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", 6 | Justification = "Parquet logs errors and returns results; it does not throw exceptions.", 7 | Scope = "namespaceanddescendants", Target = "~N:Parquet")] 8 | 9 | [assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", 10 | Justification = "This rule inhibits clarity as it prefers general terms to specific terms. Also, it creates confusion by forcing non-ItemModels to be called 'item' (for example, when implementing IEnumerableConverter).", 11 | Scope = "namespaceanddescendants", Target = "~N:Parquet")] 12 | 13 | [assembly: SuppressMessage("Performance", "CA1814:Prefer jagged arrays over multidimensional", 14 | Justification = "Parquet requires rectangular multidimensional arrays.", 15 | Scope = "namespaceanddescendants", Target = "~N:Parquet")] 16 | 17 | [assembly: SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", 18 | Justification = "This would defeat the purpose of making these classes implicitly interchangeable.", 19 | Scope = "namespaceanddescendants", Target = "~N:Parquet")] 20 | 21 | [assembly: SuppressMessage("Major Code Smell", "S3358:Ternary operators should not be nested", 22 | Justification = "This rule is incorrect.", 23 | Scope = "namespaceanddescendants", Target = "~N:Parquet")] 24 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Regions/ChunkTopography.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Regions 2 | { 3 | /// Indicates the basic form that the parquets in a take. 4 | public enum ChunkTopography 5 | { 6 | /// Indicates there are no parquets in this topography. 7 | Empty = 0, 8 | /// Indicates parquets entirely fill this topography. 9 | Solid, 10 | /// Indicates parquets are spread evenly throughout this topography. 11 | Scattered, 12 | /// Indicates parquets appear in clumps throughout this topography. 13 | Clustered, 14 | /// Indicates a central grouping of parquets in this topography. 15 | Central, 16 | /// Indicates parquets are grouped to the north end of this topography. 17 | North, 18 | /// Indicates parquets are grouped on both the north and east end of this topography. 19 | NorthEast, 20 | /// Indicates parquets are grouped to the east end of this topography. 21 | East, 22 | /// Indicates parquets are grouped on both the south and east end of this topography. 23 | SouthEast, 24 | /// Indicates parquets are grouped to the south end of this topography. 25 | South, 26 | /// Indicates parquets are grouped on both the south and west end of this topography. 27 | SouthWest, 28 | /// Indicates parquets are grouped to the west end of this topography. 29 | West, 30 | /// Indicates parquets are grouped on both the north and west end of this topography. 31 | NorthWest, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Items/IMutableItemModel.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Items 2 | { 3 | /// 4 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 5 | /// 6 | /// 7 | /// By design, subtypes of should never themselves use . 8 | /// IMutableItemModel is for use only by external types that require read/write access to model properties. 9 | /// 10 | public interface IMutableItemModel : IMutableModel 11 | { 12 | /// The type of item this is. 13 | public ItemType Subtype { get; set; } 14 | 15 | /// In-game value of the item. Must be non-negative. 16 | public int Worth { get; set; } 17 | 18 | /// How relatively rare this item is. 19 | public int Rarity { get; set; } 20 | 21 | /// How many of the item may share a single inventory slot. 22 | public int StackMax { get; set; } 23 | 24 | /// 25 | /// The of the generating the in-game effect caused by 26 | /// keeping the item in a 's . 27 | /// 28 | public ModelID EffectWhileHeldID { get; set; } 29 | 30 | /// 31 | /// The of the generating the in-game effect caused by 32 | /// using (consuming) the item. 33 | /// 34 | public ModelID EffectWhenUsedID { get; set; } 35 | 36 | /// The parquet that corresponds to this item, if any. 37 | public ModelID ParquetID { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ParquetClassLibrary/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // Make no promises to maintain public services. 7 | [assembly: ComVisible(false)] 8 | 9 | // Show warnings on CLS-noncompliant statements to better support .NET languages other than C#. 10 | [assembly: CLSCompliant(true)] 11 | 12 | // Allow unit tests to access classes and members with internal accessibility. 13 | [assembly: InternalsVisibleTo("ParquetUnitTests")] 14 | 15 | // TODO [Tests] Once unit tests are reliable, this should probably be removed and test content in Runner slimmed down. 16 | // Allow smoke test to access classes and members with internal accessibility. 17 | [assembly: InternalsVisibleTo("ParquetRunner")] 18 | 19 | namespace Parquet 20 | { 21 | /// 22 | /// Provides assembly-wide information. 23 | /// 24 | [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", 25 | Justification = "Comparing two AssemblyInfos is nonsensical.")] 26 | public readonly struct AssemblyInfo 27 | { 28 | /// Describes the version of the class library itself. 29 | /// 30 | /// The version has the format "{Major}.{Minor}.{Patch}.{Build}". 31 | /// - Major: Enhancements or fixes that break the API or its serialized data. 32 | /// - Minor: Enhancements that do not break the API or its serialized data. 33 | /// - Patch: Fixes that do not break the API or its serialized data. 34 | /// - Build: Procedural updates that do not imply any changes, such as when rebuilding for APK/IPA submission. 35 | /// 36 | public static readonly string LibraryVersion = typeof(ModelID).Assembly?.GetName()?.Version?.ToString() ?? "?.?.?.?"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ============= # 2 | # IDE generated # 3 | # ============= # 4 | /[Aa]ssets/Plugins/Editor/JetBrains* 5 | /[Aa]pp[Pp]ackages/ 6 | /[Dd]ebug/ 7 | /[Rr]elease/ 8 | /[Rr]eleases/ 9 | /x86/ 10 | /x64/ 11 | [Bb]in/ 12 | [Bb]in-[Rr]elease/ 13 | [Bb]uild/ 14 | [Bb]uilds/ 15 | [Bb]ld/ 16 | [Dd]ebugPublic/ 17 | [Ee]xported[Oo]bj/ 18 | .idea/ 19 | .builds/ 20 | .consulo/ 21 | .idea/ 22 | .vs/ 23 | *.[Cc]ache 24 | *.[Cc]ache[Ff]ile 25 | *.csproj.user 26 | *.mdb 27 | *.pidb 28 | *.pdb 29 | *.svd 30 | *.temp 31 | *.tmp 32 | *.tmp_proj 33 | *.unityproj 34 | *.userprefs 35 | 36 | # ============== # 37 | # Tool generated # 38 | # ============== # 39 | [Ll]ibrary/ 40 | [Oo]bj/ 41 | [Tt]emp/ 42 | [Uu]nity[Gg]enerated/ 43 | [Ss]witchIL2CPPCache/ 44 | [Ss]witchIL2CPPStats/ 45 | [Ll]ogs/ 46 | .gradle/ 47 | _ReSharper*/ 48 | *.pidb.meta 49 | *.[Dd]otCover 50 | *.[Dd]otSettings.user 51 | *.[Rr]e[Ss]harper 52 | sysinfo.txt 53 | 54 | # =============== # 55 | # NuGet generated # 56 | # =============== # 57 | **/[Pp]ackages/* 58 | /[Aa]rtifacts/ 59 | *.nupkg 60 | *.nuget.props 61 | *.nuget.targets 62 | project.lock.json 63 | project.assets.json 64 | 65 | # ===================== # 66 | # Text editor generated # 67 | # ===================== # 68 | ~$* 69 | *~ 70 | 71 | # ============ # 72 | # OS generated # 73 | # ============ # 74 | .DS_Store 75 | .DS_Store? 76 | ._* 77 | .Spotlight-V100 78 | .Trashes 79 | ehthumbs.db 80 | Thumbs.db 81 | Icon? 82 | Icons? 83 | 84 | # ====== # 85 | # Builds # 86 | # ====== # 87 | *.apk 88 | *.app 89 | *.dll 90 | *.exe 91 | *.iap 92 | *.unitypackage 93 | ParquetClassLibrary/Parquet.xml 94 | ParquetClassLibrary/Parquet.Documentation.xml 95 | ParquetRunner/ParquetRunner.Documentation.xml 96 | ParquetUnitTests/ParquetUnitTests.Documentation.xml 97 | 98 | # ================== # 99 | # Paige Stuff # 100 | # ================== # 101 | *.old 102 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IReadOnlyListOfTagsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Parquet 5 | { 6 | /// 7 | /// Extensions to the interface for working with s. 8 | /// 9 | public static class IReadOnlyListOfTagsExtensions 10 | { 11 | /// 12 | /// Determines whether this contains a 13 | /// beginning with the given prefix. 14 | /// 15 | /// The to search. 16 | /// The prefix to search for. 17 | /// true if this and the given instance are identical, ignoring case; otherwise, false. 18 | /// This is a convenience for client code and not used by the library itself. 19 | public static bool ContainsPrefixOrdinalIgnoreCase(this IReadOnlyList collection, ModelTag prefix) 20 | => collection.Any(tag => tag.StartsWithOrdinalIgnoreCase(prefix)); 21 | 22 | /// 23 | /// Determines whether this contains the given . 24 | /// 25 | /// The to search. 26 | /// The to search for. 27 | /// true if this and the given instance are identical, ignoring case; otherwise, false. 28 | /// This is a convenience for client code and not used by the library itself. 29 | public static bool ContainsOrdinalIgnoreCase(this IReadOnlyList collection, ModelTag tag) 30 | => collection.Any(aTag => aTag.EqualsOrdinalIgnoreCase(tag)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ParquetUnitTests/Crafts/CraftingRecipeUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Parquet; 4 | using Parquet.Crafts; 5 | using Xunit; 6 | 7 | namespace ParquetUnitTests.Crafts 8 | { 9 | /// 10 | /// Unit tests . 11 | /// 12 | public class CraftingRecipeUnitTest 13 | { 14 | #region Test Values 15 | /// Identifier used when creating a new block. 16 | private static readonly ModelID newCraftingRecipeID = TestModels.TestCraftingRecipe.ID - 1; 17 | 18 | /// A valid minimal list of test ingredients. 19 | private static readonly IReadOnlyList ingredientList = new List 20 | { 21 | new RecipeElement(1, "ingredient"), 22 | }; 23 | 24 | /// A valid minimal list of test products. 25 | private static readonly IReadOnlyList productList = new List 26 | { 27 | new RecipeElement(1, "product"), 28 | }; 29 | #endregion 30 | 31 | [Fact] 32 | internal void ValidCraftingRecipeIDsArePermittedTest() 33 | { 34 | var newCraftingRecipe = new CraftingRecipe(newCraftingRecipeID, "will be created", "", "", null, 35 | productList, ingredientList); 36 | 37 | Assert.NotNull(newCraftingRecipe); 38 | } 39 | 40 | [Fact] 41 | internal void InvalidCraftingRecipeIDsThrowTest() 42 | { 43 | var badCraftingRecipeID = TestModels.TestBlock.ID - 1; 44 | 45 | void TestCode() 46 | { 47 | var _ = new CraftingRecipe(badCraftingRecipeID, "will fail", "", "", null, 48 | productList, ingredientList); 49 | } 50 | 51 | Assert.Throws(TestCode); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ParquetUnitTests/Parquets/CollectibleModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Parquet.Parquets; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests.Parquets 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class CollectibleModelUnitTest 12 | { 13 | #region Test Values 14 | /// Identifier used when creating a new collectible. 15 | private static readonly ModelID newCollectibleID = TestModels.TestCollectible.ID - 1; 16 | #endregion 17 | 18 | [Fact] 19 | internal void ValidCollectibleIDsArePermittedTest() 20 | { 21 | var testCollectible = new CollectibleModel(newCollectibleID, "will be created", "", ""); 22 | 23 | Assert.NotNull(testCollectible); 24 | } 25 | 26 | [Fact] 27 | internal void InvalidCollectibleIDsRaiseExceptionTest() 28 | { 29 | var badCollectibleID = TestModels.TestBlock.ID; 30 | 31 | void TestCode() 32 | { 33 | var _ = new CollectibleModel(badCollectibleID, "will fail", "", ""); 34 | } 35 | 36 | Assert.Throws(TestCode); 37 | } 38 | 39 | [Fact] 40 | internal void ValidItemIDsArePermittedTest() 41 | { 42 | ModelID goodItemID = -All.ItemIDs.Minimum; 43 | 44 | var testBlock = new CollectibleModel(newCollectibleID, "will be created", "", "", null, goodItemID); 45 | 46 | Assert.NotNull(testBlock); 47 | } 48 | 49 | [Fact] 50 | internal void InvalidItemIDsRaiseExceptionTest() 51 | { 52 | var badItemID = TestModels.TestBlock.ID; 53 | 54 | void TestCode() 55 | { 56 | var _ = new CollectibleModel(newCollectibleID, "will fail", "", "", null, badItemID); 57 | } 58 | 59 | Assert.Throws(TestCode); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IReadOnlyCollectionOfTagsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Parquet 5 | { 6 | /// 7 | /// Extensions to the interface for working with s. 8 | /// 9 | public static class IReadOnlyCollectionOfTagsExtensions 10 | { 11 | /// 12 | /// Determines whether this contains a 13 | /// beginning with the given prefix. 14 | /// 15 | /// The to search. 16 | /// The prefix to search for. 17 | /// true if this and the given instance are identical, ignoring case; otherwise, false. 18 | /// This is a convenience for client code and not used by the library itself. 19 | public static bool ContainsPrefixOrdinalIgnoreCase(this IReadOnlyCollection collection, ModelTag prefix) 20 | => collection.Any(tag => tag.StartsWithOrdinalIgnoreCase(prefix)); 21 | 22 | /// 23 | /// Determines whether this contains the given . 24 | /// 25 | /// The to search. 26 | /// The to search for. 27 | /// true if this and the given instance are identical, ignoring case; otherwise, false. 28 | /// This is a convenience for client code and not used by the library itself. 29 | public static bool ContainsOrdinalIgnoreCase(this IReadOnlyCollection collection, ModelTag tag) 30 | => collection.Any(aTag => aTag.EqualsOrdinalIgnoreCase(tag)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IMutableModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet 4 | { 5 | /// 6 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 7 | /// 8 | /// 9 | /// and its subtypes are not editable once created. This is because during play, these classes serve 10 | /// as definitions, which need to remain fixed. Any state that changes during runtime is instead housed in a corresponding 11 | /// State class. 12 | /// 13 | /// However, at design time even definitions need to be changeable. To support this easily while still preserving the fixedness 14 | /// of Model and company during play, and its subtypes provide interfaces that may be used to safely 15 | /// make changes to the properties of their corresponding models. 16 | /// 17 | /// By design, subtypes of should never themselves use their interface. 18 | /// IMutableModel is for use only by external types (such as those in a design-time tool) that require read/write access to model 19 | /// properties. 20 | /// 21 | public interface IMutableModel : IVisibleData 22 | { 23 | /// Game-wide unique identifier. 24 | /// Be cautious editing this. 25 | public ModelID ID { get; set; } 26 | 27 | /// Player-facing name. 28 | public string Name { get; set; } 29 | 30 | /// Player-facing description. 31 | public string Description { get; set; } 32 | 33 | /// Optional comment. 34 | /// Could be used for designer's notes or to implement an in-game dialogue with or on the . 35 | public string Comment { get; set; } 36 | 37 | /// Any additional functionality this item has, e.g. contributing to a . 38 | public ICollection Tags { get; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ParquetUnitTests/Parquets/ParquetPackUnitTest.cs: -------------------------------------------------------------------------------- 1 | using Parquet.Parquets; 2 | using Xunit; 3 | 4 | namespace ParquetUnitTests.Parquets 5 | { 6 | /// 7 | /// Unit tests . 8 | /// 9 | public class ParquetPackUnitTest 10 | { 11 | [Fact] 12 | internal void ParquetPackIsEmptyWhenAllFieldsAreNullTest() 13 | { 14 | var pack = new ParquetModelPack(); 15 | 16 | Assert.True(pack.IsEmpty); 17 | } 18 | 19 | [Fact] 20 | internal void ParquetPackDotEmptyIsEmpty() 21 | { 22 | Assert.True(ParquetModelPack.Empty.IsEmpty); 23 | } 24 | 25 | [Fact] 26 | internal void IdenticalStacksAreEqualTest() 27 | { 28 | var pack1 = new ParquetModelPack(TestModels.TestFloor.ID, 29 | TestModels.TestBlock.ID, 30 | TestModels.TestFurnishing.ID, 31 | TestModels.TestCollectible.ID); 32 | var pack2 = new ParquetModelPack(TestModels.TestFloor.ID, 33 | TestModels.TestBlock.ID, 34 | TestModels.TestFurnishing.ID, 35 | TestModels.TestCollectible.ID); 36 | 37 | Assert.Equal(pack1, pack2); 38 | } 39 | 40 | [Fact] 41 | internal void DifferingStacksAreUnequalTest() 42 | { 43 | var pack1 = new ParquetModelPack(TestModels.TestFloor.ID, 44 | TestModels.TestBlock.ID, 45 | TestModels.TestFurnishing.ID, 46 | TestModels.TestCollectible.ID); 47 | var pack2 = new ParquetModelPack(TestModels.TestFloor.ID - 1, 48 | TestModels.TestBlock.ID - 1, 49 | TestModels.TestFurnishing.ID - 1, 50 | TestModels.TestCollectible.ID - 1); 51 | 52 | Assert.NotEqual(pack1, pack2); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Documentation/3-How_Parquets_Work.md: -------------------------------------------------------------------------------- 1 | A **parquet** is the smallest interactive unit of the game world. These are the units with which the player builds and that are available in the map editor. 2 | 3 | The word "parquet" refers to a specific part of game logic, not a graphical component or style. 4 | 5 | Parquets are defined once and used many times throughout the game world's 2D map. Once a type of parquet is defined, each parquet of that type shares all the mechanical attributes of the definition. For example, all silver coin collectibles award the player the same amount of money. 6 | 7 | Some types of parquets, notably *floors* and *blocks*, may have statuses that are particular to a specific location. For example, the block at the player's location may have been damaged while other blocks have not. 8 | 9 | Parquets are normally arranged in **Parquet Packs**. In a ptack, a given location on the map may hold four parquets of different types simultaneously, arranged as follows from top to bottom: 10 | 11 | * Collectibles 12 | * Furnishings 13 | * Blocks 14 | * Floors 15 | 16 | As an example, a Parquet Pack might consist of: 17 | 18 | * a feather Collectible 19 | * on a birdhouse Furnishing 20 | * in a pine tree Block 21 | * growing on a grass forest Floor. 22 | 23 | Another example is: 24 | 25 | * a rubber duck Collectible 26 | * and no Furnishing 27 | * on a water Block 28 | * sitting in a *dug out* porcelain basin Floor. 29 | 30 | The player automatically interacts with Collectibles when moving onto their location. Other parquets require mediation for interaction. 31 | 32 | Furnishings, Blocks, and Floors require tools to be interacted with. These interactions can result in Floors changing state (e.g. *filled in* vs. *dug out*), Blocks becoming Collectibles (chopping down a tree to form wood), or in being directly removed from the map and added to the player's inventory as **Items**. 33 | 34 | When Furnishings and Blocks are transferred from the map to the inventory they are said to be **Gather**ed. 35 | Collectibles are said to be **Collect**ed, since this happens without the mediation of tools. This distinction is important on a game design and implementation level but is invisible to the player. 36 | -------------------------------------------------------------------------------- /ParquetUnitTests/ITypeConverterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using CsvHelper.TypeConversion; 5 | using Parquet; 6 | using Xunit; 7 | 8 | namespace ParquetUnitTests 9 | { 10 | /// 11 | /// Tests that classes implementing also provide a ConverterFactory static property. 12 | /// 13 | public class ITypeConverterTest 14 | { 15 | /// 16 | /// Ensures that all concrete classes implementing the ITypeConverter interface also implement the ConverterFactory static property. 17 | /// 18 | [Fact] 19 | internal void AllTypeConvertersProvideFactoriesTest() 20 | { 21 | // This discarded value is here to ensure that ParquetClassLibrary is loaded. 22 | var _ = new Point2D(1, 2); 23 | var converterProviders = AppDomain.CurrentDomain 24 | .GetAssemblies() 25 | .Where(assembly => assembly.GetName().Name == "ParquetClassLibrary") 26 | .SelectMany(assembly => assembly.GetExportedTypes()) 27 | .Where(type => typeof(ITypeConverter).IsAssignableFrom(type) 28 | && !type.IsInterface 29 | && !type.IsAbstract); 30 | 31 | var factoryProviders = AppDomain.CurrentDomain 32 | .GetAssemblies() 33 | .Where(assembly => assembly.GetName().Name == "ParquetClassLibrary") 34 | .SelectMany(assembly => assembly.GetExportedTypes()) 35 | .Where(type => type.GetRuntimeProperties() 36 | .Any(info => info.Name == nameof(ModelTag.ConverterFactory))); 37 | 38 | foreach (var provider in converterProviders) 39 | { 40 | Assert.Contains(provider, factoryProviders); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ParquetUnitTests/Point2DUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Xunit; 4 | 5 | namespace ParquetUnitTests 6 | { 7 | /// 8 | /// Unit tests . 9 | /// 10 | public class Point2DUnitTest 11 | { 12 | #region Test Values 13 | private readonly Point2D PointTwoTwo = new(2, 2); 14 | private readonly Point2D PointNegativeUnit = new(-1, -1); 15 | #endregion 16 | 17 | [Fact] 18 | internal void ZeroPointTest() 19 | { 20 | Assert.Equal(0, Point2D.Origin.X); 21 | Assert.Equal(0, Point2D.Origin.Y); 22 | Assert.Equal(0, Point2D.Origin.Magnitude); 23 | } 24 | 25 | [Fact] 26 | internal void UnitPointTest() 27 | { 28 | Assert.Equal(1, Point2D.Unit.X); 29 | Assert.Equal(1, Point2D.Unit.Y); 30 | Assert.Equal(1, Point2D.Unit.Magnitude); 31 | } 32 | 33 | [Theory] 34 | [InlineData(-4096, -4096)] 35 | [InlineData(-1, 1)] 36 | [InlineData(0, 0)] 37 | [InlineData(1, -1)] 38 | [InlineData(4096, 4096)] 39 | internal void NewPointTest(int x, int y) 40 | { 41 | var testPoint = new Point2D(x, y); 42 | var magnitude = Convert.ToInt32(Math.Floor(Math.Sqrt((x * x) + (y * y)))); 43 | 44 | Assert.Equal(x, testPoint.X); 45 | Assert.Equal(y, testPoint.Y); 46 | Assert.Equal(magnitude, testPoint.Magnitude); 47 | } 48 | 49 | [Fact] 50 | internal void PointSumTest() 51 | { 52 | Assert.Equal(PointTwoTwo, Point2D.Unit + Point2D.Unit); 53 | Assert.Equal(Point2D.Origin, Point2D.Unit + PointNegativeUnit); 54 | } 55 | 56 | [Fact] 57 | internal void PointDifferenceTest() 58 | { 59 | Assert.Equal(Point2D.Origin, Point2D.Unit - Point2D.Unit); 60 | Assert.Equal(Point2D.Origin, PointNegativeUnit - PointNegativeUnit); 61 | } 62 | 63 | [Fact] 64 | internal void ScalarMultiplicationTest() 65 | { 66 | Assert.Equal(PointTwoTwo, 2 * Point2D.Unit); 67 | Assert.Equal(Point2D.Unit, -1 * PointNegativeUnit); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ParquetClassLibrary/IDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using Parquet.Properties; 5 | 6 | namespace Parquet 7 | { 8 | /// 9 | /// Provides extension methods to . 10 | /// 11 | internal static class IDictionaryExtensions 12 | { 13 | /// Clears the dictionary, then attempts to add all the given elements. 14 | /// The being altered. 15 | /// The new content for the . 16 | /// true if every add succeeded; false otherwise. 17 | public static bool TryReplaceWith(this IDictionary dictionaryToAlter, IReadOnlyDictionary keyValuePairs) 18 | { 19 | try 20 | { 21 | dictionaryToAlter.ReplaceWith(keyValuePairs); 22 | } 23 | catch (Exception replaceException) 24 | { 25 | Logger.Log(LogLevel.Error, 26 | string.Format(CultureInfo.CurrentCulture, Resources.ErrorCannotReplaceDictionary, replaceException.Message), 27 | replaceException); 28 | 29 | return false; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | /// Clears the dictionary, then adds all the given elements. 36 | /// The being altered. 37 | /// The new content for the . 38 | /// true if every add succeeded; false otherwise. 39 | internal static void ReplaceWith(this IDictionary dictionaryToAlter, IReadOnlyDictionary keyValuePairs) 40 | { 41 | dictionaryToAlter.Clear(); 42 | foreach (KeyValuePair kvp in keyValuePairs ?? new Dictionary()) 43 | { 44 | dictionaryToAlter[kvp.Key] = kvp.Value; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ParquetUnitTests/Regions/RegionModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using Parquet; 2 | using Parquet.Regions; 3 | using Xunit; 4 | 5 | namespace ParquetUnitTests.Maps 6 | { 7 | /// 8 | /// Unit tests . 9 | /// 10 | public class RegionModelUnitTest 11 | { 12 | #region Values for Tests 13 | private const string testColor = "#FF8822EE"; 14 | private const string testName = "Test Region"; 15 | private static readonly RegionModel defaultRegion = new(TestModels.TestRegionModel.ID - 1, nameof(defaultRegion), "", ""); 16 | #endregion 17 | 18 | #region Region Map Initialization 19 | [Fact] 20 | internal void NewDefaultMapRegionModelTest() 21 | { 22 | Assert.Equal(RegionModel.DefaultColor, defaultRegion.BackgroundColor); 23 | } 24 | 25 | [Fact] 26 | internal void NewCustomMapRegionModelTest() 27 | { 28 | var customRegion = new RegionModel(TestModels.TestRegionModel.ID - 1, testName, "", "", null, testColor); 29 | 30 | Assert.Equal(testName, customRegion.Name); 31 | Assert.Equal(testColor, customRegion.BackgroundColor); 32 | } 33 | #endregion 34 | 35 | #region Whole Region Characteristics Editing 36 | #pragma warning disable IDE0079 // Remove unnecessary suppression -- conditional compilation. 37 | #pragma warning disable CS0162 // Unreachable code detected -- conditional compilation. 38 | [Fact] 39 | internal void MapRegionModelMayBeEditedTest() 40 | { 41 | if (LibraryState.IsDebugMode) 42 | { 43 | var customRegion = new RegionModel(TestModels.TestRegionModel.ID - 1, testName, "", "", null, testColor); 44 | IMutableRegionModel editableRegion = customRegion; 45 | 46 | editableRegion.Name = testName; 47 | editableRegion.BackgroundColor = testColor; 48 | 49 | Assert.Equal(testName, customRegion.Name); 50 | Assert.Equal(testColor, customRegion.BackgroundColor); 51 | } 52 | } 53 | #pragma warning restore CS0162 // Unreachable code detected -- conditional compilation. 54 | #pragma warning restore IDE0079 // Remove unnecessary suppression -- conditional compilation. 55 | #endregion 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Items/InventoryConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace Parquet.Items 5 | { 6 | /// 7 | /// Provides rules for working with an . 8 | /// 9 | public static class InventoryConfiguration 10 | { 11 | #region Class Defaults 12 | /// The capacity to use for an when the configuration cannot be read. 13 | private const int FallbackCapacity = 16; 14 | #endregion 15 | 16 | #region Characteristics 17 | /// The capacity to use for an when none is specified. 18 | public static int DefaultCapacity { get; set; } = FallbackCapacity; 19 | #endregion 20 | 21 | #region Self Serialization 22 | /// 23 | /// Reads data from the appropriate file. 24 | /// 25 | /// The instances read. 26 | public static void GetRecord() 27 | { 28 | using var reader = new StreamReader(FilePath); 29 | 30 | // Skip the header. 31 | reader.ReadLine(); 32 | 33 | // Read in the value. 34 | var value = reader.ReadLine(); 35 | 36 | // Parse. 37 | DefaultCapacity = int.TryParse(value, out var parsedCapacity) 38 | ? parsedCapacity 39 | : Logger.DefaultWithParseLog(value, nameof(DefaultCapacity), FallbackCapacity); 40 | } 41 | 42 | /// 43 | /// Writes data to the appropriate file. 44 | /// 45 | public static void PutRecord() 46 | { 47 | using var writer = new StreamWriter(FilePath, false, new UTF8Encoding(true, true)); 48 | writer.Write($"{nameof(DefaultCapacity)}\n"); 49 | writer.Write($"{DefaultCapacity}\n"); 50 | } 51 | 52 | /// 53 | /// Returns the filename and path associated with 's definition file. 54 | /// 55 | /// A full path to the associated file. 56 | public static string FilePath 57 | => $"{All.ProjectDirectory}/{nameof(InventoryConfiguration)}.csv"; 58 | #endregion 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Delimiters.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet 2 | { 3 | /// 4 | /// Provides a unified source of serialization separators for the library. 5 | /// 6 | /// 7 | /// Every string listed here is reserved by Parquet and cannot be stored in any 8 | /// or other library type.
9 | /// Doing so will cause deserialization to fail. 10 | ///
11 | public static class Delimiters 12 | { 13 | // NOTE that currently delimiters are not stored as chars because PronounGroup requires compile-time 14 | // delimiter concatenation, which is currently only possible with strings. (Still true in .NET 6/C# 10.) 15 | 16 | /// Separator for encoding the dimensions of implementations. 17 | public const string DimensionalDelimiter = "×"; 18 | 19 | /// Separator for introducting the content of an implementations. 20 | public const string DimensionalTerminator = "≡"; 21 | 22 | /// Separates primitives within serialized s and s. 23 | public const string ElementDelimiter = "–"; 24 | 25 | /// Separates properties within a class when in serialization. 26 | public const string InternalDelimiter = "·"; 27 | 28 | /// Separates metadata within serialized s. 29 | public const string MapChunkDelimiter = "∟"; 30 | 31 | /// Separates family and personal names within serialized s. 32 | public const string NameDelimiter = "§"; 33 | 34 | /// Marks out tags that need to be replaced with pronouns from a s. 35 | public const string PronounDelimiter = "|"; 36 | 37 | /// Separates collections within files. 38 | public const string PrimaryDelimiter = ","; 39 | 40 | /// Separates objects within unnested, non- collections. 41 | public const string SecondaryDelimiter = "❟"; 42 | 43 | /// Separates objects within nested or paired collections. 44 | public const string TertiaryDelimiter = "❠"; 45 | 46 | /// Separates objects within s. 47 | public const string PackDelimiter = "⚭"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ParquetUnitTests/Items/InventorySlotUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Parquet.Items; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests.Items 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class InventorySlotUnitTest 12 | { 13 | #region Test Values 14 | private const int None = 0; 15 | private const int One = 1; 16 | private const int Some = 10; 17 | #endregion 18 | 19 | [Fact] 20 | internal void CannotStoreSomeOfNothingTest() 21 | { 22 | static void TestCode() 23 | { 24 | var _ = new InventorySlot(ModelID.None, Some); 25 | } 26 | 27 | Assert.Throws(TestCode); 28 | } 29 | 30 | [Fact] 31 | internal void CannotStoreNoneOfSomethingTest() 32 | { 33 | static void TestCode() 34 | { 35 | var _ = new InventorySlot(TestModels.TestItem1.ID, None); 36 | } 37 | 38 | Assert.Throws(TestCode); 39 | } 40 | 41 | [Fact] 42 | internal void CannotStoreNonItemTest() 43 | { 44 | static void TestCode() 45 | { 46 | var _ = new InventorySlot(TestModels.TestBlock.ID, Some); 47 | } 48 | 49 | Assert.Throws(TestCode); 50 | } 51 | 52 | 53 | [Fact] 54 | internal void CountMatchesItemsStoredTest() 55 | { 56 | var slot = new InventorySlot(TestModels.TestItem1.ID, Some); 57 | 58 | Assert.Equal(Some, slot.Count); 59 | } 60 | 61 | [Fact] 62 | internal void SlotAddTest() 63 | { 64 | var slot = new InventorySlot(TestModels.TestItem1.ID, One); 65 | var remainder1 = 0; 66 | var remainder2 = One + Some; 67 | 68 | Assert.Equal(remainder1, slot.Give(Some)); 69 | Assert.Equal(remainder2, slot.Give(TestModels.TestItem1.StackMax)); 70 | } 71 | 72 | [Fact] 73 | internal void SlotRemoveTest() 74 | { 75 | var slot = new InventorySlot(TestModels.TestItem1.ID, Some); 76 | var remainder1 = 0; 77 | var remainder2 = TestModels.TestItem1.StackMax - (Some - One); 78 | 79 | Assert.Equal(remainder1, slot.Take(One)); 80 | Assert.Equal(remainder2, slot.Take(TestModels.TestItem1.StackMax)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ParquetUnitTests/Items/ItemModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Parquet; 4 | using Parquet.Items; 5 | using Xunit; 6 | 7 | namespace ParquetUnitTests.Items 8 | { 9 | /// 10 | /// Unit tests . 11 | /// 12 | public class ItemModelUnitTest 13 | { 14 | #region Test Values 15 | /// Identifier used when creating a new block. 16 | private static readonly ModelID newItemID = TestModels.TestItem1.ID - 1; 17 | 18 | /// A valid number of s to stack. 19 | private const int goodStackMax = 99; 20 | 21 | /// 22 | private static readonly IList TestTagList = new List() { "Test-Tag" }; 23 | #endregion 24 | 25 | [Fact] 26 | internal void ValidItemIDsArePermittedTest() 27 | { 28 | var newItem = new ItemModel(newItemID, "will be created", "", "", TestTagList, ItemType.Consumable, 29 | 1, 1, goodStackMax, 0, 0, TestModels.TestBlock.ID); 30 | 31 | Assert.NotNull(newItem); 32 | } 33 | 34 | [Fact] 35 | internal void InvalidItemIDsRaiseExceptionTest() 36 | { 37 | var badItemID = TestModels.TestBlock.ID - 1; 38 | 39 | void TestCode() 40 | { 41 | var _ = new ItemModel(badItemID, "will fail", "", "", TestTagList, ItemType.Consumable, 42 | 1, 1, goodStackMax, 0, 0, TestModels.TestBlock.ID); 43 | } 44 | 45 | Assert.Throws(TestCode); 46 | } 47 | 48 | [Fact] 49 | internal void StackMaxMustBePositiveTest() 50 | { 51 | var badStackMaxZero = 0; 52 | var badStackMaxNegativeOne = -1; 53 | 54 | void TestCodeZero() 55 | { 56 | var _ = new ItemModel(newItemID, "will fail", "", "", TestTagList, ItemType.Consumable, 57 | 1, 1, badStackMaxZero, 0, 0, TestModels.TestBlock.ID); 58 | } 59 | 60 | void TestCodeNegativeOne() 61 | { 62 | var _ = new ItemModel(newItemID, "will fail", "", "", TestTagList, ItemType.Consumable, 63 | 1, 1, badStackMaxNegativeOne, 0, 0, TestModels.TestBlock.ID); 64 | } 65 | 66 | Assert.Throws(TestCodeZero); 67 | Assert.Throws(TestCodeNegativeOne); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Beings/CritterModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Parquet.Beings 4 | { 5 | /// 6 | /// Models the definition for a simple in-game actor, such as a friendly mob with limited interaction. 7 | /// 8 | public class CritterModel : BeingModel, IMutableCritterModel 9 | { 10 | #region Class Defaults 11 | /// Indicates an uninitialized critter. 12 | public static CritterModel Unused { get; } = new CritterModel(ModelID.None, nameof(Unused), "", ""); 13 | #endregion 14 | 15 | #region Initialization 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// 20 | /// Unique identifier for the . Cannot be null. 21 | /// Must be a . 22 | /// 23 | /// Player-friendly name of the . Cannot be null or empty. 24 | /// Player-friendly description of the . 25 | /// Comment of, on, or by the . 26 | /// Any additional information about the . 27 | /// The in which this is most comfortable. 28 | /// The rules that govern how this acts. Cannot be null. 29 | /// Any parquets this avoids. 30 | /// Any parquets this seeks. 31 | public CritterModel(ModelID id, string name, string description, string comment, 32 | IEnumerable tags = null, ModelID? nativeBiomeID = null, 33 | ModelID? primaryBehaviorID = null, IEnumerable avoidsIDs = null, 34 | IEnumerable seeksIDs = null) 35 | : base(All.CritterIDs, id, name, description, comment, tags, 36 | nativeBiomeID, primaryBehaviorID, avoidsIDs, seeksIDs) 37 | => Precondition.IsInRange(id, All.CritterIDs, nameof(id)); 38 | #endregion 39 | 40 | #region IMutableCritterModel Implementation 41 | // Currently, everything needed for editing CritterModels is provided by IBeingModelEdit. 42 | #endregion 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Beings/IMutableCharacterModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Parquet.Items; 3 | 4 | namespace Parquet.Beings 5 | { 6 | /// 7 | /// Facilitates editing of a from design tools while maintaining a read-only face for use during play. 8 | /// 9 | /// 10 | /// By design, subtypes of should never themselves use . 11 | /// IMutableCharacterModel is for use only by external types that require read/write access to model properties. 12 | /// 13 | public interface IMutableCharacterModel : IMutableBeingModel 14 | { 15 | /// Player-facing personal name. 16 | public string PersonalName { get; set; } 17 | 18 | /// Player-facing family name. 19 | public string FamilyName { get; set; } 20 | 21 | /// 22 | /// A key for the the uses, 23 | /// stored as "/. 24 | /// 25 | public string PronounKey { get; set; } 26 | 27 | /// The story character that this represents. 28 | /// 29 | /// This identifier provides a link between software character classes 30 | /// and the characters written of in a game's narrative that they represent. The goal 31 | /// is that these identifiers be able to span any number of shipped titles, allowing a 32 | /// sequel title to import data from prior titles in such a way that one game's NPC 33 | /// can become another game's protagonist. 34 | /// 35 | public string StoryCharacterID { get; set; } 36 | 37 | /// The the begins at. 38 | public Location StartingLocation { get; set; } 39 | 40 | /// The s that this either offers or has undertaken. 41 | /// Typically, NPCs offer quests, player characters undertake them. 42 | public ICollection StartingQuestIDs { get; } 43 | 44 | /// Dialogue lines this can say. 45 | public ModelID StartingDialogueID { get; set; } 46 | 47 | /// The set of belongings that this begins with. 48 | public InventoryCollection StartingInventory { get; } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ParquetUnitTests/Parquets/BlockModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Parquet.Parquets; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests.Parquets 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class BlockModelUnitTest 12 | { 13 | #region Test Values 14 | /// Identifier used when creating a new block. 15 | private static readonly ModelID newBlockID = TestModels.TestBlock.ID - 1; 16 | #endregion 17 | 18 | [Fact] 19 | internal void ValidBlockIDsArePermittedTest() 20 | { 21 | var testBlock = new BlockModel(newBlockID, "will be created", "", ""); 22 | 23 | Assert.NotNull(testBlock); 24 | } 25 | 26 | [Fact] 27 | internal void InvalidBlockIDsRaiseExceptionTest() 28 | { 29 | var badBlockID = TestModels.TestFloor.ID; 30 | 31 | void TestCode() 32 | { 33 | var _ = new BlockModel(badBlockID, "will fail", "", ""); 34 | } 35 | 36 | Assert.Throws(TestCode); 37 | } 38 | 39 | [Fact] 40 | internal void ValidItemIDsArePermittedTest() 41 | { 42 | ModelID goodItemID = -All.ItemIDs.Minimum; 43 | 44 | var testBlock = new BlockModel(newBlockID, "will be created", "", "", null, goodItemID); 45 | 46 | Assert.NotNull(testBlock); 47 | } 48 | 49 | [Fact] 50 | internal void InvalidItemIDsRaiseExceptionTest() 51 | { 52 | var badItemID = TestModels.TestBlock.ID; 53 | 54 | void TestCode() 55 | { 56 | var _ = new BlockModel(newBlockID, "will fail", "", "", null, badItemID); 57 | } 58 | 59 | Assert.Throws(TestCode); 60 | } 61 | 62 | [Fact] 63 | internal void ValidCollectibleIDsArePermittedTest() 64 | { 65 | var goodCollectibleID = TestModels.TestCollectible.ID; 66 | 67 | var testBlock = new BlockModel(newBlockID, "will be created", "", "", collectibleID: goodCollectibleID); 68 | 69 | Assert.NotNull(testBlock); 70 | } 71 | 72 | [Fact] 73 | internal void InvalidCollectibleIDsRaiseExceptionTest() 74 | { 75 | var badCollectibleID = TestModels.TestBlock.ID; 76 | 77 | void TestCode() 78 | { 79 | var _ = new BlockModel(newBlockID, "will fail", "", "", collectibleID: badCollectibleID); 80 | } 81 | 82 | Assert.Throws(TestCode); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ParquetUnitTests/Parquets/FurnishingModelUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Parquet; 3 | using Parquet.Parquets; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests.Parquets 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class FurnishingModelUnitTest 12 | { 13 | #region Test Values 14 | /// Identifier used when creating a new furnishing. 15 | private static readonly ModelID newFurnishingID = TestModels.TestFurnishing.ID - 1; 16 | #endregion 17 | 18 | [Fact] 19 | internal void ValidCollectibleIDsArePermittedTest() 20 | { 21 | var testFurnishing = new FurnishingModel(newFurnishingID, "will be created", "", ""); 22 | 23 | Assert.NotNull(testFurnishing); 24 | } 25 | 26 | [Fact] 27 | internal void InvalidCollectibleIDsRaiseExceptionTest() 28 | { 29 | var badFurnishingID = TestModels.TestBlock.ID; 30 | 31 | void TestCode() 32 | { 33 | var _ = new FurnishingModel(badFurnishingID, "will fail", "", ""); 34 | } 35 | 36 | Assert.Throws(TestCode); 37 | } 38 | 39 | [Fact] 40 | internal void ValidItemIDsArePermittedTest() 41 | { 42 | ModelID goodItemID = -All.ItemIDs.Minimum; 43 | 44 | var testBlock = new FurnishingModel(newFurnishingID, "will be created", "", "", null, goodItemID); 45 | 46 | Assert.NotNull(testBlock); 47 | } 48 | 49 | [Fact] 50 | internal void InvalidItemIDsRaiseExceptionTest() 51 | { 52 | var badItemID = TestModels.TestBlock.ID; 53 | 54 | void TestCode() 55 | { 56 | var _ = new FurnishingModel(newFurnishingID, "will fail", "", "", null, badItemID); 57 | } 58 | 59 | Assert.Throws(TestCode); 60 | } 61 | 62 | [Fact] 63 | internal void ValidSwapIDsArePermittedTest() 64 | { 65 | var goodItemID = newFurnishingID - 1; 66 | 67 | var testBlock = new FurnishingModel(newFurnishingID, "will be created", "", "", itemID: goodItemID); 68 | 69 | Assert.NotNull(testBlock); 70 | } 71 | 72 | [Fact] 73 | internal void InvalidSwapIDsRaiseExceptionTest() 74 | { 75 | var badItemID = TestModels.TestBlock.ID; 76 | 77 | void TestCode() 78 | { 79 | var _ = new FurnishingModel(newFurnishingID, "will fail", "", "", itemID: badItemID); 80 | } 81 | 82 | Assert.Throws(TestCode); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Scripts/Commands.cs: -------------------------------------------------------------------------------- 1 | namespace Parquet.Scripts 2 | { 3 | /// 4 | /// IDs for commands used in s. 5 | /// 6 | /// Note that these commands are encoded in upper case, but the library ignores case when reading or comparing them. 7 | public static class Commands 8 | { 9 | /// Indicates non-command. NOP. 10 | public const string None = ""; 11 | 12 | /// Display the given text as an alert by the user interface. 13 | public const string Alert = "A"; 14 | 15 | /// Calls the given to stand near another given . 16 | public const string CallCharacter = "C"; 17 | 18 | /// Lower the given flag. 19 | public const string ClearFlag = "!"; 20 | 21 | /// Allot the given number and type of to the given . 22 | public const string GiveItem = "I"; 23 | 24 | /// Allot the given to the given . 25 | public const string GiveQuest = "Q"; 26 | 27 | /// Immediately load and begin processing the given . 28 | public const string Jump = "J"; 29 | 30 | /// If the given variable is set, load and begin processing the given . 31 | public const string JumpIf = "F"; 32 | 33 | /// Place the given at the given . 34 | public const string Put = "P"; 35 | 36 | /// Display the given text as dialogue spoken by the given . 37 | public const string Say = "S"; 38 | 39 | /// Allot the given to the given . 40 | public const string SetBehavior = "B"; 41 | 42 | /// Allot the given to the given . 43 | public const string SetDialogue = "D"; 44 | 45 | /// Allot the given to the given . 46 | public const string SetPronoun = "R"; 47 | 48 | /// Raise the given flag. 49 | public const string SetFlag = "="; 50 | 51 | /// Highlight the given via the UI, perhaps by camera movement or particle effect. 52 | public const string ShowLocation = "L"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | This file reflects changes at each project milestone. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/). 6 | 7 | 8 | 22 | 23 | ## [0.4.0] - 2021-02-23 24 | ### Pre-Alpha 3 API Revision 25 | - This update changes the API in far-reaching ways in order to separate design-time and play-time concerns in map-related types. 26 | #### Adds 27 | - Adds Status type as a base class for all mutable-at-play types, analogous to Models. 28 | - Derives a Status-based partner class for most Model classes. 29 | - Adds Pack container type as base for ParquetModelPack and ParquetStatusPack. 30 | - Adds LibraryState static class to track build and run characteristics, particularly Debug-vs-Release and Play-vs-Edit. 31 | #### Removes 32 | - MapModel abstract class, as there is only one map-related Model in the new hierarchy. 33 | #### Changes 34 | - Merges the definitional elements (procedural generation instructions and metadata) of MapRegion and MapSketch into single RegionModel. 35 | - Merges the mutable elements (parquets and their statuses) of MapRegion and MapSketch into a single RegionStatus class. 36 | - Changes MapChunk to a stand-alone class rather than a Model subtype. 37 | - Renames namespace from Map to Region. 38 | - Alters the BeingModel class family to better interoperate with the new RegionModel class family. 39 | - Changes the name of Inventory class to InventoryCollection to avoid confusion in the BeingStatus classes. 40 | - Updates the serialized example data for RegionModel and BeingModel class families. 41 | - Reimplements cloning to enforce deep copying. 42 | - Many minor fixes and corrections along the way. 43 | 44 | ## [0.3.56] - 2021-02-12 45 | #### Adds 46 | - General purpose Tags collection to the base Model type. 47 | #### Removes 48 | - ItemModel.ItemTags collection, as Model.Tags makes it redundant. 49 | 50 | ## [0.3.52] - 2021-01-29 51 | #### Removes 52 | - Roller command line utility. It is now built as part of Scribe instead. 53 | 54 | ## [0.3.0] - 2020-08-11 55 | ### Pre-Alpha 2 Milestone 56 | #### Added 57 | - Major mechanical systems. 58 | #### Changes 59 | - Many small changes to support Scribe GUI editor. 60 | 61 | ## [0.2.0] - 2020-04-18 62 | ### Pre-Alpha 1 Milestone 63 | #### Added 64 | - Models for all major game systems. 65 | - Roller command line utility. 66 | 67 | ## [0.1.0] - 2019-01-28 68 | #### Added 69 | - Initial commit. 70 | 71 | ## [0.0.0] - 2018-12-05 72 | #### Project begun. 73 | -------------------------------------------------------------------------------- /ParquetClassLibrary/EmptyTolerantEnumConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using CsvHelper; 4 | using CsvHelper.Configuration; 5 | using CsvHelper.TypeConversion; 6 | using Parquet.Properties; 7 | 8 | namespace Parquet 9 | { 10 | /// 11 | /// Converts an to and from a 12 | /// providing sensible default values in case of null or the . 13 | /// 14 | public class EmptyTolerantEnumConverter : EnumConverter 15 | { 16 | /// Stores the type information for the kind of enumeration being converted. 17 | private readonly Type EnumType; 18 | 19 | /// 20 | /// Creates a new for the given . 21 | /// 22 | /// The type of the enumeration. 23 | public EmptyTolerantEnumConverter(Type type) 24 | : base(type) 25 | => EnumType = (type?.IsEnum ?? false) 26 | ? type 27 | : DefaultWithCastLog(nameof(type), type?.Name ?? "null", nameof(Enum), default(Type)); 28 | 29 | /// 30 | /// Convenience method that logs a casting error and returns the given default value. 31 | /// This is a fatal error as the library will be unable to handle a default type in place of an enumeration. 32 | /// 33 | /// The type of value to return. 34 | /// The value that could not be case. 35 | /// The name of type received. 36 | /// The name of type expected. 37 | /// The default value to return. 38 | /// The default value given. 39 | private static T DefaultWithCastLog(string value, string actualTypeName, string expectedTypeName, T defaultValue) 40 | { 41 | Logger.Log(LogLevel.Fatal, string.Format(CultureInfo.CurrentCulture, Resources.ErrorInvalidCast, 42 | value, actualTypeName, expectedTypeName), null); 43 | 44 | return defaultValue; 45 | } 46 | 47 | /// 48 | /// Converts the string to an object. 49 | /// 50 | /// The string to convert. 51 | /// The for the current record. 52 | /// The for the member being created. 53 | /// The object created from the string. 54 | public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) 55 | => string.IsNullOrEmpty(text) 56 | ? Activator.CreateInstance(EnumType) 57 | : base.ConvertFromString(text, row, memberMapData); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ParquetRunner/ParquetRunner.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ParquetRunner 6 | 7 | Runner 8 | ParquetRunner 9 | 0.4.12.0 10 | 0.4.0.0 11 | 0.4.12.0 12 | Parquet Pre-Alpha 3 13 | 14 | Paige Ashlynn 15 | Girl Potion 16 | A simple smoke test routine. 17 | 2018-2022 Paige Ashlynn 18 | ParquetIcon.ico 19 | Parquet Logo Large.png 20 | LICENSE.txt 21 | http://parquet.gay/ 22 | https://github.com/mxashlynn/Parquet 23 | git 24 | 25 | This code is not meant for external use. 26 | en-US 27 | 28 | Exe 29 | net6.0 30 | latest 31 | CS1701;CS1702 32 | False 33 | NU1605;CS1591 34 | True 35 | AllEnabledByDefault 36 | latest 37 | 5 38 | warnings 39 | True 40 | ParquetRunner 41 | ParquetRunner.RunnerProgram 42 | 43 | False 44 | False 45 | False 46 | Off 47 | False 48 | True 49 | Debug;Release 50 | bin\ 51 | disable 52 | send 53 | 54 | 55 | 56 | 57 | DEBUG 58 | 59 | 60 | 61 | TRACE 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | True 71 | \ 72 | 73 | 74 | True 75 | \ 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Rooms/RoomConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace Parquet.Rooms 6 | { 7 | /// 8 | /// Provides parameters for s. 9 | /// 10 | public static class RoomConfiguration 11 | { 12 | #region Class Defaults 13 | /// Minimum walkable spaces to use if the configuration cannot be read. 14 | private const int DefaultMinWalkableSpaces = 4; 15 | 16 | /// Maximum walkable spaces to use if the configuration cannot be read. 17 | private const int DefaultMaxWalkableSpaces = 121; 18 | #endregion 19 | 20 | #region Characteristics 21 | /// Minimum number of open walkable spaces needed for any room to register. 22 | public static int MinWalkableSpaces { get; set; } = DefaultMinWalkableSpaces; 23 | 24 | /// Maximum number of open walkable spaces needed for any room to register. 25 | public static int MaxWalkableSpaces { get; set; } = DefaultMaxWalkableSpaces; 26 | 27 | /// Minimum number of enclosing spaces needed for any room to register. 28 | public static int MperimeterSpaces => MinWalkableSpaces * 3; 29 | #endregion 30 | 31 | #region Self Serialization 32 | /// 33 | /// Reads data from the appropriate file. 34 | /// 35 | /// The instances read. 36 | public static void GetRecord() 37 | { 38 | using var reader = new StreamReader(FilePath); 39 | 40 | // Skip the header. 41 | reader.ReadLine(); 42 | // Read in the values. 43 | var valueLine = reader.ReadLine(); 44 | var values = valueLine.Split(Delimiters.PrimaryDelimiter); 45 | 46 | // Parse. 47 | MinWalkableSpaces = int.TryParse(values[0], All.SerializedNumberStyle, CultureInfo.InvariantCulture, out var temp1) 48 | ? temp1 49 | : Logger.DefaultWithParseLog(values[0], nameof(MinWalkableSpaces), DefaultMinWalkableSpaces); 50 | MaxWalkableSpaces = int.TryParse(values[1], All.SerializedNumberStyle, CultureInfo.InvariantCulture, out var temp2) 51 | ? temp2 52 | : Logger.DefaultWithParseLog(values[1], nameof(MaxWalkableSpaces), DefaultMinWalkableSpaces); 53 | } 54 | 55 | /// 56 | /// Writes data to the appropriate file. 57 | /// 58 | public static void PutRecord() 59 | { 60 | using var writer = new StreamWriter(FilePath, false, new UTF8Encoding(true, true)); 61 | writer.Write($"{nameof(MinWalkableSpaces)}{Delimiters.PrimaryDelimiter}{nameof(MaxWalkableSpaces)}\n"); 62 | writer.Write($"{MinWalkableSpaces}{Delimiters.PrimaryDelimiter}{MaxWalkableSpaces}\n"); 63 | } 64 | 65 | /// 66 | /// Returns the filename and path associated with 's definition file. 67 | /// 68 | /// A full path to the associated file. 69 | public static string FilePath 70 | => $"{All.ProjectDirectory}/{nameof(RoomConfiguration)}.csv"; 71 | #endregion 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Scripts/ScriptModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using CsvHelper.Configuration.Attributes; 6 | 7 | namespace Parquet.Scripts 8 | { 9 | /// 10 | /// Models a series of imperative, procedural commands. 11 | /// This might be an AI behavior, for example. 12 | /// 13 | public class ScriptModel : Model, IMutableScriptModel 14 | { 15 | #region Characteristics 16 | /// A series of imperative, procedural commands. 17 | // TODO [SCRIPTING] This likely needs to be a sorted list. Is there an IReadOnlySortedList? 18 | // ANSWER: The closest thing I can find is SortedList which only implements ISortedCollection 19 | // and ISortedDictionary. See for more: https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedlist-2?view=net-5.0 20 | [Index(5)] 21 | public IReadOnlyList Nodes { get; } 22 | #endregion 23 | 24 | #region Initialization 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// Unique identifier for the . Cannot be null. 29 | /// Player-friendly name of the . Cannot be null or empty. 30 | /// Player-friendly description of the . 31 | /// Comment of, on, or by the . 32 | /// Any additional information about this . 33 | /// Describes the criteria for completing this . 34 | public ScriptModel(ModelID id, string name, string description, string comment, 35 | IEnumerable tags = null, IEnumerable nodes = null) 36 | : base(All.ScriptIDs, id, name, description, comment, tags) 37 | => Nodes = (nodes ?? Enumerable.Empty()).ToList(); 38 | #endregion 39 | 40 | #region IMutableScriptModel Implementation 41 | /// A series of imperative, procedural commands. 42 | /// 43 | /// By design, subtypes of should never themselves use . 44 | /// IMutableScriptModel is for external types that require read/write access. 45 | /// 46 | [Ignore] 47 | ICollection IMutableScriptModel.Nodes 48 | => LibraryState.IsPlayMode 49 | ? Logger.DefaultWithImmutableInPlayLog(nameof(Nodes), new Collection()) 50 | : (ICollection)Nodes; 51 | #endregion 52 | 53 | #region Utilities 54 | /// 55 | /// Yields an for each , in order. 56 | /// 57 | /// The action to perform for the current node. 58 | public IEnumerable GetActions() 59 | { 60 | for (var i = 0; i < Nodes.Count; i++) 61 | { 62 | yield return Nodes[i].GetAction(); 63 | } 64 | } 65 | #endregion 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Documentation/Lessons_Learned_Living_Document.md: -------------------------------------------------------------------------------- 1 | # These are some lessons learned during the course of the project's development: 2 | 3 | * As of .Net Core 2.2, the System.Console class does not offer a robust means of interacting with terminals on MacOS or Linux. For this reason, the decision was made to forgo a textmode library runner for the time being and focus on building graphical tools. 4 | 5 | * Because of the timing problems inherent in calling methods defined in child classes from parent classes in C#, there is no clean or simple way to ensure in an abstract class that all children classes implement a particular named, field-like, unchanging, readonly value that is defined in the children classes AND can be relied on in the parent’s constructor. For this reason, the parquet classes that inherit from ParquetParent pass constant values needed for runtime ID validation to their base class as part of their Constructor. 6 | 7 | * Because there may be at least as many items as parquets the ItemID range has been defined using reflection to determine the amount of space in the parquet ranges. 8 | 9 | * Visual Studio 2019 seems to occasionally not run unit tests as expected, instead reporting old results stored in the cache. Clearing the cache folder does the trick. This post explains how clearing the test cache can be made a part of the project Clean build step: https://blogs.msdn.microsoft.com/ploeh/2006/07/13/cleaning-away-the-testresults-folder/ 10 | 11 | * I considered merging MapSpace with BeingLocation because they are nearly the same thing, but in this case the domains where they are used are so distinct it seemed wiser to reflect in their comments where and how they are used. This decision can easily be changed down the road if the domains do end up overlapping. 12 | 13 | * .Net Core doesn't support loading unmanaged libraries as well as Mono or .Net Framework did. Specifically, as of Jan 2020 .Net Core does not automatically map libraries with one name on one platform to another name on another platform. Worse yet, the library import code does not allow the client developer to provide the mapping either. There are various workarounds. But it may be easiest simply to retarget from Core to Framework where possible when it comes time to release. This situation may be a nonissue by the time Parquet is ready. 14 | 15 | * Since C# does not support static methods in interfaces and also cannot invoke static methods using a generic type argument, one way to support access to class-scoped members within a generic method is to provide each implementing class of a given interface with a static method, and then compose a unit test to ensure via reflection that all classes that implement the interface do indeed implement a method with that signature. A single instance of the class can be cached in a dictionary, matching `typeof(ImplementingClass)` with `ImplementingClass.StaticMethod`, thus creating a simple runtime static fill C#'s gap. 16 | 17 | * It is sometimes practical to provide a public parameterless constructor to a class that otherwise should not have one, so that the class may be dealt with more easily from within generic methods. While messy from an API perspective, the limitations of C#'s generics prevent cleaner approaches. 18 | 19 | * In WindowsForms, SelectedValue only works if ValueMember has been set. When not using MS Databinding, use SelectedItem instead. 20 | 21 | * As of Sept. 2020, setting ComboBox.SelectedItem to NULL, or ListBox.SelectedItem to NULL, causes the control to deselect, the same as setting SelectedIndex to -1. This appears to be undocumented behavior, at least from what I can find on MSDN, but I am relying on it for now. 22 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Crafts/CraftConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace Parquet.Crafts 5 | { 6 | /// 7 | /// Provides parameters for s. 8 | /// 9 | public static class CraftConfiguration 10 | { 11 | #region Class Defaults 12 | /// A fall-back number of ingredient categories per recipe. 13 | private static Range DefaultIngredientCount { get; } = new Range(1, 5); 14 | 15 | /// A fall-back number of product categories per recipe. 16 | private static Range DefaultProductCount { get; } = new Range(1, 5); 17 | #endregion 18 | 19 | #region Characteristics 20 | /// Number of ingredient categories per recipe. 21 | public static Range IngredientCount { get; set; } = DefaultIngredientCount; 22 | 23 | /// Number of product categories per recipe. 24 | public static Range ProductCount { get; set; } = DefaultProductCount; 25 | #endregion 26 | 27 | #region Self Serialization 28 | /// 29 | /// Reads data from the appropriate file. 30 | /// 31 | /// The instances read. 32 | public static void GetRecord() 33 | { 34 | using var reader = new StreamReader(FilePath); 35 | 36 | // Skip the header. 37 | reader.ReadLine(); 38 | // Read in the values. 39 | var valueLine = reader.ReadLine(); 40 | var serializedRanges = valueLine.Split(Delimiters.PrimaryDelimiter); 41 | var ingredientValues = serializedRanges[0].Split(Delimiters.ElementDelimiter); 42 | var productValues = serializedRanges[1].Split(Delimiters.ElementDelimiter); 43 | 44 | // Parse. 45 | IngredientCount = int.TryParse(ingredientValues[0], out var tempMin) 46 | && int.TryParse(ingredientValues[1], out var tempMax) 47 | ? new Range(tempMin, tempMax) 48 | : Logger.DefaultWithParseLog(serializedRanges[0], nameof(IngredientCount), DefaultIngredientCount); 49 | ProductCount = int.TryParse(productValues[0], out tempMin) 50 | && int.TryParse(productValues[1], out tempMax) 51 | ? new Range(tempMin, tempMax) 52 | : Logger.DefaultWithParseLog(serializedRanges[1], nameof(ProductCount), DefaultProductCount); 53 | } 54 | 55 | /// 56 | /// Writes data to the appropriate file. 57 | /// 58 | public static void PutRecord() 59 | { 60 | using var writer = new StreamWriter(FilePath, false, new UTF8Encoding(true, true)); 61 | writer.Write($"{nameof(IngredientCount)}{Delimiters.PrimaryDelimiter}{nameof(ProductCount)}\n"); 62 | writer.Write($"{IngredientCount.Minimum}{Delimiters.ElementDelimiter}" + 63 | $"{IngredientCount.Maximum}{Delimiters.PrimaryDelimiter}" + 64 | $"{ProductCount.Minimum}{Delimiters.ElementDelimiter}" + 65 | $"{ProductCount.Maximum}\n"); 66 | } 67 | 68 | /// 69 | /// Returns the filename and path associated with 's definition file. 70 | /// 71 | /// A full path to the associated file. 72 | public static string FilePath 73 | => $"{All.ProjectDirectory}/{nameof(CraftConfiguration)}.csv"; 74 | #endregion 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ParquetUnitTests/RangeUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Parquet; 4 | using Xunit; 5 | 6 | namespace ParquetUnitTests 7 | { 8 | /// 9 | /// Unit tests . 10 | /// 11 | public class RangeUnitTest 12 | { 13 | #region Values for Tests 14 | private const int lowerBound = 0; 15 | private const int upperBound = 10; 16 | #endregion 17 | 18 | [Fact] 19 | internal void RangeMustBeWillDefinedTest() 20 | { 21 | Assert.Throws(() => { var _ = new Range(upperBound, lowerBound); }); 22 | } 23 | 24 | [Fact] 25 | internal void WellDefinedRangeIsValidTest() 26 | { 27 | var range = new Range(lowerBound, upperBound); 28 | 29 | Assert.True(range.IsValid()); 30 | } 31 | 32 | [Fact] 33 | internal void WellDefinedRangeContainsItselfTest() 34 | { 35 | var range = new Range(lowerBound, upperBound); 36 | 37 | Assert.True(range.ContainsRange(range)); 38 | } 39 | 40 | [Fact] 41 | internal void RangeContainsStrictlySmallerRangeTest() 42 | { 43 | var range = new Range(lowerBound, upperBound); 44 | var smallerRange = new Range(lowerBound + 1, upperBound - 1); 45 | 46 | Assert.True(range.ContainsRange(smallerRange)); 47 | } 48 | 49 | [Fact] 50 | internal void AverageIsWithinRangeTest() 51 | { 52 | var range = new Range(lowerBound, upperBound); 53 | var average = (lowerBound + upperBound) / 2; 54 | 55 | Assert.True(range.ContainsValue(average)); 56 | } 57 | 58 | [Fact] 59 | internal void MinimumIsWithinRangeTest() 60 | { 61 | var range = new Range(lowerBound, upperBound); 62 | 63 | Assert.True(range.ContainsValue(lowerBound)); 64 | } 65 | 66 | [Fact] 67 | internal void MaximumIsWithinRangeTest() 68 | { 69 | var range = new Range(lowerBound, upperBound); 70 | 71 | Assert.True(range.ContainsValue(upperBound)); 72 | } 73 | 74 | [Fact] 75 | internal void GreaterThanMaximumIsNotWithinRangeTest() 76 | { 77 | var range = new Range(lowerBound, upperBound); 78 | var greater = upperBound + 1; 79 | 80 | Assert.False(range.ContainsValue(greater)); 81 | } 82 | 83 | [Fact] 84 | internal void LessThanMinimumIsNotWithinRangeTest() 85 | { 86 | var range = new Range(lowerBound, upperBound); 87 | var lesser = lowerBound - 1; 88 | 89 | Assert.False(range.ContainsValue(lesser)); 90 | } 91 | 92 | [Fact] 93 | internal void EmptyRangeCollectionContainsNothingTest() 94 | { 95 | var range = new Range(lowerBound, upperBound); 96 | var emptyCollection = new List>(); 97 | 98 | Assert.False(emptyCollection.ContainsRange(range)); 99 | Assert.False(emptyCollection.ContainsValue(lowerBound)); 100 | } 101 | 102 | [Fact] 103 | internal void KnownRangeCanBeFoundInRangeCollectionTest() 104 | { 105 | var range = new Range(lowerBound, upperBound); 106 | var collection = new List> { range }; 107 | 108 | Assert.True(collection.ContainsRange(range)); 109 | } 110 | 111 | [Fact] 112 | internal void KnownValueCanBeFoundInRangeCollectionTest() 113 | { 114 | var range = new Range(lowerBound, upperBound); 115 | var collection = new List> { range }; 116 | 117 | Assert.True(collection.ContainsValue(lowerBound)); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ParquetUnitTests/Rooms/RoomRecipeUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Parquet; 4 | using Parquet.Parquets; 5 | using Parquet.Rooms; 6 | using Xunit; 7 | 8 | namespace ParquetUnitTests.Rooms 9 | { 10 | public class RoomRecipeUnitTest 11 | { 12 | #region Test Values 13 | private static readonly ParquetModelPack TestWall = new(TestModels.TestFloor.ID, TestModels.TestBlock.ID, ModelID.None, ModelID.None); 14 | 15 | private static readonly ParquetModelPack TestWalk = new(TestModels.TestFloor.ID, ModelID.None, ModelID.None, ModelID.None); 16 | 17 | private static readonly ParquetModelPack TestEntry = new(TestModels.TestFloor.ID, ModelID.None, TestModels.TestFurnishing.ID, ModelID.None); 18 | 19 | private static readonly IReadOnlySet TestPerimeter = new HashSet 20 | { 21 | new MapSpace(0, 0, TestWall, null), 22 | new MapSpace(1, 0, TestWall, null), 23 | new MapSpace(2, 0, TestWall, null), 24 | new MapSpace(3, 0, TestWall, null), 25 | new MapSpace(0, 1, TestWall, null), 26 | new MapSpace(3, 1, TestWall, null), 27 | new MapSpace(0, 2, TestWall, null), 28 | new MapSpace(3, 2, TestWall, null), 29 | new MapSpace(0, 3, TestWall, null), 30 | new MapSpace(1, 3, TestWall, null), 31 | new MapSpace(2, 3, TestWall, null), 32 | new MapSpace(3, 3, TestWall, null), 33 | }; 34 | 35 | private static readonly IReadOnlySet TestWalkableArea = new HashSet 36 | { 37 | new MapSpace(1, 1, TestWalk, null), 38 | new MapSpace(2, 1, TestWalk, null), 39 | new MapSpace(1, 2, TestWalk, null), 40 | new MapSpace(2, 2, TestEntry, null), 41 | }; 42 | 43 | private static readonly IReadOnlyList TestRequiredFurnishings = TestModels.TestRecipeElementList; 44 | 45 | private static readonly RoomRecipe MinimalRecipe = 46 | new(-All.RoomRecipeIDs.Minimum, "Minimal Room Recipe", "", "", null, RoomConfiguration.MinWalkableSpaces, TestRequiredFurnishings); 47 | 48 | private static readonly Room MinimalRoom = new(TestWalkableArea, TestPerimeter); 49 | #endregion 50 | 51 | [Fact] 52 | internal void MinimumWalkableSpacesBelowGlobalMinimumThrowsTest() 53 | { 54 | var BadMinimum = RoomConfiguration.MinWalkableSpaces - 1; 55 | 56 | void HasBadRequiredBlocks() 57 | { 58 | var _ = new RoomRecipe(-All.RoomRecipeIDs.Minimum, "will fail", "", "", null, BadMinimum); 59 | } 60 | 61 | Assert.Throws(HasBadRequiredBlocks); 62 | } 63 | 64 | [Fact] 65 | internal void MinimumWalkableSpacesAboveGlobalMaximumThrowsTest() 66 | { 67 | var BadMinimum = RoomConfiguration.MaxWalkableSpaces + 1; 68 | 69 | void HasBadRequiredBlocks() 70 | { 71 | var _ = new RoomRecipe(-All.RoomRecipeIDs.Minimum, "will fail", "", "", null, BadMinimum); 72 | } 73 | 74 | Assert.Throws(HasBadRequiredBlocks); 75 | } 76 | 77 | [Fact] 78 | internal void StricterRoomRequirementsGenerateHigherPriorityTest() 79 | { 80 | var stricterRecipe = TestModels.TestRoomRecipe; 81 | 82 | Assert.True(MinimalRecipe.Priority < stricterRecipe.Priority); 83 | } 84 | 85 | [Fact] 86 | internal void KnownMismatchReturnsFalse() 87 | { 88 | var stricterRecipe = TestModels.TestRoomRecipe; 89 | 90 | Assert.False(stricterRecipe.Matches(MinimalRoom)); 91 | } 92 | 93 | [Fact] 94 | internal void KnownMatchReturnsTrue() 95 | { 96 | Assert.True(MinimalRecipe.Matches(MinimalRoom)); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Documentation/1-Terminology.md: -------------------------------------------------------------------------------- 1 | Here are definitions for some of the terms used in this class library. 2 | 3 | ## Parquets 4 | 5 | * A **parquet** in everyday speech is a flooring composed of wooden blocks in a geometrical arrangement. In the United States it is pronounced /pɑːɹˈkeɪ/, i.e. *"par-kay"*. 6 | 7 | * Within the context of this library, a **parquet** is the smallest interactive unit of the game world. These are the units with which the player builds and that are available in the map editor. Oftentimes in the context of other libraries or games the word "tile" is used to mean something similar; the word "parquet" is employed here to avoid ambiguity. 8 | 9 | * **Parquet Packs** hold parquets during game play. Up to four parquets of distinct types may occupy a ptack. 10 | 11 | * **Floor**s are a type of parquet. They are the lowest layer of parquets in a ptack, providing a background and context for other parquets and the various *character*s that inhabit the world. Floors may be either filled in or dug out; if they are dug out they can contain liquid blocks. Natural surfaces are implemented as floors. Examples of floors: 12 | 13 | * Sand 14 | * Mosaic Tile 15 | * Metal Catwalk 16 | * Dropcloth 17 | 18 | * **Block**s are a type of parquet. They exist in a layer above floors and below furnishings. Blocks are the most complex type of parquet, often the layer with which the player and other characters are interacting. They may be **gather**ed directly or broken down into **collectible**s. They may also be crafted and placed in the world by the player. Examples of blocks: 19 | 20 | * Seawater 21 | * Inlaid Brick 22 | * Plasteel Plating 23 | * Plastered Drywall 24 | 25 | Liquid blocks can only exist on top of floors with the *dug out* property set. 26 | 27 | * **Furnishing**s are a type of parquet. They exist in a layer above blocks and below collectibles. Furnishings are complex parquets that can have special behaviors. Often they are crafted objects and may be placed or removed by the player. Examples of furnishings: 28 | 29 | * Anchored Buoy 30 | * Palace Gate 31 | * Photon Forge 32 | * Padded Sawhorse 33 | 34 | * **Collectible**s are a type of parquet. Constituting the topmost layer, collectibles are simple parquets that represent items on the map that characters (such as the player) may pick up. Often, they form as a result of breaking down blocks. Examples of collectibles: 35 | 36 | * Seashell 37 | * Heart (that grants health) 38 | * 1 Federation Credit 39 | * Wrist & Ankle Restraints 40 | 41 | *See [How Parquets Work](https://github.com/mxashlynn/Parquet/wiki/How-Parquets-Work).* 42 | 43 | ## Inventory-Related Terms 44 | 45 | * Characters including the player carries objects from the game world around with them in their **Inventory**. 46 | 47 | * An **Item** is any game object in the inventory. Items never exist on the map, although they may be equivalent to parquets that exist on the map. In the right circumstances, the player may remove parquets from the map. When a player does so, the parquets become items. 48 | 49 | ## Map-Related Terms 50 | 51 | * The **Map** is a 2D space, the entirety of the playable game world. Once play has begun the Map is, at the atomic level, composed of parquets. 52 | 53 | * **Map Region**s are sections of the game world in play. Exactly one of these is in play at a time. Map Regions are composed of *Parquet Packs*. All of a given Map Region's parquets exists on the same elevation level, although regions may be higher or lower than one another. Exits from the current region to other regions may be defined at any specific location. 54 | 55 | * **Map Chunk Grid**s are sections of the game world equivalent to Map Regions, but in an uninitialized state before play begins. They consist of Map Chunks together with locations that have been designated as entrances or exits. Map Regions may be generated from Map Chunk Grids in a procedural process at the time of load. 56 | 57 | * **Map Chunk**s are small subsections of the Map. They come in two varieties. Custom map chunks are "Handmade" segments of parquets which will be loaded exactly as they are laid out in the editor. Procedural map chunks consist of instructions describing the parquets to procedurally generate in this location on the grid. 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parquet 2 | 3 | This class library implements mechanics and models for a 2D sandbox game focused on building, crafting, and narrative, more or less in that priority order. 4 | 5 | A game built with this system offers many of the features of contemporary quest-driven building games but in a simple, top-down world and without combat. 6 | 7 | # Version 0.4 Warning 8 | 9 | Code and documentation are incomplete and under rapid development. Expect frequent breaking changes. Maybe don't use this yet; when the [Alpha milestone](https://github.com/mxashlynn/Parquet/milestone/2) has been hit the project should be much more usable. 10 | 11 | Design and usage will be [fully documented](https://github.com/mxashlynn/Parquet/tree/master/Documentation) once the [Beta milestone](https://github.com/mxashlynn/Parquet/milestone/3) has been reached. 12 | 13 | Development milestone deadlines are tentative right now. 14 | Because this is one of several side projects that I maintain in addition to my fulltime dayjob, it will be a long time before Parquet is ready. 15 | 16 | # Goals 17 | 18 | This project has two goals: 19 | 20 | 1. To provide a foundation for games developed in C# stacks; 21 | 2. To provide a learning exercise for the lead developer. 22 | 23 | Due to goal 2, no attempt has been made to design the most elegant, efficient, general-purpose, or powerful library possible. 24 | What has been attempted is a cleanly-coded, clearly-documented, easily maintainable class library. 25 | 26 | # Features 27 | 28 | Parquet targets the following features: 29 | 30 | 1. A peaceful 2D top-down overworld map that may be explored. 31 | 2. Simple free-form building mechanics allowing player characters to create homes in the world. 32 | 3. Resource collection mechanics allowing players to upgrade their homes and tools. 33 | 4. A simple crafting system allowing players to unlock new tools and building materials. 34 | 5. Interactive noncombatant NPCs. 35 | 6. A quest system encouraging players to build particular building types. 36 | 7. Dialogue and narrative delivery. 37 | 8. Data-driven design with all game models customizable from CSV files. 38 | 39 | # Repository Structure 40 | 41 | The solution contains several related projects. 42 | Each C# namespace gets its own folder. 43 | 44 | - **Documentation** 45 | - How to use the library and its tools. 46 | - **ExampleData** 47 | - Configuration files used in developing and testing the library. 48 | - **ParquetClassLibrary** 49 | - The library itself. The root namespace contains classes for working with [Models](https://github.com/mxashlynn/Parquet/blob/master/Documentation/2-How_Parquet_Handles_Game_Objects.md) and Statuses. 50 | - **Beings**, including player characters and NPCs. 51 | - **Biomes**, sets of characteristics that Regions may share. 52 | - **Crafts**, recipes and mini-games that produce Items. 53 | - **Items**, objects that Beings may carry, including parquets. 54 | - **Parquets**, [the basic units of play](https://github.com/mxashlynn/Parquet/blob/master/Documentation/3-How_Parquets_Work.md). 55 | - **Properties** error strings, icons, and other static content. 56 | - **Regions**, collections of parquets and Beings which together make up the game world. 57 | - **Rooms**, built from parquets and [recognized at runtime](https://github.com/mxashlynn/Parquet/blob/master/Documentation/4.-Room_Detection_and_Type_Assignment.md). 58 | - **Scripts**, used to define Being Interactions and Item effects. 59 | - **ParquetRunner** 60 | - A simple smoke test. 61 | - **ParquetUnitTests** 62 | - Unit tests for ParquetClassLibrary. 63 | 64 | # Requirements 65 | 66 | To work with this repository you will need: 67 | 68 | - [.NET 5](https://dotnet.microsoft.com/download/dotnet/5.0) 69 | - [CSVHelper 19](https://joshclose.github.io/CsvHelper/) 70 | - [XUnit 2.4](https://github.com/xunit/xunit) (to compile the unit tests only) 71 | 72 | # Contributors 73 | - Library design and code by [Paige Ashlynn](https://github.com/mxashlynn/). 74 | - Special thanks to [Mint Gould](https://github.com/WispyMouse), [Caidence Stone](https://github.com/caidencestone), [Ashley Hauck](https://github.com/khyperia), [Emma Maassen](https://github.com/Enichan), and Lillian Harris for help with code reviews, mathematics, algorithms, game design, and technical decisions. 75 | -------------------------------------------------------------------------------- /ParquetUnitTests/Items/InventoryCollectionUnitTest.cs: -------------------------------------------------------------------------------- 1 | using Parquet.Items; 2 | using Xunit; 3 | 4 | namespace ParquetUnitTests.Items 5 | { 6 | /// 7 | /// Unit tests . 8 | /// 9 | public class InventoryCollectionUnitTest 10 | { 11 | #region Test Values 12 | private const int TestCapacity = 30; 13 | #endregion 14 | 15 | [Fact] 16 | internal void NewInventoryHasGivenCapacityTest() 17 | { 18 | var inventory = new InventoryCollection(TestCapacity); 19 | 20 | Assert.Equal(TestCapacity, inventory.Capacity); 21 | } 22 | 23 | /* TODO [TESTING] Introduce mutable unit tests! 24 | [Fact] 25 | internal void NewInventoryContainsOneSlotsForEachItemTest() 26 | { 27 | var inventory = new Inventory(TestCapacity); 28 | inventory.Give(TestModels.TestItem1.ID); 29 | inventory.Give(TestModels.TestItem2.ID); 30 | inventory.Give(TestModels.TestItem3.ID); 31 | inventory.Give(TestModels.TestItem4.ID); 32 | var numberOfItemTypes = 4; 33 | 34 | Assert.Equal(numberOfItemTypes, inventory.Count); 35 | } 36 | 37 | [Fact] 38 | internal void InventoryContainsGivenNumbersOfItemsTest() 39 | { 40 | var inventory = new Inventory(TestCapacity); 41 | var numberOfItems = 6; 42 | inventory.Give(TestModels.TestItem1.ID, numberOfItems); 43 | 44 | Assert.Equal(numberOfItems, inventory.Contains(TestModels.TestItem1.ID)); 45 | } 46 | 47 | [Fact] 48 | internal void InventoryHasGivenItemsTest() 49 | { 50 | var inventory = new Inventory(TestCapacity); 51 | var slots = new List 52 | { 53 | new InventorySlot(TestModels.TestItem2.ID, 4), 54 | new InventorySlot(TestModels.TestItem3.ID, 40), 55 | new InventorySlot(TestModels.TestItem4.ID, 400), 56 | }; 57 | foreach (var slot in slots) 58 | { 59 | inventory.Give(slot); 60 | } 61 | 62 | Assert.True(inventory.Has(slots)); 63 | } 64 | 65 | [Fact] 66 | internal void InventoryReturnsCorrectRemainderAfterTakingItemsTest() 67 | { 68 | var inventory = new Inventory(TestCapacity); 69 | var slots = new List 70 | { 71 | new InventorySlot(TestModels.TestItem2.ID, 4), 72 | new InventorySlot(TestModels.TestItem3.ID, 40), 73 | new InventorySlot(TestModels.TestItem4.ID, 400), 74 | }; 75 | var amountToTake = 10; 76 | 77 | foreach (var slot in slots) 78 | { 79 | inventory.Give(slot); 80 | var expectedRemainder = slot.Count > amountToTake 81 | ? 0 82 | : amountToTake - slot.Count; 83 | Assert.Equal(expectedRemainder, inventory.Take(slot.ItemID, amountToTake)); 84 | } 85 | } 86 | 87 | [Fact] 88 | internal void InventoryMergesItemsTest() 89 | { 90 | var inventory = new Inventory(TestCapacity); 91 | var slots = new List 92 | { 93 | new InventorySlot(TestModels.TestItem1.ID, 4), 94 | new InventorySlot(TestModels.TestItem1.ID, 40), 95 | new InventorySlot(TestModels.TestItem1.ID, 400), 96 | }; 97 | var total = 444; 98 | foreach (var slot in slots) 99 | { 100 | inventory.Give(slot); 101 | } 102 | 103 | Assert.Equal(total, inventory.Contains(TestModels.TestItem1.ID)); 104 | } 105 | 106 | [Fact] 107 | internal void InventoryDoesNotContainItemsNotGivenTest() 108 | { 109 | var inventory = new Inventory(TestCapacity); 110 | inventory.Give(TestModels.TestItem1.ID, 4); 111 | var notInInventoryID = TestModels.TestItem1.ID + 10; 112 | 113 | Assert.Equal(0, inventory.Contains(notInInventoryID)); 114 | } 115 | */ 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /ParquetUnitTests/ParquetUnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ParquetUnitTests 6 | 7 | Parquet 8 | ParquetUnitTests 9 | 0.4.12.0 10 | 0.4.0.0 11 | 0.4.12.0 12 | Parquet Pre-Alpha 3 13 | 14 | Paige Ashlynn 15 | Girl Potion 16 | Unit tests for Parquet. 17 | 2018-2022 Paige Ashlynn 18 | ParquetIcon.ico 19 | Parquet Logo Large.png 20 | LICENSE.txt 21 | http://parquet.gay/ 22 | https://github.com/mxashlynn/Parquet 23 | git 24 | 25 | This code is not meant for external use. 26 | en-US 27 | 28 | net6.0 29 | latest 30 | CS1701;CS1702 31 | False 32 | NU1605;CS1591 33 | True 34 | AllEnabledByDefault 35 | latest 36 | 5 37 | warnings 38 | True 39 | ParquetUnitTests 40 | 41 | False 42 | False 43 | False 44 | Off 45 | False 46 | True 47 | Debug;Release 48 | bin\ 49 | disable 50 | send 51 | 52 | 53 | 54 | 55 | DEBUG 56 | 5 57 | 58 | 59 | 60 | TRACE 61 | 5 62 | 63 | 64 | 65 | ..\.vs\Parquet\v16\TestStore\0 66 | bin\ 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | all 79 | runtime; build; native; contentfiles; analyzers; buildtransitive 80 | 81 | 82 | runtime; build; native; contentfiles; analyzers; buildtransitive 83 | all 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | True 95 | \ 96 | 97 | 98 | True 99 | \ 100 | 101 | 102 | -------------------------------------------------------------------------------- /ParquetClassLibrary/SeriesConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CsvHelper; 6 | using CsvHelper.Configuration; 7 | using CsvHelper.TypeConversion; 8 | 9 | namespace Parquet 10 | { 11 | /// 12 | /// Type converter for any collection that implements . 13 | /// 14 | /// The type collected. 15 | /// The type of the collection. 16 | public class SeriesConverter : CollectionGenericConverter 17 | where TCollection : ICollection, new() 18 | where TElement : ITypeConverter, new() 19 | { 20 | /// Allows the converter to construct itself statically. 21 | internal static SeriesConverter ConverterFactory { get; } = 22 | new SeriesConverter(); 23 | 24 | /// Allows the converter to construct its contents. 25 | internal static readonly TElement ElementFactory = new(); 26 | 27 | /// 28 | /// Converts the given 1D collection into a record column. 29 | /// 30 | /// The collection to convert. 31 | /// The current context and configuration. 32 | /// Mapping info for a member to a CSV field or property. 33 | /// The given collection serialized. 34 | public override string ConvertToString(object collection, IWriterRow row, MemberMapData memberMapData) 35 | { 36 | Precondition.IsNotNull(collection, nameof(collection)); 37 | if (collection is not TCollection series) 38 | { 39 | return Logger.DefaultWithConvertLog(collection?.ToString() ?? "null", nameof(TCollection), ""); 40 | } 41 | 42 | if (series.Count < 1 43 | || (series.Count == 1 44 | && series.Contains(ElementFactory))) 45 | { 46 | return ""; 47 | } 48 | 49 | var result = new StringBuilder(); 50 | foreach (var element in series) 51 | { 52 | result.Append(element.ConvertToString(element, row, memberMapData)); 53 | result.Append(Delimiters.SecondaryDelimiter[0]); 54 | } 55 | result.Remove(result.Length - Delimiters.SecondaryDelimiter.Length, Delimiters.SecondaryDelimiter.Length); 56 | 57 | return result.ToString(); 58 | } 59 | 60 | /// 61 | /// Converts the given record column to a 1D collection. 62 | /// 63 | /// The record column to convert to an object. 64 | /// The for the current record. 65 | /// The for the member being created. 66 | /// The created from the record column. 67 | public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) 68 | => ConvertFromString(text, row, memberMapData, Delimiters.SecondaryDelimiter); 69 | 70 | /// 71 | /// Converts the given record column to a 1D collection. 72 | /// 73 | /// The record column to convert to an object. 74 | /// The for the current record. 75 | /// The for the member being created. 76 | /// The string used to separate elements in the series. 77 | /// The created from the record column. 78 | public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData, string delimiter) 79 | { 80 | var collection = new TCollection(); 81 | if (string.IsNullOrEmpty(text) 82 | || string.Equals(nameof(ModelID.None), text, StringComparison.OrdinalIgnoreCase) 83 | || string.Equals(nameof(Enumerable.Empty), text, StringComparison.OrdinalIgnoreCase)) 84 | { 85 | return collection; 86 | } 87 | 88 | var textCollection = text.Split(delimiter); 89 | foreach (var currentText in textCollection) 90 | { 91 | collection.Add((TElement)ElementFactory.ConvertFromString(currentText, row, memberMapData)); 92 | } 93 | return collection; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Biomes/BiomeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Parquet.Regions; 5 | 6 | namespace Parquet.Biomes 7 | { 8 | /// 9 | /// Provides rules for determining a 's . 10 | /// 11 | public static class BiomeConfiguration 12 | { 13 | #region Class Defaults 14 | /// Used in computing thresholds. 15 | private const int ParquetsPerLayer = RegionStatus.ParquetsPerRegionDimension * RegionStatus.ParquetsPerRegionDimension; 16 | #endregion 17 | 18 | #region Characteristics 19 | /// 20 | /// There must be at least this percentage of non-liquid s in a given 21 | /// to generate the associated with them. 22 | /// 23 | public static double LandThresholdFactor { get; set; } = 1.25; 24 | 25 | /// How many of a layers' worth of parquets must contribute to a land-based . 26 | public static int LandThreshold 27 | => (int)Math.Round(ParquetsPerLayer * LandThresholdFactor, 0, MidpointRounding.AwayFromZero); 28 | 29 | /// 30 | /// There must be at least this percentage of liquid s in a given 31 | /// to generate the associated with them. 32 | /// 33 | public static double LiquidThresholdFactor { get; set; } = 0.25; 34 | 35 | /// How many of a layers' worth of parquets must contribute to a Liquid-based . 36 | public static int LiquidThreshold 37 | => (int)Math.Round(ParquetsPerLayer * LiquidThresholdFactor, 0, MidpointRounding.AwayFromZero); 38 | 39 | /// 40 | /// There must be at least this percentage of s included in rooms in a given 41 | /// to generate the associated with them. 42 | /// 43 | public static double RoomThresholdFactor { get; set; } = 0.67; 44 | 45 | /// How many of a layers' worth of parquets must contribute to a room-based . 46 | public static int RoomThreshold 47 | => (int)Math.Round(ParquetsPerLayer * RoomThresholdFactor, 0, MidpointRounding.AwayFromZero); 48 | #endregion 49 | 50 | #region Self Serialization 51 | /// 52 | /// Reads data from the appropriate file. 53 | /// 54 | /// The instances read. 55 | public static void GetRecord() 56 | { 57 | using var reader = new StreamReader(FilePath); 58 | 59 | // Skip the header. 60 | reader.ReadLine(); 61 | // Read in the values. 62 | var valueLine = reader.ReadLine(); 63 | var values = valueLine.Split(Delimiters.PrimaryDelimiter); 64 | 65 | // Parse. 66 | LandThresholdFactor = double.TryParse(values[0], out var temp) 67 | ? temp 68 | : Logger.DefaultWithParseLog(values[0], nameof(LandThresholdFactor), 1.0); 69 | LiquidThresholdFactor = double.TryParse(values[1], out temp) 70 | ? temp 71 | : Logger.DefaultWithParseLog(values[1], nameof(LiquidThresholdFactor), 1.0); 72 | RoomThresholdFactor = double.TryParse(values[2], out temp) 73 | ? temp 74 | : Logger.DefaultWithParseLog(values[2], nameof(RoomThresholdFactor), 1.0); 75 | } 76 | 77 | /// 78 | /// Writes data to the appropriate file. 79 | /// 80 | public static void PutRecord() 81 | { 82 | using var writer = new StreamWriter(FilePath, false, new UTF8Encoding(true, true)); 83 | writer.Write($"{nameof(LandThresholdFactor)}{Delimiters.PrimaryDelimiter}{nameof(LiquidThresholdFactor)}{Delimiters.PrimaryDelimiter}{nameof(RoomThresholdFactor)}\n"); 84 | writer.Write($"{LandThresholdFactor}{Delimiters.PrimaryDelimiter}{LiquidThresholdFactor}{Delimiters.PrimaryDelimiter}{RoomThresholdFactor}\n"); 85 | } 86 | 87 | /// 88 | /// Returns the filename and path associated with 's definition file. 89 | /// 90 | /// A full path to the associated file. 91 | public static string FilePath 92 | => $"{All.ProjectDirectory}/{nameof(BiomeConfiguration)}.csv"; 93 | #endregion 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ParquetClassLibrary/ParquetClassLibrary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Parquet 6 | 7 | Parquet 8 | Parquet 9 | 0.4.12.0 10 | 0.4.0.0 11 | 0.4.12.0 12 | Parquet Pre-Alpha 3 13 | 14 | Paige Ashlynn 15 | Girl Potion 16 | Mechanics and models for 2D building, crafting, and narrative sandbox games. 17 | 2018-2022 Paige Ashlynn 18 | ParquetIcon.ico 19 | Parquet Logo Large.png 20 | README.md 21 | LICENSE.txt 22 | http://parquet.gay/ 23 | https://github.com/mxashlynn/Parquet 24 | git 25 | C# .net6 net6 game library 26 | Code and documentation are incomplete and under rapid development. Expect frequent breaking changes. 27 | en-US 28 | 29 | Library 30 | net6.0 31 | latest 32 | CS1701;CS1702;CS0809 33 | False 34 | NU1605;CS1591 35 | True 36 | AllEnabledByDefault 37 | latest 38 | 5 39 | warnings 40 | False 41 | Parquet 42 | 43 | True 44 | True 45 | True 46 | Off 47 | True 48 | snupkg 49 | True 50 | Debug;Release 51 | disable 52 | send 53 | 54 | 55 | 56 | DEBUG 57 | True 58 | 59 | 60 | 61 | TRACE 62 | Parquet.Documentation.xml 63 | 64 | 65 | 66 | 67 | 68 | 69 | all 70 | runtime; build; native; contentfiles; analyzers; buildtransitive 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | True 79 | True 80 | Resources.resx 81 | 82 | 83 | 84 | 85 | 86 | ResXFileCodeGenerator 87 | Resources.Designer.cs 88 | 89 | 90 | 91 | 92 | 93 | True 94 | \ 95 | 96 | 97 | True 98 | \ 99 | 100 | 101 | True 102 | \ 103 | 104 | 105 | True 106 | \ 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Parquet.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.29613.14 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParquetClassLibrary", "ParquetClassLibrary\ParquetClassLibrary.csproj", "{5D426EB2-83DD-44E3-8404-E9914CE98FE7}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParquetUnitTests", "ParquetUnitTests\ParquetUnitTests.csproj", "{6F347E83-0546-4447-A0ED-73C2ED3626F2}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParquetRunner", "ParquetRunner\ParquetRunner.csproj", "{BD78B49F-9E73-4D20-91C2-99F9E417AC93}" 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{33AB7F7D-6D20-4F53-AC7F-386C8E1144C3}" 12 | ProjectSection(SolutionItems) = preProject 13 | .editorconfig = .editorconfig 14 | .gitattributes = .gitattributes 15 | .gitignore = .gitignore 16 | CHANGELOG.md = CHANGELOG.md 17 | CONTRIBUTING.md = CONTRIBUTING.md 18 | LICENSE.txt = LICENSE.txt 19 | README.md = README.md 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExampleData", "ExampleData", "{75B6EFDC-819B-4CA1-A77A-9014E51C53B0}" 23 | ProjectSection(SolutionItems) = preProject 24 | ExampleData\BiomeConfiguration.csv = ExampleData\BiomeConfiguration.csv 25 | ExampleData\BiomeModels.csv = ExampleData\BiomeModels.csv 26 | ExampleData\BlockModels.csv = ExampleData\BlockModels.csv 27 | ExampleData\CharacterModels.csv = ExampleData\CharacterModels.csv 28 | ExampleData\CollectibleModels.csv = ExampleData\CollectibleModels.csv 29 | ExampleData\CraftConfiguration.csv = ExampleData\CraftConfiguration.csv 30 | ExampleData\CraftingRecipes.csv = ExampleData\CraftingRecipes.csv 31 | ExampleData\CritterModels.csv = ExampleData\CritterModels.csv 32 | ExampleData\FloorModels.csv = ExampleData\FloorModels.csv 33 | ExampleData\FurnishingModels.csv = ExampleData\FurnishingModels.csv 34 | ExampleData\InteractionModels.csv = ExampleData\InteractionModels.csv 35 | ExampleData\ItemModels.csv = ExampleData\ItemModels.csv 36 | ExampleData\MapChunks.csv = ExampleData\MapChunks.csv 37 | ExampleData\MapRegions.csv = ExampleData\MapRegions.csv 38 | ExampleData\MapRegionSketches.csv = ExampleData\MapRegionSketches.csv 39 | ExampleData\PronounGroups.csv = ExampleData\PronounGroups.csv 40 | ExampleData\RoomConfiguration.csv = ExampleData\RoomConfiguration.csv 41 | ExampleData\RoomRecipes.csv = ExampleData\RoomRecipes.csv 42 | ExampleData\ScriptModels.csv = ExampleData\ScriptModels.csv 43 | EndProjectSection 44 | EndProject 45 | Global 46 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 47 | Debug|Any CPU = Debug|Any CPU 48 | Release|Any CPU = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 51 | {5D426EB2-83DD-44E3-8404-E9914CE98FE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {5D426EB2-83DD-44E3-8404-E9914CE98FE7}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {5D426EB2-83DD-44E3-8404-E9914CE98FE7}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {5D426EB2-83DD-44E3-8404-E9914CE98FE7}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {6F347E83-0546-4447-A0ED-73C2ED3626F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {6F347E83-0546-4447-A0ED-73C2ED3626F2}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {6F347E83-0546-4447-A0ED-73C2ED3626F2}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {6F347E83-0546-4447-A0ED-73C2ED3626F2}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {BD78B49F-9E73-4D20-91C2-99F9E417AC93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {BD78B49F-9E73-4D20-91C2-99F9E417AC93}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {BD78B49F-9E73-4D20-91C2-99F9E417AC93}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {BD78B49F-9E73-4D20-91C2-99F9E417AC93}.Release|Any CPU.Build.0 = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(NestedProjects) = preSolution 68 | {75B6EFDC-819B-4CA1-A77A-9014E51C53B0} = {33AB7F7D-6D20-4F53-AC7F-386C8E1144C3} 69 | EndGlobalSection 70 | GlobalSection(ExtensibilityGlobals) = postSolution 71 | SolutionGuid = {3A31AC43-AA2B-4E9F-8DE8-CD3BFCED9B1E} 72 | EndGlobalSection 73 | GlobalSection(MonoDevelopProperties) = preSolution 74 | Policies = $0 75 | $0.DotNetNamingPolicy = $1 76 | $1.DirectoryNamespaceAssociation = PrefixedHierarchical 77 | $1.ResourceNamePolicy = MSBuild 78 | $0.TextStylePolicy = $2 79 | $2.inheritsSet = null 80 | $2.scope = application/xml 81 | $0.XmlFormattingPolicy = $3 82 | $3.scope = application/xml 83 | $0.StandardHeader = $4 84 | $0.VersionControlPolicy = $5 85 | $5.CommitMessageStyle = $6 86 | $6.FileSeparator = ", " 87 | $6.LineAlign = 0 88 | $6.InterMessageLines = 0 89 | $6.Wrap = False 90 | EndGlobalSection 91 | EndGlobal 92 | -------------------------------------------------------------------------------- /Documentation/2-How_Parquet_Handles_Game_Objects.md: -------------------------------------------------------------------------------- 1 | The vast majority of game objects in a given Parquet game consist of details set at design time that do not change at any point during play. Parquet tracks these as "Models". 2 | 3 | Here is how the system works. 4 | 5 | ## Models 6 | A **Model** could be considered the fundamental concept in the Parquet library. It represents the parts of a game object that do not change over time, or from one instance to another. 7 | 8 | ## ModelIDs and ModelCollections 9 | 10 | As part of their definition, every Model has an identifier, providing a means to track and rapidly swap large numbers of models at runtime. 11 | 12 | Many different game objects can then share a single definition when working out their game rules and behaviors. For example, we might define what it means to be a bunny and then introduce many bunnies around the place; they would all share our original definition. In this sense, you could think of Models as equivalent to C# classes. 13 | 14 | As the analogy with C# classes would suggest, Parquet also provides a means to track individual instances of each model -- a way to have a collection of five individual bunnies, rather than simply the idea of a bunny. 15 | 16 | These individuals are similar to C# object instances, and Parquet represents them as **ModelID**s. 17 | 18 | Individual game objects reference their models, and those models live within **ModelCollection**s. A ModelCollection can contain any number of models of the same general sort -- bunnies with other critters, wooden planks with other types of flooring, and so on. 19 | 20 | ## All: Where Models Are Defined 21 | 22 | An ModelID is used to look up the singular Model from the appropriate ModelCollection. Most of the time, this is done via one of the canonical model collections provided by the "**All**" super-collection. 23 | 24 | This allows (hopefully!) fluent constructions in code such as 25 | 26 | ```cs 27 | block1 = All.Parquets.Get(1); 28 | ``` 29 | 30 | where `1` is the ModelID. 31 | 32 | Using lines of code like this, the library looks up the game object definitions for any particular game object as needed when game rules are checked or game elements interact. 33 | 34 | All is populated during initialization and remains immutable afterward, serving as the source of truth about the parts of game objects that do not change during play. 35 | 36 | ## Model Status 37 | 38 | Of course, even in a Parquet game many game objects have details that _do_ change over time or from one instance to another. 39 | 40 | For example, one of the bunnies might hop over from the clover down to the brook, and the game would need to track where exactly that specific bunny had moved. 41 | 42 | If individual game objects must have mutable state like this, then a separate partner class captures those details. Such classes always end in the word "Status"; examples include **ParquetStatus** for floors and blocks, or **BeingStatus** for player characters and NPCs. 43 | 44 | ## Model Types and Sub-Types 45 | 46 | Most of the time when we are talking about a group of Models we are talking about a group of very similar Models, such as those bunnies. 47 | 48 | Many of the game objects that Parquet deals with can be grouped together like this into collections with shared characteristics. In such a collection, Parquet guarantees that all the given Models have the same general type of information in their definition. 49 | 50 | The types of Entities that Parquet knows about are: 51 | - Being Models 52 | - Character Models 53 | - Critter Models 54 | - Biome Models 55 | - Interaction Models 56 | - Dialogue Models 57 | - Quest Models 58 | - Item Models 59 | - Parquet Models 60 | - Floor Models 61 | - Block Models 62 | - Furnishing Models 63 | - Collectible Models 64 | - Recipes 65 | - Crafting Recipes 66 | - Room Recipes 67 | 68 | Similar to how a C# compiler provides type-checking for C# objects, Parquet defines valid ID ranges for all Model types and sub-types. 69 | 70 | ## Tags: Grouping Models Across Types 71 | 72 | Sometimes we want to talk about Models that are not in the same formal group but nevertheless share a characteristic. For example, we might be interested to note that one of the bunnies is bright pink, and that there are similar pink flowers down by the brook. 73 | 74 | For scenarios like this, Parquet provides a way to identify characteristics outside the scope of the formal definition. Narrative, aesthetic, or mechanical features can be tagged to indicate what informal attributes they exhibit. 75 | 76 | This allows for the definition of Models that rely on a loose category of other Models. For example, a volcanic BiomeModel might be interested in every ParquetModel that has the "Volcanic" tag, or a candy CraftingRecipe might be interested in any Item Model with the "Sugary" tag. 77 | 78 | To support this kind of flexibility, more than one **ModelTag** can coexist on a specific Model. 79 | 80 | ## More Details 81 | 82 | For more technical details, please read the remarks on the [Model](https://github.com/mxashlynn/Parquet/blob/master/ParquetClassLibrary/Model.cs) class and related classes. 83 | -------------------------------------------------------------------------------- /Documentation/Parquet_CSharp_Style_Guide.md: -------------------------------------------------------------------------------- 1 | Code in this library works to adhere to the following standards, with the understanding that rare particular circumstances may require a different approach. 2 | 3 | ## Overall 4 | 5 | - Generally, favor verbosity in names. 6 | - Generally, favor more short lines of code over fewer long lines of code. 7 | - An exception is made for fluent concatenation of method calls, as in Linq. 8 | - Generally, favor more comments than fewer. 9 | - Generally, follow Microsoft's [C# Coding Conventions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions), with the following additions and changes. 10 | 11 | ## Project Layout 12 | 13 | - One folder per namespace. 14 | - Child namespaces should be plural, i.e. `ParquetClassLibrary.Utilities`. 15 | - One namespace per folder. 16 | - One namespace per file, unless you are embedding namespaces to hide internal classes. 17 | - One class per file, except for internal classes or static classes that collect method extensions. 18 | - If you have a class of type `CustomType` and it is frequently collected in arrays, extension methods of the form 19 | ```cs 20 | Method(this CutomType[] inArrayArgument) 21 | ``` 22 | should be located in a class named `CustomTypeArrayExtensions` at the bottom of the file in which `CustomType` is defined. 23 | 24 | ## File Format 25 | - Spaces, not tabs. 26 | - 4 spaces for each indentation level. 27 | - If continuation lines are not indented automatically, indent them until they match logically with the line they continue, as follows: 28 | ```cs 29 | var leastYValue = Spaces 30 | .Select(space => space.Position.Y) 31 | .Min(); 32 | ``` 33 | 34 | ## File Layout 35 | - One blank line between using directives and namespace declarations. 36 | - One blank line between class definitions. 37 | - One blank line between every property, field, and method. 38 | - Generally, one statement or definition per line. 39 | - Long statements should be broken up into multiple lines to aid readability. 40 | - Linq statements should be broken up across multiple lines to aid readability as follows: 41 | ```cs 42 | var greatestXValue = Spaces 43 | .Select(space => space.Position.X) 44 | .Max(); 45 | ``` 46 | - Whenever possible, the ternary operator `?:` should be broken up across multiple lines in this fashion: 47 | ```cs 48 | return useCapitalLeters 49 | ? "Hello, World!" 50 | : "hello, world!"; 51 | ``` 52 | - Using directives before the outermost namespace, unless you are embedding the namespace reference to hide it from client code. 53 | - Use the `#region` macro liberally to collect related code elements. 54 | - In particular, if a class is defined as `someClass : ISomeInterface`, then the implementation of that interface should be grouped together in a region as `#region ISomeInterface Implementation` 55 | - Many Parquet classes are separated in this way: 56 | ```cs 57 | public class SomeClass : IInterface1, IInterface2 58 | { 59 | #region Class Defaults 60 | // Any constants, readonly static variables, or default values. 61 | #endregion 62 | 63 | #region Characteristics 64 | // Fields representing state that does NOT change while that game is running. 65 | #endregion 66 | 67 | #region Status 68 | // Fields representing state that DOES change while that game is running. 69 | #endregion 70 | 71 | #region Initialization 72 | // Constructors. 73 | #endregion 74 | 75 | #region IInterface1 Implementation 76 | // Methods implementing IInterface1 77 | #endregion 78 | 79 | #region IInterface2 Implementation 80 | // Methods implementing IInterface2 81 | #endregion 82 | 83 | #region Utilities 84 | // ToString() and other general purpose helper methods. 85 | #endregion 86 | } 87 | ``` 88 | ## Comment Conventions 89 | - Comments go on separate lines, not on the same line. 90 | - Comments go above the line or block they are commenting on, not below it. 91 | - Comments should be full sentences whenever possible. 92 | - Fill out the XML comment section for all methods and properties, even private ones. Paige relies heavily on these. 93 | - Think of `#region` titles as a type of comment. 94 | ## Other Conventions 95 | - Write each logical condition in an `if` statement on a separate line as follows: 96 | ```cs 97 | if (null != someVariable 98 | && null != someOtherVariable 99 | { 100 | // Do something. 101 | } 102 | ``` 103 | - If a method or property can be simplified into a single statement without compromising its safety or legibility, write it in expression-bodied form. For example: 104 | ```cs 105 | public bool IsEmpty 106 | => EntityID.None == Floor 107 | && EntityID.None == Block 108 | && EntityID.None == Furnishing 109 | && EntityID.None == Collectible; 110 | ``` 111 | ## Finally 112 | I am always open to constructive criticism on these guidelines, and for considering special cases. -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/FloorModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using CsvHelper.Configuration.Attributes; 4 | using Parquet.Biomes; 5 | using Parquet.Items; 6 | using Parquet.Properties; 7 | 8 | namespace Parquet.Parquets 9 | { 10 | /// 11 | /// Configurations for a sandbox parquet walking surface. 12 | /// 13 | [SuppressMessage("Design", "CA1033:Interface methods should be callable by subtypes", 14 | Justification = "By design, subtypes of Model should never themselves use IMutableModel or derived interfaces to access their own members. The IMutableModel family of interfaces is for external types that require read/write access.")] 15 | public class FloorModel : ParquetModel, IMutableFloorModel 16 | { 17 | #region Class Defaults 18 | /// A name to employ for parquets when IsTrench is set, if none is provided. 19 | [Ignore] 20 | public string DefaultTrenchName { get; } = Resources.PlayerFacingDefaultTrenchName; 21 | 22 | /// The set of values that are allowed for Floor IDs. 23 | public static Range Bounds 24 | => All.FloorIDs; 25 | #endregion 26 | 27 | #region Characteristics 28 | /// The tool used to dig out or fill in the floor. 29 | [Index(8)] 30 | public ModificationTool ModTool { get; private set; } 31 | 32 | /// Player-facing name of the parquet, used when it has been dug out. 33 | [Index(9)] 34 | public string TrenchName { get; private set; } 35 | #endregion 36 | 37 | #region Initialization 38 | /// 39 | /// Initializes a new instance of the class. 40 | /// 41 | /// Unique identifier for the . Cannot be null. 42 | /// Player-friendly name of the . Cannot be null. 43 | /// Player-friendly description of the . 44 | /// Comment of, on, or by the . 45 | /// Any additional information about the . 46 | /// The of the awarded to the player when a character gathers this . 47 | /// Which, if any, this helps to generate. 48 | /// Describes which, if any, (s) this helps form. 49 | /// The tool used to modify this . 50 | /// The name to use for this when it has been dug out. 51 | public FloorModel(ModelID id, string name, string description, string comment, 52 | IEnumerable tags = null, ModelID? itemID = null, 53 | IEnumerable addsToBiome = null, IEnumerable addsToRoom = null, 54 | ModificationTool modTool = ModificationTool.None, string trenchName = null) 55 | : base(Bounds, id, name, description, comment, tags, itemID, addsToBiome, addsToRoom) 56 | { 57 | ModTool = modTool; 58 | TrenchName = 59 | // TODO [Robustness] Are there places we use IsNullOrEmpty where IsNullOrWhiteSpace would be more appropriate? 60 | string.IsNullOrWhiteSpace(trenchName) 61 | ? DefaultTrenchName 62 | : trenchName; 63 | } 64 | #endregion 65 | 66 | #region IMutableFloorModel Implementation 67 | /// The tool used to dig out or fill in the floor. 68 | /// 69 | /// By design, subtypes of should never themselves use . 70 | /// IMutableFloorModel is for external types that require read/write access. 71 | /// 72 | [Ignore] 73 | ModificationTool IMutableFloorModel.ModTool 74 | { 75 | get => ModTool; 76 | set => ModTool = LibraryState.IsPlayMode 77 | ? Logger.DefaultWithImmutableInPlayLog(nameof(ModTool), ModTool) 78 | : value; 79 | } 80 | 81 | /// Player-facing name of the parquet, used when it has been dug out. 82 | /// 83 | /// By design, subtypes of should never themselves use . 84 | /// IMutableFloorModel is for external types that require read/write access. 85 | /// 86 | [Ignore] 87 | string IMutableFloorModel.TrenchName 88 | { 89 | get => TrenchName; 90 | set => TrenchName = LibraryState.IsPlayMode 91 | ? Logger.DefaultWithImmutableInPlayLog(nameof(TrenchName), TrenchName) 92 | : value; 93 | } 94 | #endregion 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ParquetClassLibrary/Parquets/CollectibleModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using CsvHelper.Configuration.Attributes; 4 | using Parquet.Biomes; 5 | 6 | namespace Parquet.Parquets 7 | { 8 | /// 9 | /// Configurations for a sandbox collectible object, such as crafting materials. 10 | /// 11 | [SuppressMessage("Design", "CA1033:Interface methods should be callable by subtypes", 12 | Justification = "By design, subtypes of Model should never themselves use IMutableModel or derived interfaces to access their own members. The IMutableModel family of interfaces is for external types that require read/write access.")] 13 | public class CollectibleModel : ParquetModel, IMutableCollectibleModel 14 | { 15 | #region Class Defaults 16 | /// The set of values that are allowed for Collectible IDs. 17 | public static Range Bounds 18 | => All.CollectibleIDs; 19 | #endregion 20 | 21 | #region Characteristics 22 | /// The effect generated when a character encounters this Collectible. 23 | [Index(8)] 24 | public CollectingEffect CollectionEffect { get; private set; } 25 | 26 | /// 27 | /// The scale in points of the effect. 28 | /// For example, how much to alter a stat if the is set to alter a stat. 29 | /// 30 | [Index(9)] 31 | public int EffectAmount { get; private set; } 32 | #endregion 33 | 34 | #region Initialization 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// Unique identifier for the . Cannot be null. 39 | /// Player-friendly name of the . Cannot be null. 40 | /// Player-friendly description of the . 41 | /// Comment of, on, or by the . 42 | /// Any additional information about the . 43 | /// The of the that this corresponds to, if any. 44 | /// A set of flags indicating which, if any, this parquet helps to generate. 45 | /// A set of flags indicating which, if any, this parquet helps to generate. 46 | /// Effect of this . 47 | /// The scale in points of the effect. For example, how much to alter a stat if is set to alter a stat. 48 | public CollectibleModel(ModelID id, string name, string description, string comment, 49 | IEnumerable tags = null, ModelID? itemID = null, 50 | IEnumerable addsToBiome = null, IEnumerable addsToRoom = null, 51 | CollectingEffect collectionEffect = CollectingEffect.None, int effectAmount = 0) 52 | : base(Bounds, id, name, description, comment, tags, itemID, addsToBiome, addsToRoom) 53 | { 54 | CollectionEffect = collectionEffect; 55 | EffectAmount = effectAmount; 56 | } 57 | #endregion 58 | 59 | #region IMutableCollectibleModel Implementation 60 | /// The effect generated when a character encounters this Collectible. 61 | /// 62 | /// By design, subtypes of should never themselves use . 63 | /// IMutableCollectibleModel is for external types that require read/write access. 64 | /// 65 | [Ignore] 66 | CollectingEffect IMutableCollectibleModel.CollectionEffect 67 | { 68 | get => CollectionEffect; 69 | set => CollectionEffect = LibraryState.IsPlayMode 70 | ? Logger.DefaultWithImmutableInPlayLog(nameof(CollectionEffect), CollectionEffect) 71 | : value; 72 | } 73 | 74 | /// 75 | /// The scale in points of the effect. 76 | /// For example, how much to alter a stat if the is set to alter a stat. 77 | /// 78 | /// 79 | /// By design, subtypes of should never themselves use . 80 | /// IMutableCollectibleModel is for external types that require read/write access. 81 | /// 82 | [Ignore] 83 | int IMutableCollectibleModel.EffectAmount 84 | { 85 | get => EffectAmount; 86 | set => EffectAmount = LibraryState.IsPlayMode 87 | ? Logger.DefaultWithImmutableInPlayLog(nameof(EffectAmount), EffectAmount) 88 | : value; 89 | } 90 | #endregion 91 | } 92 | } 93 | --------------------------------------------------------------------------------