INTRODUCTION
I’ve always been a lover of RPGs and JRPGs and one of my dreams has always been to create my own game with its very own RPG Engine.
To be honest, I’ve tried several times to create a game of this kind, but I always ended up overcomplicating things, documenting the code too little, and after a few breaks that life forces you to take, I always parked the project to oblivion.
A few days ago, while I was digging into my hard drive to check all the projects that were started and never finished, I stumbled upon a project from a few years ago, yet another attempt that was then suspended.
I started checking what I did, and I must admit that this time I had done a good job, even if not free from defects.
My main ambition is to create an engine that is as flexible as possible, so that it can be adapted to the most varied needs. A project like this is extremely complicated because an RPG has an infinite amount of data to organize and manage that is pretty scary: just think about all the attributes of the creatures, the weapons, the shields, the equipment, the consumables, the spells, the skills, in short: a lot of data to handle that is interconnected with each other.
What I had done however had some parts that were not flexible because ‘hardcoded’, that parts couldn’t be customizable, so I decided to take up the project again and make the engine (still incomplete) as customizable as possible.
I also decided to document what I’m doing in the hope of helping anyone who wants to try to get their hand dirty with a similar project. I’m not claiming that my implementation is the right way, but perhaps it can be used to give some ideas to whoever want to try to build something like this from scratch, and that’s the main reason for this devlog series!
Despite all my efforts some little things are hardcoded just because it is impossible to abstract anything!
This first post is going to be a bit long, be prepared 🙂
WHAT I’M USING TO CODE
I use Hollywood-MAL as programming language, it’s very similar to Lua but it includes an infinite number of commands that satisfy the most diverse needs. If you are familiar with Lua you will understand the code without problems, at least in these first steps where no multimedia content is involved at all.
Why such an obscure language like Hollywood-MAL?
Hollywood is a well know language on Amiga-land but it’s not just for Amiga and Amiga-like systems, it compiles for almost all the platforms and architectures around, have a look at its home page if you are curious.
Said that, I have been using Hollywood for many years with great satisfaction and since it is really similar to lua which I love, I don’t really see why I shouldn’t use it for a project like this!
I will use object-oriented programming as much as possible, that is: data structures (objects) with methods and other features that (at least from my point of view) simplify reading the source. Readibility and documentation is really important in an huge project like this and writing this articles will also let me document it and clearify points that I could forget.
Furthermore I will try to keep the various topics separated with separated sources (modules) that will then be included in the main project. At one point I’ll publish the source code because I trust in open source and in sharing each other knowledge.
My main editor of choice is TextAdept, one of the best text editor I ever used: it’s very light and highly customizable, infact I’ve customized it for my own needs with syntax highlighting for Hollywod and for some of my own library I often use. My development system is Linux Manjaro. I switched to Manjaro some years ago, and I’m so happy I’ve found the courage to switch to Linux (from Windows).
This first, quite long post, explains what I’ve implemented so far, things can change during the development since this is a work in progress!
DAMAGE TYPES
The first aspect I’m going to analyze are the damage types. What I want to implement is completely abstracted so that everyone can define and use its very own damage types, but why this feature is important?
Well, it’s important for at least these reasons:
- Weapons and Magic can inflict a specific type of damage.
- Creatures and races can be more sensible to certain type of damage, and can resist to some of them.
- Equipment can protect a creature with different efficiency depending on the received damage type.
- …more
For example a sword could inflict physic damage, more specifically cut damage, a spear could also inflict physic damage but more specifically pierce damage, and the same could also be applied to magic.
So I decided to implement damage types as group and sub-groups, for example physic and magic are two groups while cut, pierce, impact are sub-groups of physic damage and fire, water, light and dark are sub-groups of magic damage.
But what I said above are just examples, I want to be totally free to define such groups and sub-groups as I wish so I implemented the creation of damage types with this method:
app.damage_type:add(args)
The args
parameter represent a table that accepts two fields named group
and sub
that are used to define the damage type.
Here is an example:
app.damage_type:add({ group = "physic", sub = "pierce")
app.damage_type:add({ group = "physic", sub = "cut")
app.damage_type:add({ group = "physic", sub = "impact")
The created damage types can then be referenced later using a string with the notation group.sub
or just group
, this last form will includes all the sub-groups when referenced.
This way I avoid creating hardcoded structures and fixed damage types: I’m able to create something never seen before like:
app.damage_type:add({ group = "sound", sub = "resonance" })
app.damage_type:add({ group = "sound", sub = "subwaves" })
app.damage_type:add({ group = "psychic", sub = "mind" })
To complete the damage types argument, all of these definitions are stored into a table like this:
app.damage_types =
{ sound =
{ resonance = true,
subwaves = true },
psychic =
{ mind = true }
}
It’s easy to find all the sub-groups of a given group. The true
value you see to the right of the sub-group means that it’s active and can be used. It may be switched off for testing reasons or other development needs.
There is also a :get()
method that returns different data depending on what you are asking for:
result = app.damage_type:get(args)
args
is a table that accepts the group
and sub
fields, like the :add()
method, but this time sub
is optional and you can get all the sub-groups if you just ask for the main group.
From my internal documentation:
result = app.damage_type:get(args)
Retrieve the specified damage type.
If asked for a group it returns the table, if asked for sub returns if it's enabled or not, if what asked does not exists prints a warning and returns False.
INPUT
args [TBL] A table holding these items:
.group [STR] Group name (Physic, Magic, ...)
.sub [STR][OPT] Sub group (Pierce, Cut, Fire, Dark, ...)
-- or --
args [STR] A string using the format 'group' or 'group.sub'
OUTPUT
result [TBL]|[BLN] See the description above.
The fact that this method returns False
if the damage type doesn’t exists is important because i can use :get()
also to validate other fields (in other functions/methods) that require a damage type as data type.
Let’s conclude this part with my test code:
; DAMAGE TYPES DEFINITION
; =======================
DBG.Console.Out("Creating damage types...", DBG.Hilight, app.dch.main)
app.damage_type:add({ group = "physic", sub = "impact" })
app.damage_type:add({ group = "physic", sub = "cut" })
app.damage_type:add({ group = "physic", sub = "pierce" })
app.damage_type:add({ group = "physic", sub = "health" })
app.damage_type:add({ group = "magic", sub = "fire" })
app.damage_type:add({ group = "magic", sub = "air" })
app.damage_type:add({ group = "magic", sub = "water" })
app.damage_type:add({ group = "magic", sub = "dark" })
app.damage_type:add({ group = "magic", sub = "necro" })
app.damage_type:add({ group = "magic", sub = "blood" })
app.damage_type:add({ group = "magic", sub = "nature" })
app.damage_type:add({ group = "magic", sub = "restore" })
and here is the console output I get:

CREATURE’S ATTRIBUTES
Creature’s attributes are parameters that determine how much is strong or weak a certain creature in specific tasks that are based on such values.
A typical example is the strenght
attribute: generally a higher value make the physic attacks stronger causing more damage.
But as I stated earlier I don’t want to hardcode these attributes for two main reasons:
- Every game is different: you may want to use different or unconventional stats (unconventional attribute names), a name like
psychic_power
for example in not common in RPG games. - We could hardcode a full set of common stats but you may use only a few of them wasting resources.
Another important feature I added are computed stats: stats that has a value calculated at runtime, attributes that use a formula based on other attributes. For example we could have an attribute like this:
smartness = intelligence*0.5+intuition*0.3
where intelligence
and intuition
are two existing attributes.
Let’s see how I managed to implement completely programmable attributes, here is the main data structure:
app.char_attr = ; attribute's structure
{ name = "",
type = 0,
computed = False,
formula = "",
vital = False,
inverse = False }
Let’s see what these fields mean:
name
: [STR] Attribute’s name (intelligence, mana, life, etc…)type
: [CNS] Attribute’s data type (#VTYPE_STR
for strings, but here it’s not used,#VTYPE_PERC
for percentuals,#VTYPE_NUM
for numbers)computed
: [BLN] True if it’s a computed attribute (it has a formula)formula
: [STR] Holds the formula for computed attributesvital
: [BLN] Means that when this attribute reaches 0 the character dies.inverse
: [BLN] Means that we have to manage the values in inverse mode: 0 is the maximum value, maximum value is handled as the minimum
NOTE: [BLN] stands for boolean, [STR] for string, [CNS] for constant.
The last two fields are interesting because they allow to define something unusual like, for example, an attribute fatigue
, it could let the creature die when it reaches its maximum value. The tipical life attribute instead let the creature die when it reaches zero.
Some additional words are needed for the formula field: it holds a string used to compute the real values based on other attributes.
Premise: attributes have 3 fixed sub-attributes (hardcoded, sorry!) that are: current
(current stat value), maximum
(stat cap, its maximum value) and regen
(regeneration value at each turn, think of stamina
that regenerates over time, or mana
for example).
And that’s the reason why within formulas we have to use only the attribute’s name: because to compute the current value the attribute.current
will be used, to compute the maximum value the attribute.maximim
value will be accessed, and so on.
In our previously example:
smartness = intelligence*0.5+intuition*0.3
will use intelligence.current
and intuition.current
to compute the smartness.current
value.
Sub-attributes (current, maximum and regen) can be enabled or disabled at will, they are stored is a table called app.subAttrs
like this:
app.subAttrs =
{ current = True,
maximum = True,
regen = True }
To create an attribute I coded this method:
app.char_attr:add(args)
; Adds a new attribute to the character's template
;
; INPUT
; args
; .name: [STR] Attribute name
; .type: [CNS] Attribute type (#VTYPE_...)
; .computed: [BLN] True if it's a computed attribute
; .formula: [STR] Formula for computed attributes
; .vital: [BLN] True: when the attribute reaches 0 the char dies
; .inverse: [BLN] True: we have to manage the value in inverse mode: 0
; is the max, max is the min.
To check if an attribute exists I coded this other method:
result = app.char_attr:exists(attr)
; Checks the specified creature's attribute and returns True if it exists
; otherwise returns False. 'attr' can also be passed in the 'attr.sub'.
; 'sub' can only have one of the following values: 'current', 'maximum'
; or 'regen'.
; False is also returned if the sub-attribute is disabled.
;
; INPUT
; attr : [STR] The attribute name we want to check
;
; OUTPUT
; result : [BLN] True if the attribute exists otherwise False.
Finally here is my test code:
; CREATURE'S ATTRIBUTES
; =====================
DBG.Console.Out("Creating creature's attributes...", DBG.Hilight, app.dch.main)
app.char_attr:add({ name = "life", type = #VTYPE_NUM, computed = False, formula = "", vital = True, inverse = False })
app.char_attr:add({ name = "constitution", type = #VTYPE_NUM, computed = False, formula = "", vital = True, inverse = False })
app.char_attr:add({ name = "stamina", type = #VTYPE_NUM, computed = False, formula = "", vital = False, inverse = False })
app.char_attr:add({ name = "dexterity", type = #VTYPE_NUM, computed = False, formula = "", vital = False, inverse = False })
app.char_attr:add({ name = "willpower", type = #VTYPE_NUM, computed = False, formula = "", vital = False, inverse = False })
app.char_attr:add({ name = "intelligence", type = #VTYPE_NUM, computed = False, formula = "", vital = False, inverse = False })
app.char_attr:add({ name = "mana", type = #VTYPE_NUM, computed = False, formula = "", vital = False, inverse = False })
app.char_attr:add({ name = "fatigue", type = #VTYPE_NUM, computed = False, formula = "", vital = True, inverse = True })
app.char_attr:add({ name = "strength", type = #VTYPE_NUM, computed = False, formula = "", vital = False, inverse = False })
app.char_attr:add({ name = "magic_power", type = #VTYPE_NUM, computed = True, formula = "life*0.25+willpower*0.25", vital = False, inverse = False })
app.char_attr:add({ name = "physic_power", type = #VTYPE_NUM, computed = True, formula = "life*0.25+strength*0.25", vital = False, inverse = False })
That produce the following output:

STATUSES
If you have played an RPG or a JRPG you have noticed that, especially during battles, a creature can be afflicted by the so called status change like freeze, sleep, haste, slow, etc… This is a must have for every wannabe RPG Engine project!
A status change is something that alters the usual creature’s behaviour, forbidding certain actions or altering his normal actions flow.
The first step I did to allow the implementation of status changes was to define a list of global variables that a status can alter:
; List of variables accessible by the status definition
app.status_vars =
{ turn_position = True,
act_randomly = True,
target_allies = True,
target_foes = True,
can_cast = True,
cannot_cast = True,
can_summon = True,
can_use_item = True,
can_equip = True,
can_defende = True,
can_learn = True,
get_damage = True,
do_damage = True,
change_status = True,
is_locked = True,
can_use_abilities = True,
can_be_targeted = True,
immunity = True,
clear_permanent = True,
clear_status = True }
And here is a description for each of them, but please note that you may encounter some variable’s names that are explained later, like alterValue
which means alteration value.
turn_position
This variable is calculated before the battle turn is started and determines when the creature will take action positioning it into an action queue. A status change can target this variable to modify the creature’s position in the said queue.
Tipical examples are slow and haste statuses that can move the creature position above or below the other creatures positions in the action queue.act_randomly
This is a simple flag that is used to determine if the creature should be controlled by the player/AI or act randomly. Normally it’s set to False. A tipical example is the confusion status.target_allies
This flag is used to determine if the creature can target his allies. Normally it’s set to True.target_foes
This flag is used to determine if the creature can target his foes. Normally it’s set to True.can_cast
Determines if the creature can cast spells.cannot_cast
Same as above but inverted, determines if the creature cannot cast spells.
This flag and the above one are used to determine if the creature can cast spells and, if specified insubTarget
, for which damage type. Since the damage type is involved it can also be used to forbid some physical attack too.
An example could be the classic silence that could be easily implemented using:can_cast
withsubTarget="magic"
andalterValue=False
which means:Not(can_cast[magic])
ORcannot_cast
withsubTarget="magic"
andalterValue=True
.can_summon
Determines if a summon scroll can be read and executed and, if specified, for which damage type (in this case it is intended as magic type) usingsubTarget
.can_use_item
Determines if the creature can use items.can_equip
Determines if the creature can change equipment.can_defende
Determines if the creature can selectdefend
as battle action.can_learn
Determines if the creature can learn new magics or new abilities.get_damage
Determines if there is a variation of damage received and, if specified withsubTarget
, for which damage type. This affect also curative effects, which are just negative damages.do_damage
Determines if there is a variation of inflicted damage and, if specified withsubTarget
, for which damage type. Same as above: it affect also curative effects, which are just negative damages.change_status
Determines if the creature can be affected by status changes.is_locked
Determines if the creature is completely locked resulting in a complete turn skip.can_use_abilities
Determines if the creature can use learned abilities and, if specified withsubTarget
, for which damage type.can_be_targeted
Determines if the creature can be targeted by other creatures. For example invisibility could be inplemented using this status.immunity
Determines if the creature is immune to all or one damage type (usingsubTarget
).clear_permanent
Used to clear one or more permanent status,alterValue
indicates how many permanent status to remove (randomly). 0 clears all permanent statuses.clear_status
Used to clear one or more non-permanent status,alterValue
indicates how many non-permanent status to remove (randomly). o clears all non-permanent statuses.
They is a lot of stuff but i think that should cover almost any case.
Now that we had this long introduction let’s see how a status change is defined using the following data structure:
app.char_status =
{ name = "",
list = { }
}
Where list
holds one or more entry like this:
{ target = "", ; internal target's name
subTarget = "", ; sub target, where needed
alterMode = 0, ; alteration mode (#ALTMODE_ABS, _ADD, _PERC, _BOOL)
alterValue = 0, ; alteration value
permament = False, ; permanent change?
duration = 0 } ; place holder for when the status will be applied
That’s it: a status change can inflict one or more changes and each change is determined by the list entry. The char_status.name
field is used to store an string identifier. Let’s see the list entry fields one by one:
target
It’s the internal target’s name, the internal variable’s name listed earlier.subTarget
Some targets need this field to determine the affected damage type. If not specified it means ‘all damage types’.alterMode
Alteration mode determines how thealterValue
is interpreted and can be set with one of the following constants:#ALTMODE_ABS
,#ALTMODE_ADD
,#ALTMODE_PERC
,#ALTMODE_NONE
alterValue
This field is used to set the amount of variation or as boolean values to be used as switches.permanent
If this field is set toTrue
the status will never expireduration
This field is just a placeholder, when we will use status changes to define the alterations (please don’t cry, I know it’s complex!) it will be filled automatically with the alteration’s duration.
Now let’s see a couple of examples before closing this topic. Let’s see 3 real examples:
FREEZE STATUS
app.char_status:add(
{ name = "freeze",
list = {
{ target = "is_locked",
alterMode = #ALTMODE_BOOL,
alterValue = True } } })
It seems pretty simple isn’t it? I’m creating a freeze
status that sets the global variable is_locked
to True
.
MADNESS STATUS
app.char_status:add(
{ name = "madness",
list = {
{ target = "act_randomly",
alterMode = #ALTMODE_BOOL,
alterValue = True } } })
This is pretty similar to the previous one: I’m creating a madness
status by setting the global variable act_randomly
to True
.
CURSE OF WEAKNING
app.char_status:add(
{ name = "curse_of_weakening",
list = {
{ target = "get_damage",
alterMode = #ALTMODE_PERC,
alterValue = 1.50 },
{ target = "do_damage",
alterMode = #ALTMODE_PERC,
alterValue = 0.50 } } })
This last example is a bit more complex, it uses two entries to define a weakening status called curse of weakening
. It apply two effects, the first one increase each damage taken (get_damage
) by 50%, while the second one decrease any damage done (do_damage
) by 50%. With damage I also intend curative variations which are just negative damages.
It is also possible to apply the damage variation to only a damage type like magic
or physic.pierce
(using of course the previously examples since damage types are fully customizable).
Finally let’s have a look at my test code:
; CREATURE'S STATUSES
; ===================
DBG.Console.Out("Creating statuses...", DBG.Hilight, app.dch.main)
app.char_status:add({ name = "freeze", list =
{ { target = "is_locked", alterMode = #ALTMODE_BOOL, alterValue = True } } })
app.char_status:add({ name = "madness", list =
{ { target = "act_randomly", alterMode = #ALTMODE_BOOL, alterValue = True } } })
app.char_status:add({ name = "curse_of_weakening", list =
{ { target = "get_damage", alterMode = #ALTMODE_PERC, alterValue = 1.50 },
{ target = "do_damage", alterMode = #ALTMODE_PERC, alterValue = 0.50 } } })
app.char_status:add({ name = "bless_of_golems", list =
{ { target = "get_damage", subTarget = "physic", alterMode = #ALTMODE_PERC, alterValue = 0.50 },
{ target = "get_damage", subTarget = "magic", alterMode = #ALTMODE_PERC, alterValue = 1.60 },
{ target = "immunity", subTarget = "magic.restore", alterMode = #ALTMODE_BOOL, alterValue = False } } })
app.char_status:add({ name = "shadow_form", list =
{ { target = "can_cast", subTarget = "physic", alterMode = #ALTMODE_BOOL, alterValue = False },
{ target = "can_be_targeted", subTarget = "", alterMode = #ALTMODE_BOOL, alterValue = False },
{ target = "do_damage", subTarget = "magic", alterMode = #ALTMODE_PERC, alterValue = 1.90 } } })
app.char_status:add({ name = "silence", list =
{ { target = "can_cast", subTarget = "magic", alterMode = #ALTMODE_BOOL, alterValue = False } } })
app.char_status:add({ name = "haste", list =
{ { target = "turn_position", alterMode = #ALTMODE_PERC, alterValue = 0.25 } } })
app.char_status:add({ name = "slow", list =
{ { target = "turn_position", alterMode = #ALTMODE_PERC, alterValue = 1.75 } } })
app.char_status:add({ name = "stoned_hands", list =
{ { target = "can_cast", subTarget = "physic", alterMode = #ALTMODE_BOOL, alterValue = False } } })
app.char_status:add({ name = "forbid_fire_magic", list =
{ { target = "cannot_cast", subTarget = "magic.fire", alterMode = #ALTMODE_BOOL, alterValue = True } } })
app.char_status:add({ name = "bless_of_the_fire_bird", list =
{ { target = "do_damage", subTarget = "magic.fire", alterMode = #ALTMODE_PERC, alterValue = 2.50 } } })
app.char_status:add({ name = "curse_of_mist", list =
{ { target = "do_damage", subTarget = "", alterMode = #ALTMODE_PERC, alterValue = 0.70, permanent = True },
{ target = "get_damage", subTarget = "", alterMode = #ALTMODE_PERC, alterValue = 1.30, permanent = True } } })
app.char_status:add({ name = "magic_weakness", list =
{ { target = "do_damage", subTarget = "magic", alterMode = #ALTMODE_PERC, alterValue = 0.50 } } })
And here is the console output I get:

In next post I’ll discuss how I implemented Alterations and how I’m implementing items as a root data structure and the its first derivate: weapons.
I hope I haven’t bored you with this very long lalk!