Clockwork to Helix Migration
If you are here, you probably want to be converting your code from another framework to Helix. Doing so should not be a difficult task. Most of the previous functions are probably within Helix in one form or another! This means all you need to do is match x function found in the old framework to y function in Helix. Some headings will contain a link - this will bring you to the documentation for Helix's equivalent library or class.
This tutorial assumes basic to intermediate knowledge and experience with Garry's Mod Lua.
Before you start! You will notice that Helix uses client for the variable that represents a player. Clockwork uses player for the variable instead, but this will conflict with the player library. So if you see _player
being used in Clockwork, it means the Garry's Mod player library. This is just a preference and does not affect anything besides appear. So keep in mind throughout the tutorial, you may see player being used for Clockwork code and client being used for Helix code. They represent the same thing, just with a different name.
If you are converting Clockwork code to Helix, keep in mind that _player
is not defined so you will need to either define _player
yourself or switch it to player instead and change the variable name to client for player objects.
Basics of Conversion
Folders
Clockwork code and file structure is not too different from Helix. In the schema, the plugins folder and schema folder stay in the same place. There are some minor differences in naming however:
- The
schema/entities
folder should be moved outside out of the schema folder. - The
libraries
folder needs to be renamed tolibs
to load. - The
commands
tab will not load as each command is now defined in a single shared file, does not matter which one.
Deriving from Helix
This is pretty important. If you want to use Helix as the base, you need to set it as the base. So, go to your Clockwork schema's gamemode
folder. Inside should be two files: init.lua
and cl_init.lua
. Open both, and you should see something along the lines of DeriveGamemode("Clockwork")
. Change this to DeriveGamemode("helix")
.
The Schema
Introduction
Inside of the schema
folder of the actual schema, you should see a file named sh_schema.lua
. This is the main schema file in both Clockwork and Helix. Most of your changes may actually be within this file.
Including Files
Both frameworks come with a utility function to include a file without worrying about sending them to the client and stuff. In Clockwork, this function is Clockwork.kernel:IncludePrefixed("sh_myfile.lua")
. Change this to ix.util.Include("sh_myfile.lua")
and save.
The Plugin
Introduction
Plugins serve as a means to add on to a schema or framework without directly modifying either. This allows for easier modifications that can be added/removed with ease. It is recommended that you keep all custom modifications left to plugins rather than editing the framework or the schema if possible.
Structure
All plugins in Clockwork and Helix go into the plugins
folder. However, there are many differences with the CW plugin structure. First of all, there are two things you see when you open a plugin folder: plugin
again and plugin.ini
.
Helix only has one file needed: sh_plugin.lua
which acts like sh_schema.lua
but for plugins.
Conversion
The first step is to move all of the contents from the plugin
folder to the main folder of the plugin folder. The sh_plugin.lua
file needs to be changed to provide basic information about the plugin.You need to define three things in sh_plugin.lua
which can be found within the plugin.ini
file:
PLUGIN.name = "Plugin Name"
PLUGIN.author = "Plugin Author"
PLUGIN.description = "Plugin Description"
If the plugin uses a special variable (e.g. cwPluginName
) for the plugin, change it to PLUGIN
.
- Note that the
PLUGIN
table is removed after the plugin is loaded. So if you want to usePLUGIN
after the plugin has loaded (such as in console commands, in entities, etc.), addlocal PLUGIN = PLUGIN
at the top. - You can see if a global variable is defined for it by looking for
PLUGIN:SetGlobalAlias("cwMyPlugin")
. So, one would changecwMyPlugin
toPLUGIN
.
The Character Object
One main thing that is very notable is how the character is referenced using client:GetCharacter()
which returns a character object. The way the object works is just like an entity you spawn. It has its own properties like the model, color, etc. that makes it unique. You can access all the characters in a table which stores loaded characters with ix.char.loaded.
The character object comes with many predefined methods. You can look at how they are defined by clicking here. The character object makes it very simple to manager character information.
You will notice throughout the framework, the character object is used a lot. The use of the character object makes a large barrier between what belongs to the character and what belongs to the player. For example: flags, models, factions, data, and other things are stored on the character and can be accessed by the character object.
In Clockwork, there is no use of an object. Instead, the character information is intertwined with the player object. For example:
-- in Clockwork
player:SetCharacterData("foo", "bar")
-- in Helix
client:GetCharacter():SetData("foo", "bar")
The use of the character object allows you to access other characters a player might own without needing to have them be the active character, or even access them when the player is not on the server. Overall, the use of the character object may seem like a complex concept, but will simplify a lot of things once you get the hang of the idea.
The Libraries
Animations (ix.anim)
Clockwork features many functions to set up animations for a specific model. Helix too has this functionality. Helix has one function instead that pairs a model to a specific "animation class" (grouping of animation types). So, all one needs to do is find the appropriate animation class to match the model with. Looking at the Clockwork function name should tell you.
-- before
Clockwork.animation:AddCivilProtectionModel("models/mymodel.mdl")
-- after
ix.anim.SetModelClass("models/mymodel.mdl", "metrocop")
Attributes (ix.attributes
)
Attributes allow the player to boost certain abilities over time. Both frameworks require one to register attributes, but they are done differently. In Clockwork, the ATTRIBUTE
table needs to be defined and registered manually. In Helix, the ATTRIBUTE
table is automatically defined and registered for you. All you need to do is have ATTRIBUTE.value = "value"
. The basic parts of the attribute needed is ATTRIBUTE.name
and ATTRIBUTE.description
.
One extra feature for attributes in Helix is ATTRIBUTE:OnSetup(client, value)
which is a function that gets called on spawn to apply any effects. For example, the stamina attribute changes the player's run speed by adding the amount of stamina points the player has.
You can find an example at https://github.com/NebulousCloud/helix/blob/master/plugins/stamina/attributes/sh_stm.lua
Classes (ix.class)
Classes are a part of the factions. They basically are a more specific form of a faction. Factions in Helix and Clockwork work similarly. For instance, all classes are placed in the classes
folder under the schema folder and use CLASS
as the main variable inside the file.
However:
- You do not need to use
local CLASS = Clockwork.class:New("My Class")
. Instead,CLASS
is already defined for you and you set the name usingCLASS.name = "My Class"
CLASS.factions
is not a table, soCLASS.factions = {FACTION_MYFACTION}
becomesCLASS.faction = FACTION_MYFACTION
- You do not need to use
CLASS:Register()
as classes are registered for you after the file is done processing. - Classes are optional for factions rather than being required.
Commands (ix.command)
Commands no longer need to be in separate files. Instead, they are just placed into one large file. However, if you really wanted you can register multiple commands across multiple files or however you want. One thing you may notice is Clockwork uses a COMMAND table while Helix does not always. It is simply a design preference. You can find examples at https://github.com/NebulousCloud/helix/blob/master/gamemode/core/sh_commands.lua
It should be noted that:
COMMAND.tip
is not used.COMMAND.text
is not used.COMMAND.flags
is not used.COMMAND.arguments
does not need to be defined if no arguments are needed but is defined as a table of argument types when neededarguments = {ix.type.character, ix.type.number}
. See ix.command.CommandArgumentsStructure for details.COMMAND.access
for checking whether or not a person is a (super)admin can be replaced withadminOnly = true
orsuperAdminOnly = true
in the command table.
Configurations (ix.config)
In Helix, the method of adding configurations that can be changed by server owners is heavily simplified. See an example here.
Adding a configuration is as follows:
-- before
Clockwork.config:Add("run_speed", 225)
-- after
ix.config.Add("runSpeed", 235, ...)
You'll notice that ellipses (...) were added at the end. This is because there are more arguments since adding configuration information has been placed into one function. Additionally:
Clockwork.config:ShareKey()
is not needed.- The 3rd argument for
Clockwork.config:AddToSystem(name, key, description, min, max)
is also the 3rd argument for ix.config.Add - The 4th argument for ix.config.Add is an optional function that is called when the configuration is changed.
- The 5th argument for ix.config.Add is a table. You can specify the category for the configuration to group it with other configurations. There is also a data table inside which can be used to determine the minimum value and maximum value for numbers. Check out an example here. See also ix.config.
Currency (ix.currency)
Updating your currency code is simple:
-- before
Clockwork.config:SetKey("name_cash", "Tokens")
Clockwork.config:SetKey("name_cash", "Dollars") -- another example
-- after
ix.currency.Set("", "token", "tokens")
ix.currency.Set("$", "dollar", "dollars")
Note that you need to provide a symbol for that currency (€ for Euro, £ for Pound, ¥ for Yen, etc.) or just leave it as an empty string (""
) and then provide the singular form of the name for the currency, then the plural form.
Datastream
Helix uses the net library whereas Clockwork uses datastream (netstream).
If you're unfamiliar with the net library, you can include the netstream library to your schema by downloading netstream to schema/libs/thirdparty/sh_netstream2.lua
and adding ix.util.Include("libs/thirdparty/sh_netstream2.lua")
to your sh_schema.lua
file.
Starting a datastream:
-- before
Clockwork.datastream:Start(receiver, "MessageName", {1, 2, 3});
-- after
netstream.Start(receiver, "MessageName", 1, 2, 3)
Receiving a datastream:
-- before
Clockwork.datastream:Hook("MessageName", function(player, data)
local a = data[1];
local b = data[2];
local c = data[3];
print(a, b, c);
end);
-- after
netstream.Hook("MessageName", function(client, a, b, c)
print(a, b, c)
end)
Factions (ix.faction)
Factions, like classes, are pretty similar too. They share pretty much the same differences as classes in Clockwork and Helix do.
For instance:
- You do not need to use
local FACTION = Clockwork.faction:New("Name Here")
, insteadFACTION
is already defined for you and you set the name usingFACTION.name = "Name Here"
FACTION.whitelist = true
is changed toFACTION.isDefault = false
FACTION.models
does not need a male and female part. Instead, all the models are combined into one big list.function FACTION:GetName(name)
becomesfunction FACTION:GetDefaultName(name)
FACTION.description = "Describe me"
is added to the faction.FACTION_MYFACTION = FACTION:Register()
becomesFACTION_MYFACTION = FACTION.index
Flags (ix.flag)
Flags are functionally equivalent in Helix. To add a new flag:
-- before
Clockwork.flag:Add("x", "Name", "Description")
-- after
ix.flag.Add("x", "Description")
To check or manipulate a character's flag(s):
-- before
Clockwork.player:GiveFlags(player, flags)
Clockwork.player:TakeFlags(player, flags)
Clockwork.player:HasFlags(player, flags)
-- after
client:GetCharacter():GiveFlags(flags)
client:GetCharacter():TakeFlags(flags)
client:GetCharacter():HasFlags(flags)
Inventories (Inventory)
Inventories have also had a change in the way they work that may seem very different than Clockwork. Similar to how characters are their own objects, inventories become their own objects as well. These inventory objects belong to character objects, which belongs to players. So, this creates a chain of objects which is neat. The use of inventories as objects makes it very simple to attach inventories to anything.
To access a player's inventory, you need to use client:GetCharacter():GetInventory()
which returns the main inventory object for the player's character. You can also access all loaded inventories with ix.item.inventories
but that is not important right now.
Items (Item)
As discussed above, inventories contain items. Items are still used in inventories and world entities, use default class data, have callback functions, and can contain unique item data per instance.
Setting up items
Every time needs to be registered, or have information about it (such as the name, model, what it does, etc.) defined. In Clockwork, you have your items defined in schemas/plugins under the items folder.
So let's start with the differences in structure in the item file.
local ITEM = Clockwork.item:New();
is removedITEM.uniqueID
is completely optional- Replace
ITEM.cost
withITEM.price
ITEM:Register()
is removed
Item Sizes
Helix's inventory uses a grid and utilizes width and height instead of weight as a means of inventory capacity. This means you will have to change your item's weight (ITEM.weight
) to something that might be analagous to the item's size using ITEM.width
and ITEM.height
. The item's size must be at least one by one grid cell. It's up to you to balance the sizes of items in your use case - taking into account how many items a character might have at once, the default inventory size set in the config, etc.
Item Functions
Item functions are defined very differently than they are in Clockwork. For example:
-- before
function ITEM:OnUse(player, entity)
print("My name is: " .. player:Name(), entity)
end
-- after
ITEM.functions.Use = {
OnRun = function(item)
print("My name is: " .. item.player, item.entity)
end
}
All item functions are defined in the ITEM.functions
table. This allows the drop-down menus when using the item a lot easier and cleaner to generate dynamically. There is also more control of the icons used for the options, whether or not the function should be displayed, etc.
You can see an example of a water item here: https://github.com/NebulousCloud/helix-hl2rp/blob/master/schema/items/sh_water.lua
Here, we can define what happens when the function is run, what the icon is, and what sound it plays when used. It is basically put into one area rather than being scattered among hooks and stuff.
Giving/Taking Items
So before we can give/take items, we need to understand what the item instance is. Using the analogy earlier about how the inventory system is like a forum, and inside the forum are posts (the items in this case), we can think of instancing an item as making a new post on a forum. So when we talk about an item instance, it is an item that has been created in the past. The reason we use an item instance (which is its own object too, neat!) is to make each item ever created unique. Each item instance can have its own data unique to itself.
Clockwork also uses an item instance system where you have to instance an item. So, to instance an item in Clockwork you would use:
item = Clockwork.item:CreateInstance("item")
And this would create a new instance of an item. Helix's instancing system is slightly different. Instead of having the function return the instance like it does in Clockwork, Helix relies on a callback to pass the instance. The reason for this is the item must be inserted into the database to get a unique number to represent that item. This is not done instantly, otherwise servers would freeze when new items are made. Clockwork uses the time and adds a number to get the numeric ID for an item, which allows the item to be returned which "solves" the issue, but I digress.
The Helix equivalent would be:
ix.item.Instance(0, "item", data, x, y, function(item) end)
Let's break down the differences:
- For Helix's item instance, the 1st argument (
0
) is the inventory that the item belongs to. You can specify 0 so it does not belong to any inventory. - The data argument is optional and is just a table for the item data.
- x and y are the position of the items in inventory. You can find an available x and y with
inventory:FindEmptySlot()
. - The function is an optional argument that passes the item instance. This is where you can directly access the new item.
Keep in mind that Helix will simplify the item system for you when it can. Normally, you would not need to instance an item yourself unless you were doing something advanced.
So you might be wondering, how do I spawn an item in the map, and how do I give a player an item? In Clockwork, you would do the following:
-- spawning an item in the map
Clockwork.entity:CreateItem(player, Clockwork.item:CreateInstance("item"), Vector(1, 2, 3));
-- giving a player an item
player:GiveItem(Clockwork.item:CreateInstance("item"));
The equivalent in Helix would be:
-- spawning an item in the map
ix.item.Spawn("item", Vector(1, 2, 3))
-- giving a player an item
client:GetCharacter():GetInventory():Add("test")
So in these two examples, the whole deal of instancing items is done for you in Helix!
Hooks
You will need to modify the function name and arguments for your schema or plugin hooks.
-- before
function Schema:PlayerPlayPainSound(player, gender, damageInfo, hitGroup)
-- ...
end
-- after
function Schema:GetPlayerPainSound(client)
-- ...
end
You can see the documented hooks for the schema and plugins in the Plugin section.
Conclusion
Overall, most of the conversion from Clockwork to Helix is simply renaming a certain function and/or switching the order of arguments around. Both are frameworks so they function similarly.
You may want to use our HL2 RP schema example for reference which can be found at https://github.com/NebulousCloud/helix-hl2rp