Let’s build a Dungeon Crawler with Hollywood-MAL! (part 2, Designing the game)

I’m going to discuss deeply the character’s class data structure, it’s a core game element and must be planned carefully, so let’s go.

LEVEL AND EXPTABLE

Every character, friend or foe, has a level: basically it’s a number that allow the player to have an idea of how strong the character is. To gain levels the player must performs actions in order to gain experience points, when he reach a certain experience value he get a new level and the ability to distribute some bonus points to his main attributes.

In this kind of games, the character’s level determines how many spells and abilities the character is able to learn, moreover spells and abilities may have some requirements based on the level and/or attributes.

In this article we are going to discuss the class attributes and how I will implement them in the game.

Usually the higher is the character’s level and the more experience points are needed to reach the next level.

Writing down all experience points for each level and for each class is extremely boring so I thought to use the following table to describe how a given character’s class grows:

expTable =                                  ; Defines how the class grows
{ firstExpLevel = -1,                     ; Set the level 1 experience needed
increase = -1,                          ; A % value to compute the experience
; needed for the next level
levelCap = -1,                          ; Level cap, max level allowed
levels = { },                           ; This table will be precalculated at
; creation time, will be populated with
; Exp needed for each level.
attributePoints = -1                    ; How many main attributes point can be
; assigned freely at each level up.
},

Let’s expand the concept:

• firstExpLevel
We set here the experience points needed to reach the level 2
• increase
This is a multiplier that determines the formula to calculate all the subsequent levels, the formula is :
nextLevelExp = previousLevelExp * increase

For example, let’s say we set level 2 (firstExpLevel) at 1000 Exp points and we increase it using 0.75 as multiplier, here is how we are going to calculate experience points for each level:

• Level 2 Needed Exp = 1000
• Level 3 Needed Exp = 1000 + 1000 * 0.75 = 1750
• Level 4 Needed Exp = 1750 + 1750 * 0.75 = 3062
• Level 5 Needed Exp = 3062 + 3062 * 0.75 = 5358
• Level 6 Needed Exp = 5358 + 5358 * 0.75 = 9376
• and so on…
• levelCap
It’s the maximum level this class can reach
• levels
It’s a table that will be populated with all needed experience points for each level, as I have showed above.
• attributePoints
Here we have to specify how many bonus points the player gains when he get a new level, this points can be freely added on the main chatracter’s attributes.

PRIMARY ATTRIBUTES

If you are an RPG fan you already know what attributes are: basically an attribute is a value that represent a character trait, the most common are Intelligence, Dexterity, Strenght, and so on… so I will put the most common attributes in our class template like this:

attributes =
{ level = 1,                               ; Current character level
experience = 0,                          ; Current character experience
attributesCap = 100,                     ; Max attribute value
rollPoints = 10,                         ; How many points for the initial roll

primary =
{ strenght = 20,
intelligence = 20,
dexterity = 20,
constitution = 20,
knowledge = 20,
luck = 20 },

secondary =
{ health =
{ maxValue_formula = { },      ; List of attribute multipliers
RegenValue_formula = { },    ; List of attribute multipliers
currentValue = -1,           ; Character's current health
maxValue = -1,               ; Character's max health
regenValue = -1 },           ; Health regen value at each turn

mana =
{ maxValue_formula = { },      ; List of attribute multipliers
regenValue_formula = { },    ; List of attribute multipliers
currentValue = -1,           ; Character's current mana
maxValue = -1,               ; Character's max mana
regenValue = -1 },           ; Mana regen value at each turn

weight =
{ maxValue_formula = { },      ; List of attributes multipliers
currentValue = -1,           ; Character's current weight
maxValue = -1 },             ; Character's max transportable weight

attack =
{ bareHands_formula  = { },    ; Physical attack with bare hands
physicalCrit_formula = { },  ; % of a physical critical hit
magicalCrit_formula = { },   ; % of magical critical hit
physicalHit_formula = { },   ; % to hit with a physical attack
magicalHit_formula = { } },  ; % to hit with a magical attack

defence =
{ physical_formula = { },      ; % Physical damage reduction
magical_formula = { },       ; % Magical damage reduction
physicalDodge_formula = { }, ; % to dodge a physical attack
magicalDodge_formula = { } },; % to dodge a magical attack

learnSpell_formula = { },      ; % to learn an enemy spell
learnAbility_formula = { },    ; % to learn an enemy ability
steal_formula = { },           ; % to steal an enemy item
escape_formula = { },          ; % to leave the battle at each turn
},
}
}

