Lesson 16.2: Creating the Battle class

Now that we have a decoupled message broker, we can move most of our existing combat logic into the new Battle class.




Lesson Steps

Step 1: Create \Engine\EventArgs\CombatVictoryEventArgs.cs

This is a simple EventArgs class to let our Battle class notify the GameSession class that the player defeated their opponent.

It doesn’t have any payload (additional properties), because we don’t need to pass any additional information with the event – only that the player won the battle.





Step 2: Modify \Engine\Models\LivingEntity.cs

Previously, we had an IsDead property on line 122. I added an IsAlive property, to make a check in our Battle class sound positive – _opponent.IsAlive, instead of !_opponent.IsDead.

I also changed the code for IsDead to “!IsAlive”, so we don’t make a mistake by have one property “CurrentHitPoints <= 0” and the other “CurrentHitPoints > 0”. That’s a good way to accidentally forget an equal sign on one of the properties.


LivingEntity.cs (lines 122-123)



Step 3: Create \Engine\Models\Battle.cs

This is where we’ll put the combat logic.

On line 7, notice that this class implements the IDisposable interface. This means the class must have a Dispose() function. We’re going to use Dispose() to unsubscribe to eventhandlers. If we didn’t do this, we’d get some strange behavior of previous battles staying in memory and potentially interacting with the other classes.

On lines 10 and 11, we store the player and opponent values that are passed into Battle’s constructor.

There’s an enum “Combatant” on line 13. We’ll use this to make the code a little easier to read when we add the logic to decide who attacks first in a battle – the player or the monster.

Lines 19-22 is an expression-bodied function, a little function we can call to get a random number to determine who attacks first in a battle.

Line 24 is the event GameSession will subscribe to, to know if the player won a battle, and if GameSession needs to instantiate new Monster and Battle objects.

Lines 26-42 are the Battle constructor.

It accepts the Player object and Monster and stores them in the private class-level variables for later use.

Then, we subscribe to the combat action events for the Player and Monster and the Monster’s OnKilled event. We don’t subscribe to the Player’s OnKilled event here. We’ll leave that in the GameSession class, in case we add other non-Battle ways for the Player to die – like being cursed or poisoned and receiving damage every turn.

We raise some UI messages, to display what the player is fighting, on lines 35 and 36.

Finally, on lines 38-41, we call the FirstAttacker function. If the Monster is selected, we call the AttackPlayer() function, where the Monster attacks the Player.

On lines 44-58, we have the Player’s attack function. It’s mostly cut-and-pasted from the old GameSession class. But we removed the code to check if the monster died. That will be handled by our eventhandler for the Monster’s OnKilled event.

The Dispose() function on lines 60-65 unsubscribes to the events that were subscribed to in the constructor. Without having this (and calling it), old Battle objects might never be released from memory. The garbage collector will see that something is still subscribed to the events, so it will think the object needs to be kept in memory.

Lines 67-85 contains the logic for a Player victory – giving the Player experience, gold, and the Monster’s loot items. It’s mostly cut-and-pasted from the old GameSession code.

After giving the victory rewards, we raise an OnCombatVictory event on line 84. GameSession will subscribe to this event and use it to know it needs to instantiate new Monster and Battle objects for the next fight.

Lines 87-90 hold the Player’s attack code, which just uses their current weapon on the Monster.

Lines 92- 95 has OnCombatActionPerformed, which was subscribed to by the Player’s and Monster’s OnActionPerformed events in the constructor. All it does is send the attack message to the MessageBroker.





Step 4: Modify \Engine\ViewModels\GameSession.cs

Now we can delete most of the combat logic from GameSession.

Because we’re adding and deleting lines, double-check the property and function names when you make the changes below. Compare your code with the source code listed below when you make your changes.

On line 12, add “_currentBattle”, to store the new Battle object.

On lines 28 and 37, remove the _currentPlayer.OnActionPerformed subscription and unsubscription.

I also renamed the “OnCurrentPlayerKilled” function to “OnPlayerKilled”. This is a single-player game, and “current player” seems redundant.

On line 59, in the CurrentLocation setter, I changed “GetMonsterAtLocation();” to “CurrentMonster = CurrentLocation.GetMonster();”. The GetMonsterAtLocation function was only one line, and only used in two places. So, I decided to eliminate it and just put the one line of code in the places where the function was called.

In the CurrentMonster setter, lines 69-85, I removed the event subscriptions to the Monster’s events and replaced them with subscriptions to the Battle’s events. Notice the call to “_currentBattle.Dispose();” on line 73. This is how we clean up the previous Battle object, before we create the new Battle object.

On line 244, delete the GetMonsterAtLocation() function, because we moved that single line of code into the two locations that used to call this function.

On line 249, replace the old logic for AttackCurrentMonster() with a single line that calls “_currentBattle.AttackOpponent();”, since the combat logic is in the Battle class now.

Delete the OnCurrentPlayerPerformedAction() function on line 310 and OnCurrentMonsterPerformedAction() function on line 315. That logic is in the Battle class now.

Rename “OnCurrentPlayerKilled()”, on line 320, to “OnPlayerKilled()”.

On line 329, change the OnCurrentMonsterKilled() function to just set the CurrentMonster property to a new Monster from the CurrentLocation. All the rewards and looting logic in in the Battle class now.





Step 5: Test the game

We make some significant changes, so let’s make sure the game still works.

If you want to see why we added the Dispose function, you can temporarily comment it out on line 73. The first monster we fight looks OK. But when we fight the second (and subsequent) monsters, you’ll see we start to get extra combat messages.



Additional links for this project

Source code: https://github.com/ScottLilly/SOSCSRPG

Project plan: https://github.com/ScottLilly/SOSCSRPG/projects/1

Discord: https://discord.gg/AUYXYtH

Return to main page

Leave a Reply

Your email address will not be published. Required fields are marked *