[Tutorial] Creating a Basic Extension
To start you should plan out what you want your extension to do. Its also helpful to unzip several pre-made extensions and rule sets to see how things are done in them. To do this copy the rule set or extension out of the rule set or extenstion folder into a location outside the Fantasy Grounds folder. Then rename the file from *.pak or *.ext to *.zip. Then open it as you would a normal zip file. Extract those files to a location you can easily get to. Then open those files in your favorite reader (I use Notepad++).
An extension is based on the same code as a rule set, but it supersedes the code in a rule set. This is helpful because you can override entire files in a ruleset or simply override a few widgets or pieces of code.
What you need to do next is create a folder in your Fantasy Grounds extensions folder:
<drive letter>:\Users\<username>\AppData\Roaming\Fantasy Grounds\extensions\<extension folder name>
After you create the folder, next you need to create an xml file called 'extension' in that folder. More information can be found here. Then we fill in the xml file with the following:
Code:
<?xml version="1.0" encoding="iso-8859-1"?>
<root version="3.3" release="1.0">
<announcement text="Text that is displayed on start of ruleset. Usually the extension name and copyright info." font="emotefont" />
<properties>
<name>Name of Extension</name>
<version>0.1(version of extension)</version>
<author>Author of Extension</author>
<description>A description of the extension.</description>
</properties>
<base>
<!-- This file is our main Lua file. You can name it what you want, but you must use xml restrictions in order to do that.-->
<script name="ScriptPackageName" file="scripts/scriptFileName.lua" />
</base>
</root>
Once you have this done, you can begin coding the extension in the script file that you created. To access your script files functions and global variables you'll use the 'name=' attribute of the <script> tag, in this case it is "ScriptPackageName". This is useful if you have to put scripts in other locations such as within xml tags within other file locations that you are overriding (such as the example xml file of "campaign/campaign_images.xml"). Sometimes you will need to put scripts within xml tags in order to have certain functions and callbacks work properly.
A good practice is to keep all of your global variables in your main Lua file (in this case "ScriptPackageName") and access them from the custom script locations, because it is difficult to derive a package name to where those other scripts are located (and likely have to be done at run time due to 'instances' of those packages and classes).
Our Lua file should look like this:
Code:
--
-- © Copyright James (Lokiare) Holloway 2014+ except where explicitly stated otherwise.
-- Fantasy Grounds is Copyright © 2004-2014 SmiteWorks USA LLC.
-- Copyright to other material within this file may be held by other Individuals and/or Entities.
-- Nothing in or from this LUA file in printed, electronic and/or any other form may be used, copied,
-- transmitted or otherwise manipulated in ANY way without the explicit written consent of
-- James (Lokiare) Holloway or, where applicable, any and all other Copyright holders.
--
You'll want to put your copyright info and the copyright info of SmiteWorks first using the comment tags "--". This will help protect SmiteWorks and yourself from any kind of lawsuit or copyright theft.
Code:
-- Global Variables for this package
count = 0;
Global variables can be accessed in other packages by using the package name and a '.' and then the global variable name (for instance: "ScriptPackageName.count" for the above variable). In this Lua file package they can be accessed just using the variable name "count". Variables in Lua aren't typed, so you don't have to know what's in a variable to use it or assign it.
To keep this simple I'm going to make an extension for 4E that allows you to send some text to the chat window on the start of someones turn. This text will include the name of the character who's turn it is and the number of rounds that have gone by. In order to do this we have to find out who's turn it is on the combat tracker.
Code:
function getTokenInfo(nodeEntry)
-- This is the structure of Fantasy Grounds chat messages. Sender is who it shows as sending the message. Font is the font used to display the message. Icon is the special fantasy grounds icon displayed next to the sender. You can assign special icons, but that is beyond the scope of this tutorial.
local msg = {sender = "Zones", font = "emotefont", icon = "turn_flag"};
msg.text = "";
-- Get the current characters name (check to make sure the nodeEntry is not nil)
if nodeEntry then
-- Here we use the ActorManager to get the actor from the Combat Tracker based on the node entry that is passed into the function.
local rActor = ActorManager.getActorFromCT(nodeEntry);
-- We set the message text to be equal to the actors name.
msg.text = rActor.sName;
end
-- Return the msg that we created so it can be used to display the message to the chat panel.
return msg;
end
Now that we know who's turn it is and we return that information in a msg object (all variables are really tables in Lua). We need to use a callback in order to have Fantasy Grounds trigger the action on the start of a turn. For more functions and callbacks refer to the rule set reference guide. Some specific functions and callbacks may not be detailed there, but you can find them specifically in the rule set .pak file that they belong to. The rule set reference guide mainly covers the core Rule set that all other rule sets are based on.
Code:
-- This function is one that the Combat Tracker calls if present at the start of a creatures turn.
function onTurnStartEvent(nodeEntry)
-- Here again we create a msg and call the getTokenInfo function that we created earlier to get a message with the creatures name whose turn it is.
local msg = getTokenInfo(nodeEntry);
-- We concatenate several strings onto it using '..' the Lua concatenation symbols for strings. (its useful because it tries to convert whatever you are putting together into a string.
msg.text = "Its " .. msg.text .. "s turn.";
--Here we call the Comm package to send the message to the chat. We use deliverChatMessage in order to have it echoed to the clients that are connected.
-- Iterate the count
count = count + 1;
-- Add the count to the message
msg.text = msg.text .. " The turn count is " .. count .. ".\r";
Comm.deliverChatMessage(msg);
end
After this you need to register your callback in the combat tracker package.
Code:
-- This function is to initialize variables
function onInit()
-- Here is where we register the onTurnStartEvent. We can register many of these which is useful. It adds them to a list and iterates through them in the order they were added.
CombatManager.setCustomTurnStart(onTurnStartEvent);
end
There you have it. If you load up Fantasy Grounds, you should see your extension listed. I suggest creating a new campaign to test your extension in, so you don't mess up an existing campaign with bugs. Throw a few creatures into the combat tracker and change turns and see what the chat log says.
Any questions or comments are welcome.