#ifndef DIALOGUE_HPP
#define DIALOGUE_HPP
#include <string>
#include <vector>
#include <iostream>
// Gameplay is expressed using dialogues, which present a piece of
// information and some responses, and the ask the user to pick one. If
// they do not pick a valid one then the dialogue loops until they do
class Dialogue
{
private:
// Initial piece of information that the dialogue displays
std::string description;
// A vector of choices that will be outputted. No numbering is
// necessary, the dialogue does that automatically
std::vector<std::string> choices;
public:
// Run the dialogue
int activate()
{
// Output the information
std::cout << description << std::endl;
// Output and number the choices
for(int i = 0; i < this->choices.size(); ++i)
{
std::cout << i+1 << ": " << this->choices[i] << std::endl;
}
int userInput = -1;
// Repeatedly read input from stdin until a valid option is
// chosen
while(true)
{
std::cin >> userInput;
// 'Valid' means within the range of numbers outputted
if(userInput >= 0 && userInput <= choices.size())
{
return userInput;
}
}
return 0;
}
Dialogue(std::string description, std::vector<std::string> choices)
{
this->description = description;
this->choices = choices;
}
Dialogue()
{
}
};
#endif /* DIALOGUE_HPP */
#ifndef CREATURE_HPP
#define CREATURE_HPP
#include "inventory.hpp"
#include "weapon.hpp"
#include "armour.hpp"
#include <string>
class Creature
{
public:
// Name of the creature and the name of its class, if it has one
// Class may be Fighter, Rogue etc
std::string name;
std::string className;
// Creature stats. Reasonable values are in parentheses
int health; // Current hit points (10-1000+)
int maxHealth; // Maximum hit points (10-1000+)
int str; // Strength. Determines damage in battle (1-100)
int end; // Endurance. Determines maximum health (1-100)
int dex; // Dexterity. Determines speed in battle (1-100)
double hitRate; // Modifier to hit chance. (1-150)
// Current level of the creature. Determines the amount of experience
// that it gives to the victor when defeated (see Battle class for more)
// and the amount of experience required to level up again. Upon
// levelling up the creature will gain stat improvements.
// 1-50 is reasonable
unsigned int level;
// Current experience. 0-1M is reasonable, see the levelup() function
// for a decent scale
unsigned int exp;
// Items that the creature possesses
Inventory inventory;
// Currently equipped weapon. Used as a pointer to an atlas entry,
// but not necessary. nullptr denotes that no weapon is equipped
Weapon* equippedWeapon;
// Armour currently equipped into each slot
Armour* equippedArmour[Armour::Slot::N];
Creature(std::string name, int health, int str, int end, int dex, double hitRate,
unsigned int level = 1, std::string className = "")
{
this->name = name;
this->health = health;
this->maxHealth = health;
this->str = str;
this->end = end;
this->dex = dex;
this->hitRate = hitRate;
this->className = className;
this->equippedArmour[Armour::Slot::HEAD] = nullptr;
this->equippedArmour[Armour::Slot::TORSO] = nullptr;
this->equippedArmour[Armour::Slot::LEGS] = nullptr;
this->equippedWeapon = nullptr;
this->level = level;
this->exp = 0;
}
Creature()
{
this->equippedArmour[Armour::Slot::HEAD] = nullptr;
this->equippedArmour[Armour::Slot::TORSO] = nullptr;
this->equippedArmour[Armour::Slot::LEGS] = nullptr;
this->equippedWeapon = nullptr;
this->level = 1;
this->exp = 0;
}
// Equip a weapon by setting the equipped weapon pointer. Currently
// a pointless function (simple enough to be rewritten each time)
// but handy if dual wielding is ever added, or shields etc
void equipWeapon(Weapon* weapon)
{
this->equippedWeapon = weapon;
return;
}
// Equip the armour into it's correct slot. A slightly more useful
// function!
void equipArmour(Armour* armour)
{
this->equippedArmour[(int)armour->slot] = armour;
return;
}
// Calculates the experience required to reach a certain level,
// *in total*. Really this is class specific and not object specific
unsigned int expToLevel(unsigned int level)
{
// Exp to level x = 128*x^2
return 128 * level * level;
}
// Level the creature to the next level if it has enough experience
// to do so, returning true if it could level up and false otherwise.
bool levelUp()
{
// We want the experience to the next level, not the current level
if(this->exp >= expToLevel(this->level+1))
{
// Advance to the next level
++level;
// Variables to keep track of stat changes. Neater than
// having a bunch of stat increases all over the place,
// and removes the issue of the next level's stats affecting
// themselves (increasing endurance then increasing health
// based on the boosted instead of the original value, for
// example
unsigned int healthBoost = 0;
unsigned int strBoost = 0;
unsigned int endBoost = 0;
unsigned int dexBoost = 0;
// Give a large boost to health every third level
if(level % 3 == 0)
{
// Randomly increase health, but always give a sizeable
// chunk proportional to the creature's endurance
healthBoost = 10 + (rand() % 4) + this->end / 4;
}
else
{
// Just increase health by a small amount
healthBoost = this->end / 4;
}
// If the creature is a fighter, then favour strength and
// endurance boosts over dexterity, but increase dexterity
// 50% of the time too
if(this->className == "Fighter")
{
strBoost = 1;
endBoost = 1;
if(rand() % 2 == 0) dexBoost = 1;
}
// Same as for fighter but favour dexterity and endurance
// instead. Rogue's favour endurance too in order to keep
// them at roughly the same capability
else if(this->className == "Rogue")
{
endBoost = 1;
dexBoost = 1;
if(rand() % 2 == 0) strBoost = 1;
}
// Adjust all of the variables accordingly
this->maxHealth += healthBoost;
this->str += strBoost;
this->end += endBoost;
this->dex += dexBoost;
// Tell the user that they grew a level, what the boosts where
// and what their stats are now
std::cout << this->name << " grew to level " << level << "!\n";
std::cout << "Health +" << healthBoost << " -> " << this->maxHealth << std::endl;
std::cout << "Str +" << strBoost << " -> " << this->str << std::endl;
std::cout << "End +" << endBoost << " -> " << this->end << std::endl;
std::cout << "Dex +" << dexBoost << " -> " << this->dex<< std::endl;
std::cout << "----------------\n";
return true;
}
return false;
}
};
#endif /* CREATURE_HPP */
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <utility>
#include <cstdlib>
#include <ctime>
[i]#include "atlas.hpp"
#include "item.hpp"
#include "weapon.hpp"
#include "armour.hpp"
#include "inventory.hpp"
#include "creature.hpp"
#include "dialogue.hpp"
#include "area.hpp"
#include "battle.hpp"[/i]
// New character menu
Creature dialogue_newchar();
// Character information menu, displays the items the player has, their
// current stats etc.
void dialogue_menu(Creature& player);
int main(void)
{
std::vector<Creature> creatureAtlas;
std::vector<Item> itemAtlas;
std::vector<Weapon> weaponAtlas;
std::vector<Armour> armourAtlas;
std::vector<Area> areaAtlas;
Creature player;
// Build the atlases
buildatlas_creature(creatureAtlas);
buildatlas_item(itemAtlas);
buildatlas_weapon(weaponAtlas);
buildatlas_armour(armourAtlas);
buildatlas_area(areaAtlas, itemAtlas, weaponAtlas, armourAtlas, creatureAtlas);
// Seed the random number generator with the system time, so the
// random numbers produced by rand() will be different each time
srand(time(NULL));
// Main game menu dialogue
int result = Dialogue(
"Welcome!",
{"New Game"}).activate();
switch(result)
{
case 1: player = dialogue_newchar(); break;
default: return 0; break;
}
// Set the current area to be the first area in the atlas, essentially
// placing the player there upon game start
Area* currentArea = &(areaAtlas[0]);
// Play the game until a function breaks the loop and closes it
while(1)
{
// If the player has died then inform them as such and close
// the program
if(player.health <= 0)
{
std::cout << "\t----YOU DIED----\n Game Over\n";
return 0;
}
// If the area the player is in has any creatures inside it,
// then begin a battle with the first creature in the list
if(currentArea->creatures.size() > 0)
{
Battle(&player, currentArea->creatures[0]).run();
// Remove the creature from the area. This is fine to do
// because if the player wins the creature will not respawn,
// and if the creature wins the player isn't around to see it
// (This does break the 'non-mutable' feature of the atlases,
// but doing so saves a lot of memory, as we don't need to keep
// two versions of each area)
currentArea->creatures.pop_back();
}
// Activate the current area's dialogue
result = currentArea->dialogue.activate();
// These could be moved inside of the area code using an event
// style system, but that allows for much less flexibility with
// what happens in each area. Since we're defining the areas in
// code anyway, sticking with this isn't too much of a problem,
// and it keeps things easy to understand
if(currentArea == &(areaAtlas[0]))
{
switch(result)
{
// Open the menu
case 0:
dialogue_menu(player);
break;
case 1:
// Move to area 1
currentArea = &(areaAtlas[1]);
break;
case 2:
// Search the area
currentArea->search(player);
break;
default:
break;
}
}
else if(currentArea == &(areaAtlas[1]))
{
switch(result)
{
// Open the menu
case 0:
dialogue_menu(player);
break;
// Move to area 0
case 1:
currentArea = &(areaAtlas[0]);
break;
// Search the area
case 2:
currentArea->search(player);
break;
default:
break;
}
}
}
return 0;
}
// Create a new character
Creature dialogue_newchar()
{
// Ask for a name and class
// Name does not use a dialogue since dialogues only request options,
// not string input. Could be generalised into its own TextInput
// class, but not really necessary
std::cout << "Choose your name" << std::endl;
std::string name;
std::cin >> name;
int result = Dialogue(
"Choose your class",
{"Fighter", "Rogue"}).activate();
switch(result)
{
// Fighter class favours health and strength
case 1:
return Creature(name, 35, 20, 10, 5, 10.0, 1, "Fighter");
break;
// Rogue class favours dexterity and hit rate
case 2:
return Creature(name, 30, 5, 10, 20, 15.0, 1, "Fighter");
break;
// Default case that should never happen, but it's good to be safe
default:
return Creature(name, 30, 10, 10, 10, 10.0, 1, "Adventurer");
break;
}
}
void dialogue_menu(Creature& player)
{
// Output the menu
int result = Dialogue(
"Menu\n====",
{"Items", "Equipment", "Character"}).activate();
switch(result)
{
// Print the items that the player owns
case 1:
std::cout << "Items\n=====\n";
player.inventory.print();
std::cout << "----------------\n";
break;
// Print the equipment that the player is wearing (if they are
// wearing anything) and then ask if they want to equip a weapon
// or some armour
case 2:
{
std::cout << "Equipment\n=========\n";
std::cout << "Head: "
<< (player.equippedArmour[Armour::Slot::HEAD] != nullptr ?
player.equippedArmour[Armour::Slot::HEAD]->name : "Nothing")
<< std::endl;
std::cout << "Torso: "
<< (player.equippedArmour[Armour::Slot::TORSO] != nullptr ?
player.equippedArmour[Armour::Slot::TORSO]->name : "Nothing")
<< std::endl;
std::cout << "Legs: "
<< (player.equippedArmour[Armour::Slot::LEGS] != nullptr ?
player.equippedArmour[Armour::Slot::LEGS]->name : "Nothing")
<< std::endl;
std::cout << "Weapon: "
<< (player.equippedWeapon != nullptr ?
player.equippedWeapon->name : "Nothing")
<< std::endl;
int result2 = Dialogue(
"",
{"Equip Armour", "Equip Weapon", "Close"}).activate();
// Equipping armour
if(result2 == 1)
{
int userInput = 0;
// Cannot equip armour if they do not have any
// Print a list of the armour and retrieve the amount
// of armour in one go
int numItems = player.inventory.print_armour(true);
if(numItems == 0) break;
while(!userInput)
{
// Choose a piece of armour to equip
std::cout << "Equip which item?" << std::endl;
std::cin >> userInput;
// Equipment is numbered but is stored in a list,
// so the number must be converted into a list element
if(userInput >= 1 && userInput <= numItems)
{
int i = 1;
for(auto it : player.inventory.armour)
{
if(i++ == userInput)
{
// Equip the armour if it is found
player.equipArmour(it.first);
break;
}
}
}
}
}
// Equip a weapon, using the same algorithms as for armour
else if(result2 == 2)
{
int userInput = 0;
int numItems = player.inventory.print_weapons(true);
if(numItems == 0) break;
while(!userInput)
{
std::cout << "Equip which item?" << std::endl;
std::cin >> userInput;
if(userInput >= 1 && userInput <= numItems)
{
int i = 1;
for(auto it : player.inventory.weapons)
{
if(i++ == userInput)
{
player.equipWeapon(it.first);
break;
}
}
}
}
}
std::cout << "----------------\n";
break;
}
// Output the character information, including name, class (if
// they have one), stats, level, and experience
case 3:
std::cout << "Character\n=========\n";
std::cout << player.name;
if(player.className != "") std::cout << " the " << player.className;
std::cout << std::endl;
std::cout << "HP: " << player.health << " / " << player.maxHealth << std::endl;
std::cout << "Str: " << player.str << std::endl;
std::cout << "End: " << player.end << std::endl;
std::cout << "Dex: " << player.dex << std::endl;
std::cout << "Lvl: " << player.level << " (" << player.exp;
std::cout << " / " << player.expToLevel(player.level+1) << ")" << std::endl;
std::cout << "----------------\n";
break;
default:
break;
}
return;
}
#ifndef ITEM_HPP
#define ITEM_HPP
#include <string>
class Item
{
public:
// Name and description of the item
std::string name;
std::string description;
// Standard constructors, nothing special
Item(std::string name, std::string description)
{
this->name = name;
this->description = description;
}
Item()
{
}
};
#endif /* ITEM_HPP */
#ifndef WEAPON_HPP
#define WEAPON_HPP
#include "item.hpp"
#include <string>
// Weapons are items, so they should inherit their properties
class Weapon : public Item
{
public:
// Weapon damage. See the Battle class for formula, but values
// between 1-50 are reasonable
unsigned damage;
// Modifier to hit chance. Small values are encouraged, e.g.
// 5-30%
double hitRate;
// Pass inherited qualities to the normal item constructor
Weapon(std::string name, std::string description, int damage, double hitRate) :
Item(name, description)
{
this->damage = damage;
this->hitRate = hitRate;
}
Weapon()
{
}
};
#endif /* WEAPON_HPP */
#ifndef ARMOUR_HPP
#define ARMOUR_HPP
#include "item.hpp"
#include <string>
// Armour should also inherit item properties
class Armour : public Item
{
public:
// Armour can go into one of three slots, and only one piece of
// armour may occupy each slot at a time. The N is there to quickly
// retrieve the number of slots, without having to use another variable
enum Slot { TORSO, HEAD, LEGS, N };
Slot slot;
// See Battle class for exact formula, values from 1-50 are reasonable
int defense;
// Usual constructor
Armour(std::string name, std::string description, int defense, Armour::Slot slot) :
Item(name, description)
{
this->defense = defense;
this->slot = slot;
}
Armour()
{
}
};
#endif /* ARMOUR_HPP */
#ifndef INVENTORY_HPP
#define INVENTORY_HPP
#include "item.hpp"
#include "weapon.hpp"
#include "armour.hpp"
#include <list>
#include <utility>
#include <iostream>
class Inventory
{
public:
// Whilst weapons and armour are also items, they have their own
// specific properties and so cannot be stored inside the same
// list as the items. We use a list and not a vector as inventories
// are highly mutable. This way they can also be efficiently sorted
// The first element of the pair stores a pointer to the item in
// the item/weapon/armour atlas, defined in main(), and the second
// element stores the quantity of the item
std::list<std::pair<Item*, int>> items;
std::list<std::pair<Weapon*, int>> weapons;
std::list<std::pair<Armour*, int>> armour;
Inventory()
{
}
Inventory(std::list<std::pair<Item*, int>> items,
std::list<std::pair<Weapon*, int>> weapons,
std::list<std::pair<Armour*, int>> armour)
{
this->items = items;
this->weapons = weapons;
this->armour = armour;
}
// Remove all items from the inventory, destroying them in the process
// (They remain in the atlas though)
void clear()
{
this->items.clear();
this->weapons.clear();
this->armour.clear();
}
// Add an item to the inventory, specified by a pointer to it
// from the item atlas (technically does not need to point there,
// but it should anyway)
void add_item(Item* item, int count)
{
// Perform the same operation as merging, but for a single item
for(auto& it : this->items)
{
if(it.first == item)
{
it.second += count;
return;
}
}
// If the item doesn't already exist in the inventory, then a
// pair must be created too
this->items.push_back(std::make_pair(item, count));
}
// Same as for items
void add_weapon(Weapon* weapon, int count)
{
for(auto& it : this->weapons)
{
if(it.first == weapon)
{
it.second += count;
return;
}
}
this->weapons.push_back(std::make_pair(weapon, count));
}
// Same as for items
void add_armour(Armour* armour, int count)
{
for(auto& it : this->armour)
{
if(it.first == armour)
{
it.second += count;
return;
}
}
this->armour.push_back(std::make_pair(armour, count));
}
// Remove the specified number of items from the inventory
void remove_item(Item* item, int count)
{
// Iterate through the items, and if they are found then decrease
// the quantity by the quantity removed
for(auto& it : this->items)
{
if(it.first == item) it.second -= count;
}
// Iterate through the list again, and remove any elements from
// the list that have zero or less for their quantity
// We do this in two passes because removing an element from
// a list during a for loop invalidates the iterators, and the
// loop stops working
this->items.remove_if([](std::pair<Item*, int>& element)
{
return element.second < 1;
});
}
// Same as for items
void remove_weapon(Weapon* weapon, int count)
{
for(auto& it : this->weapons)
{
if(it.first == weapon) it.second -= count;
}
this->weapons.remove_if([](std::pair<Weapon*, int>& element)
{
return element.second < 1;
});
}
// Same as for items
void remove_armour(Armour* armour, int count)
{
for(auto& it : this->armour)
{
if(it.first == armour) it.second -= count;
}
this->armour.remove_if([](std::pair<Armour*, int>& element)
{
return element.second < 1;
});
}
// Merge the specified inventory with the current one, adding
// item quantities together if they already exist and adding the item
// into a new slot if they do not
void merge(Inventory* inventory)
{
// You can't merge an inventory with itself!
if(inventory == this) return;
// Loop through the items to be added, and add them. Our addition
// function will take care of everything else for us
for(auto it : inventory->items)
{
this->add_item(it.first, it.second);
}
// Do the same for the weapons
for(auto it : inventory->weapons)
{
this->add_weapon(it.first, it.second);
}
// Do the same for the armour
for(auto it : inventory->armour)
{
this->add_armour(it.first, it.second);
}
return;
}
// Output a list of the items onto stdout, formatted nicely and
// numbered if required
int print_items(bool label = false)
{
unsigned int i = 1;
for(auto it : this->items)
{
// Number the items if asked
if(label) std::cout << i++ << ": ";
// Output the item name, quantity and description, e.g.
// Gold Piece (29) - Glimmering discs of wealth
std::cout << it.first->name << " (" << it.second << ") - ";
std::cout << it.first->description << std::endl;
}
// Return the number of items outputted, for convenience
return this->items.size();
}
// Same as for items
int print_weapons(bool label = false)
{
unsigned int i = 1;
for(auto it : this->weapons)
{
if(label) std::cout << i++ << ": ";
std::cout << it.first->name << " (" << it.second << ") - ";
std::cout << it.first->description << std::endl;
}
return this->weapons.size();
}
// Same as for items
int print_armour(bool label = false)
{
unsigned int i = 1;
for(auto it : this->armour)
{
if(label) std::cout << i++ << ": ";
std::cout << it.first->name << " (" << it.second << ") - ";
std::cout << it.first->description << std::endl;
}
return this->armour.size();
}
// Print the entire inventory; items, then weapons, then armour,
// but if the inventory is empty then output "Nothing"
void print(bool label = false)
{
if(this->items.size() == 0 &&
this->weapons.size() == 0 &&
this->armour.size() == 0)
{
std::cout << "Nothing" << std::endl;
}
else
{
this->print_items(label);
this->print_weapons(label);
this->print_armour(label);
}
return;
}
};
#endif /* INVENTORY_HPP */
#ifndef AREA_HPP
#define AREA_HPP
#include "inventory.hpp"
#include "creature.hpp"
#include "dialogue.hpp"
#include <vector>
// Movement is achieved through the use of areas, which are contained
// units of space consisting of an inventory, a list of creatures and
// a dialogue
class Area
{
public:
// Dialogue is run whenever the area is entered
Dialogue dialogue;
// Items contained within the area. Not split into individual containers
// for simplicity
Inventory items;
// Creatures contained within the area. Currently this is limited
// to just one creature due to how the battle system works, but it
// made sense to set it up as a vector from the start to simplify
// things later
std::vector<Creature*> creatures;
Area(Dialogue dialogue, Inventory items,
std::vector<Creature*> creatures)
{
this->dialogue = dialogue;
this->items = items;
this->creatures = creatures;
}
Area()
{
}
// Search the area for items and give them to the searcher, notifying
// them of their rewards
void search(Creature& player)
{
std::cout << "You find:" << std::endl;
this->items.print();
player.inventory.merge(&(this->items));
this->items.clear();
return;
}
};
#endif /* AREA_HPP */
#include "atlas.hpp"
void buildatlas_creature(std::vector<Creature>& atlas)
{
// Fill the atlas
// Creature(Name, Health, Str, End, Dex, Hit Rate, Level)
atlas.push_back(Creature("Rat", 8, 8, 8, 12, 2.0, 1));
return;
}
void buildatlas_item(std::vector<Item>& atlas)
{
// Item(Name, Description)
atlas.push_back(Item("Gold Coin", "A small disc made of lustrous metal"));
atlas.push_back(Item("Iron Key", "A heavy iron key with a simple cut"));
return;
}
void buildatlas_weapon(std::vector<Weapon>& atlas)
{
// Weapon(Name, Description, Damage, Hit Rate)
atlas.push_back(Weapon("Iron Dagger", "A short blade made of iron with a leather-bound hilt", 5, 10.0));
atlas.push_back(Weapon("Excalibur", "The legendary blade, bestowed upon you by the Lady of the Lake", 35, 35.0));
return;
}
void buildatlas_armour(std::vector<Armour>& atlas)
{
// Armour(Name, Description, Defense, Slot)
atlas.push_back(Armour("Leather Cuirass", "Torso armour made of tanned hide", 4, Armour::Slot::TORSO));
return;
}
void buildatlas_area(std::vector<Area>& atlas,
std::vector<Item>& items, std::vector<Weapon>& weapons,
std::vector<Armour>& armour, std::vector<Creature>& creatures)
{
// Area definitions are somewhat more complicated:
atlas.push_back(Area(Dialogue( // Standard dialogue definiton
"You are in room 1", // Description
{"Go to room 2", "Search"}), // Choices
Inventory( // Area inventory
{
std::make_pair(&items[0], 5) // Pair of item and quantity
},
{
std::make_pair(&weapons[0], 1) // Pair of weapon and quantity
},
{
std::make_pair(&armour[0], 1) // Pair of armour and quantity
}),
{ // Creatures
}));
atlas.push_back(Area(Dialogue(
"You are in room 2",
{"Go to room 1", "Search"}),
Inventory(
{
std::make_pair(&items[0], 10),
std::make_pair(&items[1], 1)
},
{
},
{
}),
{
&creatures[0]
}));
return;
}
#ifndef BATTLE_HPP
#define BATTLE_HPP
#include "dialogue.hpp"
#include "creature.hpp"
#include "armour.hpp"
#include "weapon.hpp"
#include <iostream>
class Battle
{
public:
// Dialogue used to ask the player battle choices
Dialogue dialogue;
// Creatures in combat. creatures[0] is the player
Creature* creatures[2];
Battle()
{
}
Battle(Creature* player, Creature* b)
{
// Start a battle with the player and another creature
this->creatures[0] = player;
this->creatures[1] = b;
// Set up the dialogue. Defending offers no tactical advangtages
// in this battle system
this->dialogue = Dialogue("What will you do?",
{
"Attack",
"Defend"
});
}
// Creature a attacks creature b, and b takes damage accordingly
void attack(Creature* a, Creature* b)
{
std::cout << a->name << " attacks!\n";
// Damage that a will inflict on b
int damage = 0;
// Cumulative modifier to hitRate
double hitRate = a->hitRate;
// If a has equipped a weapon, then add the weapon damage on
// to the current damage and add the hit rate of the weapon on to
// the current hit rate
if(a->equippedWeapon != nullptr)
{
damage += a->equippedWeapon->damage;
hitRate += a->equippedWeapon->hitRate;
}
// Increase the damage by half the attacker's strength
damage += a->str / 2;
// Damage that b will block
int defense = 0;
// Sum the defense values of the armour that b has equipped, and
// increase the defense by the summed value
for(int i = 0; i < Armour::Slot::N; ++i)
{
if(b->equippedArmour[i] != nullptr)
defense += b->equippedArmour[i]->defense;
}
// Decrease the damage by the damage blocked, then ensure that
// damage is always inflicted (we do not want battles to last
// forever, nor to we want attacks to heal the wounded!)
damage -= defense;
if(damage < 1) damage = 1;
// Add the hit rate to the base hit rate and subract the target's
// dexterity from it. Instead of halving it to normalise it into
// a percentage, we just double the range of randomly generated
// values
if(rand() % 201 <= 170 + hitRate - b->dex)
{
// The attack hit, so subtract the damage
std::cout << b->name << " takes " << damage << " damage!\n";
b->health -= damage;
}
else
{
// The attack missed
std::cout << a->name << " missed!\n";
}
return;
}
// Allow the player to act
void playerTurn()
{
// Activate the dialogue and allow the player to choose their
// battle option
int result = this->dialogue.activate();
switch(result)
{
// Attack the enemy
case 1:
attack(creatures[0], creatures[1]);
break;
// Defend, skipping to the enemy's turn
case 2:
std::cout << creatures[0]->name << " defends!\n";
break;
default:
break;
}
return;
}
// Allow the enemy to attack
void enemyTurn()
{
// Battle system does not currently allow for any kind of
// tactics, so make the enemy attack blindly
attack(creatures[1], creatures[0]);
return;
}
// Return true if the creature is dead. Split into it's own function
// to allow easy addition of effects which simulate death, such as
// petrifaction or banishment
bool isdead(Creature* creature)
{
if(creature->health <= 0)
{
return true;
}
return false;
}
// Run a round of the battle
bool activate()
{
// The creature with the highest dexterity attacks first, with
// preference to the player
if(creatures[0]->dex >= creatures[1]->dex)
{
// Run each turn and check if the foe is dead at the end of
// each
this->playerTurn();
if(isdead(creatures[1]))
{
std::cout << creatures[1]->name << " was vanquished!\n";
return true;
}
this->enemyTurn();
if(isdead(creatures[0]))
{
std::cout << creatures[0]->name << " was vanquished!\n";
return true;
}
}
else
{
this->enemyTurn();
if(isdead(creatures[0]))
{
std::cout << creatures[0]->name << " was vanquished!\n";
return true;
}
this->playerTurn();
if(isdead(creatures[1]))
{
std::cout << creatures[1]->name << " was vanquished!\n";
return true;
}
}
return false;
}
// Begin the battle
void run()
{
std::cout << creatures[1]->name << " appears!" << std::endl;
// Run the battle until one creature dies
while(!this->activate());
// If the enemy is dead, then allocate experience to the player
if(isdead(creatures[1]))
{
// Give experience to the player equal to one eigth of the
// experience the enemy gained to reach it's next level
unsigned int expGain = creatures[1]->expToLevel(creatures[1]->level+1) / 8;
std::cout << "Gained " << expGain << " exp!\n";
creatures[0]->exp += expGain;
// Repeatedly level up the player until they are the highest
// level they can be for their experience
while(creatures[0]->levelUp());
}
return;
}
};
#endif /* BATTLE_HPP */
Copyright © 2024, NextGenUpdate.
All Rights Reserved.