T4modules-4emodule 1

From Tales of Maj'Eyal
Revision as of 03:35, 4 January 2021 by QuixoticScamp (Talk | contribs) (Added explainer for DND 4E)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Author: Canderel

The goal of this series is to introduce the reader to how to easily use T-Engine to create any module of their choosing, often using mostly data driven definitions. The module created will be based off Dungeons and Dragons 4th Edition mechanics or DND 4E for short.

First step

To start, make a copy of the example folder in your /game/modules/ directory and rename it to 4Emodule. Congratulations! You now have a playable module, and you will see it as an option when you start T-Engine and start a new game. It is at the moment identical to the example module, but that will change very quickly.

Update init file

First off, open your init.lua file in your favorite text editor.

You will see a pretty plain default file, so lets change it to reflect the new module.

init.lua

 name = "DND 4E Module"
 long_name = "Tutorial Module creating a DND 4E game system"
 short_name = "4emodule"
 author = { "YOUR NAME", "EMAIL@PROVIDER.COM" }
 homepage = "YOUR WEBSITE"
 version = {1,0,0}
 engine = {1,0,0}
 description = [[t4modules 
 t-engine 4 module simulating the 4e dnd game system.
 ]]
 starter = "mod.load"

And that's it.

If you start T-Engine again you will see it appear properly in the module selection list.

Update load file

The example load.lua is reasonably sparse, so lets add some 4E definitions to it.

First, some stats.

To the top of the file, add this line:

 local ActorStats = require "engine.interface.ActorStats"

And where the default stats are defined, replace with the following:

 ActorStats:defineStat("Strength", "str", 10, 1, 100, "Melee basic attacks and Fortitude saves are based on Strength.")
 ActorStats:defineStat("Constitution", "con", 10, 1, 100, "The amount of healing surges you get per day, Fortitude saves and the amount of hit points added 1st level are determined by Constitution.")
 ActorStats:defineStat("Dexterity", "dex", 10, 1, 100, "Ranged basic attacks are based on Dexterity.  Dexterity also affects Reflex saves and AC bonus for light armor users.")
 ActorStats:defineStat("Intelligence", "int", 10, 1, 100, "If you wear light armor, intelligence may contribute to your AC.  Intelligence also affects Reflex saves.")
 ActorStats:defineStat("Wisdom", "wis", 10, 1, 100, "Wisdom contributes to will defense.")
 ActorStats:defineStat("Charisma", "cha", 10, 1, 100, "Charisma contributes to will defense.")

In ActorStats:defineStat, the first parameter is the name of the stat, also used for displaying it. The second is a shorthand way of referring to it, but the player won't ever see this. The Third value is the default value for this stat, the fourth is the minimum, the fifth the maximum and the sixth a description of the stat.

For each stat, you can use the shorthand form getStr() to get the value (replacing Str with whatever short name you use for the stat).

We won't worry too much about the minimum and maximum stats in this module since the game system prevents it from running off too much.

Next up is inventory and equipment slots. As before, you need to add an import at the top of the file.

 local ActorInventory = require "engine.interface.ActorInventory"

Now for the actual inventory definitions:

 -- Body parts
 ActorInventory:defineInventory("MAINHAND", "In main hand", true, "Most weapons are wielded in the main hand.")
 ActorInventory:defineInventory("OFFHAND", "In off hand", true, "You can use weapons two-handed or a second weapon in your off hand.")
 ActorInventory:defineInventory("ARMOR", "Main armor", true, "Armor protects your from physical attacks.")
 ActorInventory:defineInventory("RING", "On fingers", true, "Rings are worn on fingers.")
 ActorInventory:defineInventory("NECK", "Around neck", true, "Amulets and cloaks are worn around the neck.")
 ActorInventory:defineInventory("ARMS", "On arms", true, "Bracers or shields are worn on the arms slot.")
 ActorInventory:defineInventory("FEET", "On feet", true, "Sandals or boots can be worn on your feet.")
 ActorInventory:defineInventory("HANDS", "On hands", true, "Various gloves can be worn on your hands.")
 ActorInventory:defineInventory("HEAD", "On head", true, "You can wear helmets or crowns on your head.")
 ActorInventory:defineInventory("WAIST", "Around waist", true, "You can wear belts around your waist.")
 ActorInventory:defineInventory("IMPLEMENT", "On hands", true, "Divine or magic classes often use an implement to assist their attacks.")