As you can see, it’s a basic and very common definition, but no one stop us from adding more attributes or change them for our very own custom design. For now let’s stick with this basic design:

• Level
It holds the current level of a character, it must be compatible with the experience point specified below
• experience
It hold the current character’s experience points
• attributesCap
We must set the maximun value each attribute can reach
• rollPoints
At creation time we can decide to roll the specified points into the primary attributes, this points can also be assigned by the player when he creates his characters.

And now it’s time for the primary attributes, they are called this way because almost any secondary attribute is calculated starting from these values, they describe several aspects of a characters like his ability with weapons, ability with magic and so on.

Here are what I’ve defined as a starting point, it’s a very common template:

• strength
Measures how physicallly strong the character is, this can determine, for example, transportable weight,  the maximum weight of a weapon that can be wielded, how much additional damage it can deal with a weapon, and so on…
• intelligence
Intelligence determines if the character can cast a spell or not and with what proficiency. This value is often used to determine how many spells a character can “memorize” and have ready to be casted.
• dexterity
This attribute is used to determine how a character is able to avoid incoming attacks and traps, but is also used as a requirement for some complex weapons like a crossbow, bow, or anything more complex than a sword. Dexterity also means agility and it could be used as a requiremenet to pass a certain obstacole in a passage, for example. Dexterity, along with Intelligence and or Knoledge, could be used to determine the ability to disarm traps.
• constitution
Constitution determines the general physical health of the character, it’s mostly used to calculate the hit points and the health regeneration rate (healing rate), or the ability to resist poisons, etc…
• knowledge
This attribute determines how much knowledge the character have and can influence several aspects like the ability to identify and use an item, the ability to understand foreign languages, the ability to recognize certain dangerous situations, etc…
• luck
Finally we have the luck: it should have a small impact on any character action, but can be very important to put the hands on shiny loot or to save him from a deadly trap.

SECONDARY ATTRIBUTES

As I have anticipated, secondary attributes are values calculated using primary attributes, now we have to face the first problem : how can we store a formula to calculate each secondary attribute easily and fast?

My solution is very simple : for each formula we will use a table indexed with the primary attribute we want to use, while the value will be the attribute’s multiplier, all specified values will be calculated and added together.

Here is a practical example : suppose we want to calculate the secondary attribute Health and we want to influence this value using mainly the Constitution attribute and, with a less impact, the Strength attribute:

; This is just an example to make it clear how it works
primary =
{ Constitution = 30,
Strength = 27,
... }

; The formula table
Health_formula =
{ Constitution = 12.7,
Strength = 3.2 }

; Here is how the Health is calculated
Health = 0

; For each attribute specified in the formula table...
For i, v In Pairs(Health_formula)
; i = index, in this case the attribute's name
; v = value, in this case the specified multiplier

; Check if the attribute specified in the formula exists
; in the primary attributes table
If HaveItem(primary, i)
; Exists, calculate the health for this attribute
Health = Health + primary[i]*v
Else
; If not exists we may print a warning or we can look in other
; places.
EndIf
Next

; Done!
; Calculated Health will be :
; 30*12.7 + 27*3.2 = 467

This method is very fast, and using negative multipliers you can add penalties to your formulas. I will use the same method to store and calculate (with some variations) all the formulas we need.

It’s very important to keep in mind the attribute’s cap values while writing secondary attribute formulas to have balanced results between classes.

I think it’s important to avoid hardcoded stuff as much as possible, it may be a little faster when you run your programs but you will lose a lot in configurability and flexibility.

That’s all for this episode! Next time we will go on with the secondary attributes one by one, you can have a look at them in the meanwhile, name and descriptions are quite intuitive.