When you have more than just potions which causes effects like that, it can be useful to put another layer of abstraction between the potions and the game mechanics in form of a temporary status modifier system.
This system keeps track of what status modifiers are currently active, how long they are still active, and what aspect of the character they affect. Other game systems should be able to query this system as a black box to get its current sum for any character stat. For example, when the character has drunk a +10 attack potion, has a -20 attack curse and a +15 attack weapon equipped, getTemporaryStatusModifier(ATTACK)
returns 5
.
You would have to call that function in any code which operates on data which can be subject to temporary status effects. Yes, that means you might have to add not just obvious keys like ATTACK
, HEALTH_REGENERATION
or CRIT_DAMAGE
but also obscure stuff like VAMPIRE_POISON_FEEDBACK
or NIGHT_POSTPROCESSING_GAMMA_ENHANCEMENT
.
Instant effects, like the Instantly restores a certain number of health points
potion, can be implemented as status effects with a duration of a single game-tick.
A different approach is to implement status effects with functions. This system makes new status-effect creating game mechanics more complicated, but leads to cleaner code at those places which use this system.
Temporary status effects don't have a simply numerical effect. They are instead objects with one or more callback-functions which get called at specific places in the code. This is again abstracted behind a TemporaryStatusModifier
system.
For example, when you recalculate the stats of the player-character, your core game-mechanics call the TemporaryStatusModifier
system to run the PlayerStats-object through the calculateStats
callback-functions of all currently active temporary status effects which have such a function. For a simple +attack effect, this function would look like that:
void calculateStats(PlayerStat& stats) {
stats.attack += 10;
}
These functions can be as complex as you want. So you can hide the code for status effects with complex and situational mechanics away at where they are defined and keep your core game-mechanics code relatively clean. For example, your "poison vampires" effect could implement the callback function which calculates damage on the player-character like this:
void calculateDamageOnPlayer(Player& player, Enemy& enemy, Attack& attack, int& damage) {
if (enemy.monsterType == VAMPIRE && attack.hasTag(DRAW_BLOOD)) {
StatusModifier poison;
// ...build the status effect object...
enemy.addStatusModifier(poison);
}
}
All the effects mentioned in the question could be implemented by having just four places in the core-code which make a call to the status modifier system:
- At the beginning of each tick when recalculating all player stats
- When the player receives damage
- When the player does damage
- Before rendering when the engine determines the current parameters for the post-processing stack.