Dynamic Content
Note: This is an automatic translation from Russian. It looks good enough, but there may be inaccuracies. We are sorry for the inconvenience.
Since the hero of our game already has a key to the cave and can get there, it's time to create this location.
So far, we have defined the content of a location, that is, the Content, Options and Objects properties, in its constructor. However, this may not always be convenient.
Suppose that according to the plot of our game, when a player first enters a cave, an evil monster lives there and immediately attacks the hero. And when the hero defeats the monster, there will be a treasure chest in the cave. Alternatively, you can create two versions of the cave location, and send a hero to them, depending on, for example, the value of the QuestStage property. But the TeravQuest library allows you to execute certain code on each visit or each rendering of a location, dynamically changing its content. The virtual methods of the Location class OnVisit and OnDraw are used for this.
The OnVisit method is always automatically called when the GoTo method of the Game class is called. The OnDraw method is also always called when using the GoTo method, but it's also called when using the DrawLocation method of the Game class. At the same time, the GoTo method transitions between locations and places the location from which the transition was made to the PreviousLocation property of the Game class, and the location where they went to the CurrentLocation property. And the DrawLocation method only redraws the current location and there is no transition between locations.
Using the OnVisit method
Create a Cave class with the following content:
using TeravQuest;
namespace MyGame
{
[Serializable]
class Cave : Location
{
public Cave()
{
Name = "Cave";
}
public override void OnVisit(Game game)
{
if ((game as MyGame).QuestStage < 2)
{
Content = "As soon as you entered the cave, something incomprehensible and aggressive pounced on you from the darkness ...";
AddOption(new Option("Fight", Fight));
}
else
{
ClearOptions();
Content = $"<h1>{Name}</h1>" +
"Cute cave";
AddOption(new Option("Go to forest", ToForest));
}
}
private void Fight(Game game)
{
game.GoTo("Fight");
}
private void ToForest(Game game)
{
game.GoTo("Forest");
}
}
}
Here, the OnVisit method is used to fill the location with content. When the hero visits the location, the value of the QuestStage property will be checked.
If the condition is met, and the quest stage is less than 2, then the enemy is waiting for the hero at the location. The corresponding content is placed in the Content property and an action is added that moves the player to the combat location, which we will implement a little later.
Otherwise, if the quest stage is greater than or equal to 2, this means that the player has already dealt with the enemy, and an empty cave awaits him. The reward chest will be added in the next article.
Also, this class implements methods for the corresponding actions, which move the player either to the battle location or back to the forest.
Notice the call to the ClearOptions (); method. This method removes all previously added actions for this location. Since it is obvious that, first of all, the first part of the condition will be triggered, in which the action to start the battle will be added, when the second part of the condition is triggered, when the player comes here the next time, this action will not disappear anywhere. Therefore, if you need different actions at a location, depending on different conditions, the previously added actions must be removed using the ClearOptions method.
Now let's move on to creating a location to fight the enemy. The fight could be implemented right in the cave location, however, so as not to overload the code, and also, following the principles of single responsibility, and for the purpose of possible further reuse, we will move this code to an auxiliary location.
Using the OnDraw method
The battle will take place according to the scheme popular for such games. First, the player chooses where to strike, then which part of the body to defend. After that, the opponent does the same, and, depending on their choice, the result is determined.
Create a Fight class. Let's define, for now, its constructor and the variables necessary to track the battle.
using TeravQuest;
namespace MyGame
{
[Serializable]
class Fight : Location
{
[Serializable]
enum FightStage
{
Attack,
Defense,
Result
}
[Serializable]
enum BodyPart
{
Head,
Body
}
private FightStage stage = FightStage.Attack;
private int playerHealth = 100;
private int monsterHealth = 100;
private BodyPart hit;
private BodyPart defense;
public Fight()
{
Name = "Fight";
}
}
}
We create several variables to store the battle parameters.
The battle will consist of three stages: attack, when the user chooses where to strike, defense, when the user chooses what to defend, and the result, when after the enemy's response, the damage done is calculated and it is checked whether someone has died.
To conveniently store the current stage, an enumeration is declared:
[Serializable] enum FightStage { Attack, Defense, Result }
We mark it with the [Serializable] attribute so that nothing breaks if the player saves and loads the game during the battle.
Next, an enumeration is defined to help store a body part for defense or attack:
[Serializable] enum BodyPart { Head, Body }
We declare variables and initialize some of them with initial values:
// At the beginning of the battle, the player chooses where to hit. private FightStage stage = FightStage.Attack; // The health of the protagonist. private int playerHealth = 100; // Monster health. private int monsterHealth = 100; // Where the hero will hit. private BodyPart hit; // What the hero will protect. private BodyPart defense;
Let's start implementing the OnDraw method. Here we will use it, since the hero is constantly at this location, and to update the contents of the location in the action handlers, we will call the DrawLocation method of the Game class.
In the OnDraw method, we will check what stage of the battle we are at now, and fill the location with the appropriate content. Initially, we are in the attack stage. Let's implement this branch of the condition:
public override void OnDraw(Game game) { if (stage == FightStage.Attack) { Content = $"<h1>{Name}</h1>" + $"<p>Some mysterious something wants to rip you apart and dine with your brains.</p>" + $"<p>Your health: {playerHealth}</p>" + $"<p>Health of something: {monsterHealth}</p>"; ClearOptions(); AddOption(new Option("Attack head ", HitToHead)); AddOption(new Option("Attack body", HitToBody)); } }
In the content of the location, we display the current indicators of the battle: the health of the hero and the enemy. Add two actions to choose where to hit. Let's implement handlers for these actions.
private void HitToHead(Game game) { hit = BodyPart.Head; stage = FightStage.Defense; game.DrawLocation(); } private void HitToBody(Game game) { hit = BodyPart.Body; stage = FightStage.Defense; game.DrawLocation(); }
In the handlers, we fix the player's choice, transfer the battle to the stage of choosing a defense and redraw the location, provoking a call to the OnDraw method. Add the following condition branch to it:
else if (stage == FightStage.Defense) { ClearOptions(); AddOption(new Option("Protect the head", DefenseHead)); AddOption(new Option("Protect the torso", DefenseBody)); }
Actions are only re-created here. The content of the location with the battle statistics remains the same as during the attack stage, so we do not touch the Content property. Let's implement the appropriate handlers.
private void DefenseHead(Game game) { stage = FightStage.Result; defense = BodyPart.Head; game.DrawLocation(); } private void DefenseBody(Game game) { stage = FightStage.Result; defense = BodyPart.Body; game.DrawLocation(); }
Similar to the previous handlers, we set the next stage, fix what the hero will protect and update the location. Let's implement the condition branch in the OnDraw method, which is responsible for the results stage:
else if (stage == FightStage.Result) { ClearOptions(); Content = ""; Random r = new Random(); if (r.Next(2) == (int)hit) Content += "<p>Something was blocking your blow</p>"; else { monsterHealth -= 50; Content += "<p>You have dealt 50 damage.</p>"; } if (r.Next(2) == (int)defense) Content += "<p>You block the blow of something.</p>"; else { playerHealth -= 10; Content += "<p>Something deals 10 damage to you.</p>"; } if (playerHealth < 1) { Content += "<p>You have died a heroic death.</p><p>GAME OVER</p>"; } else if (monsterHealth < 1) { Content += "<p>You have eliminated this misunderstanding.</p>"; (game as MyGame).QuestStage = 2; AddOption(new Option("rest in peace", Finish)); } else AddOption(new Option("Continue", Next)); }
First, the actions and content of the location are cleared.
Then, a random value between 0 and 1 is used to determine if the player has hit an enemy. Since the enumerations we used represent numeric values by default, we can compare them with the randomly dropped number. The first random number represents the enemy's chosen defense. If it coincides with the hero's attack, then the enemy blocks the blow. Otherwise, the hero deals damage to the enemy. The corresponding messages are added to the content of the location.
The second random number is compared in the same way. If it coincided with the hero's defense, then he blocked the blow. Otherwise, the enemy deals damage to the hero.
Then the hero's health is checked. If it is less than 1, the hero died, and the game ends there. Otherwise, the health of the enemy is checked. If it is less than 1, the hero has won: the quest is advanced to the next stage and an action is added to end the battle. Otherwise, an action to continue combat is added, which will start the entire cycle over. Let's implement the appropriate action handlers.
private void Next(Game game) { stage = FightStage.Attack; game.DrawLocation(); } private void Finish(Game game) { game.GoTo(game.PreviousLocation.Name); }
In the Finish method, we return the player to the location from which he came, that is, to the cave.
The whole Fight class looks like this:
using TeravQuest;
namespace MyGame
{
[Serializable]
class Fight : Location
{
[Serializable]
enum FightStage
{
Attack,
Defense,
Result
}
[Serializable]
enum BodyPart
{
Head,
Body
}
private FightStage stage = FightStage.Attack;
private int playerHealth = 100;
private int monsterHealth = 100;
private BodyPart hit;
private BodyPart defense;
public Fight()
{
Name = "Fight";
}
public override void OnDraw(Game game)
{
if (stage == FightStage.Attack)
{
Content = $"<h>{Name}</h1>" +
$"<p>Some mysterious something wants to rip you apart and dine with your brains.</p>" +
$"<p>Your health: {playerHealth}</p>" +
$"<p>Health of something: {monsterHealth}</p>";
ClearOptions();
AddOption(new Option("Attack head", HitToHead));
AddOption(new Option("Attack body", HitToBody));
}
else if (stage == FightStage.Defense)
{
ClearOptions();
AddOption(new Option("Protect the head", DefenseHead));
AddOption(new Option("Protect the torso", DefenseBody));
}
else if (stage == FightStage.Result)
{
ClearOptions();
Content = "";
Random r = new Random();
if (r.Next(2) == (int)hit) Content += "<p>Something was blocking your blow</p>";
else
{
monsterHealth -= 50;
Content += "<p>You have dealt 50 damage.</p>";
}
if (r.Next(2) == (int)defense) Content += "<p>You block the blow of something.</p>";
else
{
playerHealth -= 10;
Content += "<p>Something deals 10 damage to you.</p>";
}
if (playerHealth < 1)
{
Content += "<p>You have died a heroic death.</p><p>GAME OVER</p>";
}
else if (monsterHealth < 1)
{
Content += "<p>You have eliminated this misunderstanding.</p>";
(game as MyGame).QuestStage = 2;
AddOption(new Option("rest in peace", Finish));
}
else AddOption(new Option("Continue", Next));
}
}
private void HitToHead(Game game)
{
hit = BodyPart.Head;
stage = FightStage.Defense;
game.DrawLocation();
}
private void HitToBody(Game game)
{
hit = BodyPart.Body;
stage = FightStage.Defense;
game.DrawLocation();
}
private void DefenseHead(Game game)
{
stage = FightStage.Result;
defense = BodyPart.Head;
game.DrawLocation();
}
private void DefenseBody(Game game)
{
stage = FightStage.Result;
defense = BodyPart.Body;
game.DrawLocation();
}
private void Next(Game game)
{
stage = FightStage.Attack;
game.DrawLocation();
}
private void Finish(Game game)
{
game.GoTo(game.PreviousLocation.Name);
}
}
}
Add locations to the game in the constructor of the MyGame class.
public MyGame()
{
Name = "Shortest quest";
PlayerInventory = new Container();
InventoryVewSource = PlayerInventory.Objects;
QuestStage = 0;
AddLocation(new Home());
AddLocation(new Forest());
AddLocation(new ElvenHouse());
AddLocation(new Cave());
AddLocation(new Fight());
GoTo("Home");
}