The slot INVEN is defined by default and does not need to be explicitly mentioned.

Note that the above only signifies the existence of these slots. The slots and the amount of each slot is defined per Actor.

We will also set a maximum level the player can reach with

 local ActorLevel = require "engine.interface.ActorLevel"

and

 ActorLevel:defineMaxLevel(10)

The maximum level is 10 since we will only be dealing with heroic tiers, but this can be set to 30 if you wish to also implement the paragon and epic tiers.

Note that you can also define resources, such as mana or stamina that regenerates over time, but 4E does not use any additional ones.

Second step

Player descriptors

When starting on birth descriptors you need a reasonably good idea of how your character creation process will look. Since we will be simulating 4E, the process is already defined.

Find your /class/Game.lua file and find the _M:newGame() function. Modify the following line:

 local birth = Birther.new(self.player, {"base", "role" }, function()

to read

 local birth = Birther.new(self.player, {"base", "race", "class", "classoptions" }, function()

This sets the order that the character creation options appear. Note that the module will hang if you try and run it now, since it can't find these named descriptors.

Note that skills, feats and powers will be selected using in-game dialogs, so don't worry too much about those yet. The Players Handbook defines 8 races, even more in later handbooks, but this tutorial will only focus on the Dwarf and Eladrin. We will also only be focusing on the Fighter and Ranger classes.

Find the file /data/birth/descriptors.lua and edit it with the following base descriptor:

 newBirthDescriptor{
       type = "base",
       name = "base",
       desc = {
       },
       experience = 1.0,
       talents = { },
       body = { INVEN = 1000, MAINHAND = 1, OFFHAND = 1, ARMOR = 1, RING = 2, NECK = 1, ARMS = 1, FEET = 1, HANDS = 1, HEAD = 1, WAIST = 1 },
 
       copy = {
 	        max_level = 10,
 	        lite = 4,
 	        money = 100,
       },
 }

Third step

Race and class features

Part 2's lua file got pretty long, making it hard to find things and to maintain. In this part, we will be defining talents by using multiple files.

Track down your /data/talents.lua file and clear it out. Replace it with this:

 load("/data/talents/features/features.lua")

Now create the directory and file /data/talents/features/features.lua with the following contents:

 newTalentType{ type="base/class", name = "class", hide = true, description = "The basic talents defining a class." }
 newTalentType{ type="base/race", name = "race", hide = true, description = "The various racial bonuses a character can have." }
 
 -- common
 load("/data/talents/features/proficiency.lua")
 
 -- racial
 load("/data/talents/features/dwarf.lua")
 load("/data/talents/features/eladrin.lua")
 
 -- class
 load("/data/talents/features/fighter.lua")
 load("/data/talents/features/ranger.lua")

This structure allows us to keep all race and class feature talents together in the features directory, and each individual race or class gets assigned its own file for its feature definitions.

Beware however, you need to actually create these files, even if they are empty, or your module will be unable to start.

/data/talents/features/fighter.lua:

 newTalent{
         name = "Fighter One-Handed Weapon Talent",
         short_name = "Fighter One Handed",
         type = { "base/class", 1 },
         info = "Attack bonus when using one-handed weapons",
         mode = "passive",
         hide = true,
 }
 
 newTalent{
         name = "Fighter Two-Handed Weapon Talent",
         short_name = "Fighter Two Handed",
         type = { "base/class", 1 },
         info = "Attack bonus when using two-handed weapons",
         mode = "passive",
         hide = true,
 }
 
 newTalent{
         name = "Combat Superiority",
         type = { "base/class", 1 },
         info = "Gain a bonus to opportunity attacks equal to wisdom modifier.",
         mode = "passive",
         hide = true,
 }

Not too impressive, since they are all passive abilities. Alone, they do not do anything, but the code can test for then (such as before swinging a one-handed weapon) and decide what their effects are.

T-Engine will interpret these files and create talent variables in the ActorTalents object for you. To refer to them again, such as when assigning during birth, you should take the short_name (or name if no short_name is defined), uppercase it, replace its spaces with _'s and append a T_ to the front. In this way the talent "Fighter One Handed" becomes ActorTalents.T_FIGHTER_ONE_HANDED.