Now that the Character Creation screen creates a list of PlayerAttributes, let’s remove the current Dexterity property, add the PlayerAttributes to the LivingEntity class, and fix most of the rest of the program to work with the new PlayerAttributes.
I’m also removing the character “Class” property. Instead of forcing the user to select a class for the player (fighter, wizard, etc.), and limiting what the player might be able to do, I’m making the game more open. Plus, it will be easier to complete the program without this ability.
We’re going to comment out some things to fix in the next lesson, since this lesson already changes 14 files.
Step 1: Modify TestEngine\Services\TestCombatService.cs, TestSaveGameService.cs, and TestEngine\ViewModels\TestGameSession.cs
We’ll fix these in the next lesson, after we update the save/load game functions to use the new Player class structure – with PlayerAttributes.
For now, comment out the following lines:
TestGameSession.cs: 13-18 and 24-31
In TestSaveGameService.cs, delete line 24, where we were checking that the Player’s CharacterClass was “Fighter”. Then comment out all the lines in the test function, from 14-47.
Step 2: Modify Engine\Services\SaveGameService.cs
Since these changes break the save and load game functions, comment out the LoadLastSaveOrCreateNew function (lines 19-46).
In the CreatePlayer function, comment out lines 50-75 and add a new line to return null.
We’ll fix these functions in the nest lesson.
Step 3: Modify Engine\Models\LivingEntity.cs
This is where we’ll add the property to hold the PlayerAttribute values.
On line 3, add the using directive to include “System.Collections.ObjectModel”, so we can have our ObservableCollection property.
Delete the Dexterity property and its backing variable _dexterity
On line 23, add the Attributes property.
Starting on line 148, change the LivingEntity constructor to take an IEnumberable<PlayerAttribute> parameter, instead of the previous dexterity integer parameter.
Delete the line where we used to assign the dexterity parameter to the Dexterity property. Then add lines 146-149, where we add the values from the passed-in playerAttributes parameter to the Attributes property.
Step 4: Modify Engine\Models\Monster.cs
Remove the using directive for System.Collections.ObjectModel (line 2)
Modify the constructor (lines 17-21) to accept the list of PlayerAttributes instead of the dexterity parameter and pass that into the base constructor.
In the GetNewInstance, where we create a clone of the monster, replace the Dexterity parameter with the Attributes property on line 42.
Step 5: Modify Engine\Models\Player.cs
Delete the CharacterClass property and its _characterClass backing variable.
Starting on line 37, change the constructor to accept the list of PlayerAttributes as a parameter and pass it to the base constructor. Also remove the characterClass parameter and the line in the constructor where we used to assign that value to the property.
Step 6: Modify Engine\Models\Trader.cs
Change the hard-coded “18” value we were passing into the base constructor for the dexterity to “new List<PlayerAttributes>()”.
Since we don’t have combat with traders, we don’t need to give them any attributes. If you want the player to be able to fight traders, you’d need to make sure the Traders have a dexterity PlayerAttribute, so they can be an opponent in the CombatService class.
Step 7: Modify Engine\Shared\ExtensionMethods.cs
In the “using” directives, add references for “System.Linq” and “Engine.Models”.
Add the new GetAttribute extension method on lines 43-48. This extension method works on LivingEntity objects and finds the PlayerAttribute that matches the passed-in key. For example, “CurrentPlayer.GetAttribute(“DEX”)” would get the dexterity PlayerAttribute from the player’s Attributes property.
Step 8: Modify Engine\Factories\MonsterFactory.cs
On line 7, I added “using Engine.Services;”, so we can read the GameDetails file and give the Monster objects the same PlayerAttributes as the Player object.
Add a private variable to hold the GameDetails on line 16 and populate it on line 23 – inside the static “constructor” function.
On lines 49-54, get the Dexterity value from the Monsters.xml file and use it for the Monster’s DEX attribute – both the BaseValue and the ModifiedValue. In the future, we can put DiceNotation values in the Monster.xml file, to give monsters random attribute values.
On line 61, pass in the attributes to the Monster constructor.
Step 9: Modify Engine\Services\CombatService.cs
Now that LivingEntity objects don’t have a Dexterity property, we need to replace all references to it with calls to our GetAttribute extension method.
This is on lines 18-21 and 36-39 in the new code.
Step 10: Modify Engine\ViewModels\CharacterCreationViewModel.cs
Let’s move the complete Player object creation here.
On line 3, add a using directive for Engine.Factories, so we can instantiate the default items for the Player’s inventory.
In the GetPlayer function, starting on line 73, pass the PlayerAttribute values into the Player constructor, then add the default inventory items and recipes to the player.
Step 11: Modify Engine\ViewModels\GameSession.cs
Now that we create the Player object in the CharacterCreation window, we can delete the GameSession constructor that used to do that. It’s on lines 145-167.
From now on, when we instantiate a GameSession object, we’ll use the one that accepts a Player object as a parameter.
We can also delete the “using” directives on line 1 and 7, for System.IO and Newtonsoft.Json.Linq.
Step 12: Modify WPFUI\MainWindow.xaml and WPFUI\MainWindow.xaml.cs
In MainWindow.xaml.cs, we’ll combine the two constructors into one that accepts the Player object as a parameter.
Then we’ll comment out the load and save game functions for now.
Comment out lines 142, 147-157, and 194.
Finally, we’ll modify the XAML.
For the Start/Load/Save game functions, I added IsEnabled=False for the MenuItems on lines 36-43.
Then I changed the “Player Stats” section to display all the PlayerAttributes, instead of only the dexterity.
I removed the labels for Class and Dexterity on lines 76-79 and moved the other labels’ Grid.Row up by two, since we deleted the previous two lines.
Finally, I added a ListBox control on lines 87-114, to display the player’s attributes. But this took some messy XAML to format everything for a dynamic list of PlayerAttribute objects. That makes me think it might be time to work on the UI some more.
This uses the IsSharedSizeScope and SharedSizeGroup to make sure the attributes’ ModifiedValue line up in a nice column. This lines up the values in columns, like a datagrid would, but without all the extra “spreadsheet” borders around all the values.
Step 13: Test the game
Make sure the game works and that the Player you create in the Character Creation window shows up in the game’s UI.
The next lesson will be modifying the save and load game functions to work with the new Player object and its PlayerAttribute properties.
After that, I’d like to convert the program to .NET 5 (or at least .NET Core 3.1) so it can run in Linux and Macs. That might be a little messy since I haven’t seen an automated way to do that.
Then, it would be nice to work on better graphics for the UI.
Let me know what you think. The current “to do” list is at https://github.com/ScottLilly/SOSCSRPG/projects/1, but the order can change.
Additional links for this project
Source code: https://github.com/ScottLilly/SOSCSRPG
Project plan: https://github.com/ScottLilly/SOSCSRPG/projects/1