With the knowledge and programming skills you have gained in the previous chapters, you are ready to create your first complete mod. In this chapter, we will create a mod that allows players to place a diner with a rotating sign (see Figure 5-1). This will require that you organize 3D assets in your mod, creating a placement system, and write the necessary code to make the sign of the diner spin. Let’s begin!

Figure 5-1
A 3 D illustration of a building with a banner on top that reads Mama Joe's Diner, which has several glass windows and an entranceway. It is surrounded by several trees, bushes, and buildings.

The diner with its rotating sign makes for an attractive decoration in your town

Technical Requirements

In this chapter, you will be working entirely in the GIANTS Editor and Studio and must meet the requirements mentioned in the “Technical Requirements” section of Chapter 2, “Getting Started with the GIANTS Editor.” While releases are infrequent, make sure you are always using the most recent version of the GIANTS Editor. This will ensure that you are able to take advantage of any new features. You can find all the code and assets used in this chapter in the book’s code repository on the GDN at the following link:

A Q R code presents a pattern of pixelated patterns in a square. A concentric square is on 3 corners.

https://gdn.giants-software.com/lp/scriptingBook.php

Preparing the Mod Folder Structure

Creating a new mod in GIANTS Studio always starts with the creation of a new project. If you don’t know how to properly set up a new project, check the “New Project” section of Chapter 4, “The GIANTS Studio,” again to get familiar with this process. This is essential for all following chapters.

So let’s create a new project called “restaurant.” GIANTS Studio will create a project setup that will look similar to Figure 5-2. As you can see, it is just a simple helloWorld project with a modDesc.xml, helloWorld.lua, and an icon.

Figure 5-2
A screenshot of the project browser window lists the folder structure under the folder restaurant. Includes scripts with helloworld dot l u a, icon helloworld dot d d s, icon helloworld dot p n g, and mod Desc dot x m l.

The restaurant project

In this sample mod, we want to create a diner with a rotating sign. Therefore, we need the source files for the restaurant. Once you have downloaded the sample files from the GDN, you should right-click the project root to open the mod folder in the explorer (Figure 5-3).

Figure 5-3
A screenshot of the project browser window lists the options under restaurant. It includes a pop-up menu with a highlighted Open in Explorer option.

Go to the mod directory

You can now unzip the sample files and copy them into the mod folder. Accept the overwrite of existing files. As an optional step, you could delete all files in the mod folder first.

To get an updated view in the project browser of GIANTS Studio, you need to refresh it. Right-click again on the root node and click Refresh (Figure 5-4).

Figure 5-4
A screenshot of the project browser window lists the options under restaurant. It includes a pop-up menu with a highlighted refresh option.

Refresh the project browser

The setup of this mod is now done. Figure 5-5 shows how the Project Browser should now look like.

Figure 5-5
A screenshot of the project browser window lists the options under restaurant, which includes placeable and script folders with their corresponding files.

The mod structure

Let’s now have a look at the actual visual representation of the diner. In the folder placeable, you should see the diner model contained within an .i3d file (restaurant.i3d). You are encouraged to look and explore the model in the GIANTS Editor. Recall you can open a model in the GIANTS Editor via the Open option of the File application menu or by pressing Ctrl+O. But you can also open it directly from GIANTS Studio. Just right-click the .i3d file and check Open with Default. The GIANTS Editor should start with the restaurant.i3d loaded. Assuming you are in the default viewing mode, the diner should look as it does in Figure 5-6.

Figure 5-6
A 3 D illustration of a building with a banner on top that reads Mama Joe's Diner,which has several glass windows with a glass door.

The diner model is the centerpiece of this mod

The restaurant mod is now already working. So you could start and test it in game. But let’s have a look at the files first. Please note that all following chapters should be set up using the same steps:

  1. 1.

    Create a project.

  2. 2.

    Download the sample files for the GDN.

  3. 3.

    Copy the files into the mod folder.

  4. 4.

    Refresh the Project Browser.

In the XML file and Lua file creation sections of the chapters, you will always be asked to create a file or add the content to files. You can ignore these lines if you use the sample files as a start because they already contain all these lines of code.

Creating Mod Scripts

In this section, we will create and explore the scripts needed for the mod. We will start by looking at the .xml files and then cover the .lua files. We will create and edit these files in GIANTS Studio. If you need a refresher on how to make and edit files, refer back to Chapter 4, “The GIANTS Studio.”

Creating XML Files

Before we create scripts for the in-game systems of your mod, we will again need to set configurations for our mod via a file called modDesc.xml. Make sure that this file is in your mod directory and add the following content:

<?xml version="1.0" encoding="utf-8" standalone="no" ?> <modDesc descVersion="72">       <Author>GIANTS Software</author>       <version>1.0.0.0</version>       <multiplayer supported="true" />       <title>             <en>Sample Mod - Restaurant</en>       </title>       <description>             <en>A sample mod</en>       </description>       <iconFilename>icon_restaurant.png</iconFilename>       <placeableSpecializations>             <specialization name="objectRotate" className="PlaceableObjectRotate"                         filename="scripts/PlaceableObjectRotate.lua" />       </placeableSpecializations>       <placeableTypes>             <type name="restaurant" parent="simplePlaceable"                         filename="$dataS/scripts/placeables/Placeable.lua">                   <specialization name="objectRotate" />             </type>       </placeableTypes>       <storeItems>             <storeItem xmlFilename="placeable/restaurant.xml"/>       </storeItems> </modDesc>

While most of the fields in the file have already been discussed in the “Debugging Scripts” section of Chapter 4, “The GIANTS Studio,” there are several new ones to discuss.

The first new field we define in the preceding code is a placeableSpecializations field which includes several subfields. Placeables use an internal specialization system that behaves like a plug-in system – that is, each specialization is used for a specific placeable feature. For example, the placement specialization adds functionality to support dynamic placement via the construction screen, and the leveling specialization adds support to level the area around the placeable.

We define a specialization field that includes the filename field which defines the script our mod will use to handle the placeable object. The Lua file we reference and will use is named PlaceableObjectRotate.lua, which we will create later in this section.

Next, we define the placeableTypes field. Placeable types are used to define a specific feature set for a placeable item. For example, the restaurant model should use the functionality given by the internal Placeable.lua file and the additional features of the simplePlaceable type and objectRotate specialization.

Lastly, we need to make the restaurant a purchasable item from the in-game store. We do this by defining the storeItems field and referencing an .xml file for our restaurant, which should be titled restaurant.xml. Your restaurant.xml file should be in the placeable subdirectory mentioned in the previous section. That concludes the content for modDesc.xml; we will now explore the contents of restaurant.xml and the meaning of each section:

<?xml version="1.0" encoding="utf-8" standalone="no" ?> <placeable type="restaurant" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://validation.gdn.giants-software.com/fs22/placeable.xsd">       <storeData>             <name>Restaurant</name>             <functions>                   <function>A deco object</function>             </functions>             <image>icon_restaurant.png</image>             <price>55000</price>             <lifetime>1000</lifetime>             <rotation>0</rotation>             <brand>NONE</brand>             <species>placeable</species>             <category>decoration</category>             <brush>                   <type>placeable</type>                   <category>decoration</category>                   <tab>uncategorized</tab>             </brush>             <vertexBufferMemoryUsage>0</vertexBufferMemoryUsage>             <indexBufferMemoryUsage>0</indexBufferMemoryUsage>             <textureMemoryUsage>0</textureMemoryUsage> <instanceVertexBufferMemoryUsage>0</instanceVertexBufferMemoryUsage>             <instanceIndexBufferMemoryUsage>0</instanceIndexBufferMemoryUsage>       </storeData>

The file starts with the XML declaration defining the information about the XML content. The root element is of the placeable type. The type attribute links the restaurant to the placeable type we defined in the placeableType section of the modDesc.xml.

The purpose of an XML schema is to define the allowed building blocks of an XML document. This includes the elements and attributes that can appear in a document, types of data for elements, the number and order of child elements, as well as default and fixed values for elements. The GDN also provides schema for vehicles, which will be covered in Chapter 6, “Rotating Mower Mod.” You can always find the newest available XML schema from the GDN:

A Q R code presents a pattern of pixelated patterns in a square. A concentric square is on 3 corners.

https://validation.gdn.giants-software.com/fs22/overview.html

The schema for placeables can be found at the following link:

https://validation.gdn.giants-software.com/fs22/placeable.xsd

Next, the file defines configurations for the restaurant model and its in-game behavior. Next, we define a storeData field which holds the price, brand, and description of the item which will be displayed in the shop. Like the .xml file for the mod itself, we will also define basic information such as a name and image in the name and image fields. The function field holds a description about the item and its function. The price of the item, as seen in the Construction Mode, is set by the price field of this section. The lifetime field defines the lifetime of the object until it is fully aged; maintenance costs are calculated based on this value. The rotation field defines an initial rotation for our object, which we will set to 0 by default.

The brand field is used if the item is associated with a real-world brand; we will set it to NONE in our case as the restaurant is fictional. The category field determines what type of in-game object the asset is treated as – our restaurant serves no functional purpose, so we categorize it as a decoration. The brush field is important as it defines the tab and category that the structure will be displayed in when in the Construction Mode. The remaining fields are used internally by the GIANTS Engine and will be set by the ModHub team; you do not need to change these values yourself. Next, we will look at more of the contents of the restaurant.xml file:

<base>       <filename>placeable/restaurant.i3d</filename> </base> <placement useRandomYRotation="false" useManualYRotation="true" > <testAreas>       <testArea startNode="testArea1Start" endNode="testArea1End" />       <testArea startNode="testArea2Start" endNode="testArea2End" /> </testAreas> </placement> <clearAreas>       <clearArea startNode="clearArea1Start" widthNode="clearArea1Width"                         heightNode="clearArea1Height"/>       <clearArea startNode="clearArea2Start" widthNode="clearArea2Width"                         heightNode="clearArea2Height"/> </clearAreas> <leveling requireLeveling="true" maxSmoothDistance="10" maxSlope="75"             maxEdgeAngle="30" >       <levelAreas>             <levelArea startNode="levelArea1Start" widthNode="levelArea1Width"                         heightNode="levelArea1Height" groundType="asphalt"/>             <levelArea startNode="levelArea2Start" widthNode="levelArea2Width"                         heightNode="levelArea2Height" groundType="asphalt"/>       </levelAreas> </leveling>

This section of the file is necessary for the placeable specialization to function. Since this is mostly internal, we will cover each field quickly. The filename field holds the name of the .i3d file of our diner relative to the mod folder.

The testAreas field defines test areas that are used to identify possible placement conflicts. We use two points to create a test box, and in game we use an overlapBox check to get objects within the box to check conflicts. That is, if other objects are within the test areas, the object cannot be placed there.

<tipOcclusionUpdateAreas>       <tipOcclusionUpdateArea             startNode="tipOcclusionUpdateArea1Start" endNode="tipOcclusionUpdateArea1End" /> </tipOcclusionUpdateAreas> <ai>       <updateAreas>             <updateArea                   startNode="tipOcclusionUpdateArea1Start" endNode="tipOcclusionUpdateArea1End" />       </updateAreas> </ai> <objectRotate>       <object node="roofLogo" rotAxis="2" rotDurationSeconds="10" /> </objectRotate>

The clearAreas field defines an area under the model where foliage and fields will be cleared once the model has been placed.

The levelAreas field also defines an area under the model where terrain will be leveled to more neatly support the diner.

The tipOcclusionUpdateAreas is used by the engine to determine whether certain environmental interactions like snow falling should still occur in locations around the restaurant or whether they have been blocked.

Similarly, the updateAreas field under the ai field instructs the AI system to sample the update area to get new information about collisions it has to avoid in the future.

Lastly, the objectRotate field defines which part of our model should rotate and information about the behavior of its rotation. Note that this is where we set the axis the object should rotate around as well as the time in seconds it takes for the object to make one revolution. Axis values of 1, 2, and 3 correspond to X, Y, and Z axes, respectively. Let us now look at the remaining content for the restaurant.xml file:

<i3dMappings>       <i3dMapping id="roofLogo" node="0>0|0|0" />       <i3dMapping id="blinker02Decal" node="0>0|0|0|1|0" />       <i3dMapping id="blinker01Decal" node="0>0|0|0|1|1" />       <i3dMapping id="dinnerDecal" node="0>0|0|0|1|2" /> ...       </i3dMappings> </placeable>

This section of the file defines i3d mappings for the model. i3d mappings are automatically created by the Blender or Maya exporter. Using the i3dMapping field id instead of the node path in the upper part of the .xml file avoids errors as you do not have to adjust the node paths manually after changing the hierarchy within the .i3d file. Let us use the following line as an example:

<object node="roofLogo" rotAxis="2" rotDurationSeconds="10" />

In this line, we use roofLogo instead of the i3d path 0>0|0|0. The script will later automatically resolve the roofLogo ID to 0>0|0|0 and resolve this i3d path to a valid entity ID. So, if we would change the hierarchy of the i3d in Maya and reexport the file, we will not have to change anything in the object element as the Maya exporter will automatically recreate the i3dMapping sections with the new i3d paths. If you examine the restaurant.i3d file in the GIANTS Editor, you will see other reference points have already been physically created and our program references them by name.

Creating Lua Files

With our .xml files created, we will need to create a script to handle the placement of your diner model in the game environment; we will name this script PlaceableObjectRotate.lua. If you already have this file from the GDN, you are still encouraged to follow along as we break down each part of the program. Let’s first look at what needs to be defined to declare the specialization:

local modName = g_currentModName -- @category Specializations PlaceableObjectRotate = {} PlaceableObjectRotate.SPEC_TABLE_NAME = "spec_"..modName..".objectRotate" function PlaceableObjectRotate.prerequisitesPresent(specializations)       return true end -- @param table placeableType the placeable type -- @includeCode function PlaceableObjectRotate.registerEventListeners(placeableType)       SpecializationUtil.registerEventListener(placeableType, "onLoad", PlaceableObjectRotate)       SpecializationUtil.registerEventListener(placeableType, "onUpdate", PlaceableObjectRotate) end

In this code, we assign the name of the mod to a variable called modName. Note that this value is determined by the name of your mod directory and is only available while the .lua file is loaded. As such, we want to store a copy in a local variable of the script to be able to access it later.

Next, we set SPEC_TABLE_NAME for our module, which will later serve as an index for functions related to our mod.

Next, we define the prerequisitesPresent function, which, for our purposes, will always return true. We include this function to ensure all specializations our mod depends on have been loaded before we attempt to use them. Errors will be produced if we try to access variables or functions from a specialization that has not yet been loaded. For example, if we want to use any part of the PlaceableLights specialization, we would add SpecializationUtil.hasSpecialization(PlaceableLights, specializations) in this function.

The registerEventListeners function will handle the creation of events. Events are signals that can be triggered or fired and have some associated function be called with relevant arguments from the event. For this mod, or more specifically the internal placeable specialization we declared we would be using in modDesc.xml, we want to create an event called onLoad and onUpdate, which we will define the functions for later in this section.

-- @includeCode function PlaceableObjectRotate.registerXMLPaths(schema, basePath)       schema:setXMLSpecializationType("ObjectRotate")       schema:register(XMLValueType.NODE_INDEX,                   basePath .. ".objectRotate.object(?)#node",                   "Node index or i3d mapping name of the object that should rotate")       schema:register(XMLValueType.FLOAT,                   basePath .. ".objectRotate.object(?)#rotDurationSeconds",                   "Duration in seconds for one rotation", 1)       schema:register(XMLValueType.INT,                   basePath .. ".objectRotate.object(?)#rotAxis",                   "Rotation axis (1-3)", 1)       schema:setXMLSpecializationType() end

Next, we define the registerXMLPaths function which allows .xml files associated with the mod to be read from. This is important as without this function, we would not be able to read information from .xml files we use to configure mod behavior. We now have the base of our specialization created. Let us look at the implementation for the placeable object, which is handled via the event functions we set listeners for earlier in the section:

function PlaceableObjectRotate:onLoad(savegame)       local spec = self[PlaceableObjectRotate.SPEC_TABLE_NAME]       spec.objects = {}       self.xmlFile:iterate("placeable.objectRotate.object", function(_, key)             local node = self.xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings)             if node ~= nil then                   local rotDurationSeconds = self.xmlFile:getValue(key ..                   "#rotDurationSeconds", 1)                   local rotAxis = self.xmlFile:getValue(key .. "#rotAxis", 1)                   if rotAxis < 1 or rotAxis > 3 then                         rotAxis = 1                         Logging.xmlWarning(self.xmlFile,                               "Invalid rotation axis for objectRotate '%s'! Using default axis '%d'!",                               key, rotAxis)                   end                   local object = {}                   object.node = node                   -- convert the duration to angle delta per -- millisecond                   object.anglePerMs = (2*math.pi) / rotDurationSeconds / 1000                   object.rotAxis = rotAxis                   table.insert(spec.objects, object)             else                   Logging.xmlWarning(self.xmlFile,                         "Invalid node given for objectRotate '%s'!", key)             end       end) end

In the preceding code, we first need to define the onLoad() function which dictates the behavior for when the placeable item loads. Normally, this is when the diner is placed in the world. This function defines some additional values for the mod while preparing relevant objects to be used in game. The spec variable we define indexes a table we refer to as a namespace. Each specialization has its own namespace under which its fields and functions are stored. With this defined, the function then uses information from the restaurant.xml file we created previously to ensure the model is correctly configured and to define behaviors for when it is present in the game.

To access .xml files via a script, we use special syntax to access its elements. Let’s assume we have the following XML content:

<root>       <element1 value="1" />       <elements>             <subElement>test</subElement>             </subElement>test2</subElement>       </elements> <root>

If we want to access the value attribute of element1, we can simply read it with the following Lua code:

xmlFile:getString("root.element1#value")

The period (.) operator accesses the sub-elements of the current element, and the pound (#) operator accesses the attribute of the current element. If you want to select the second sub-element, you would write the following Lua code:

xmlFile:getString("root.elements.subElement(1)")

Unlike Lua, access to XML elements is zero based, so 1 is the second element. You may also find the Logging utility class useful. It contains functions such as the following which can print useful errors, warnings, or other information to the log file:

warning(text , ...) xmlWarning(xmlFile, text, ...) error(text, ...) xmlError(xmlFile, text, ...) info(text, ...) xmlInfo(text, ...)

All of these functions internally use the string.format() function, so the text that is passed can contain placeholders that will be filled using the parameters provided in the function’s arguments.

Returning to PlaceableObjectRotate.lua, you can see we set the anglePerMs field of the object by converting from the time it should take the sign to make one full rotation into a value used to change the rotation of our sign each time we update it. The getValue() script function can be used to access elements from the .xml file because we defined the XML elements and their types in the registerXMLPaths() function. The script now can evaluate the given element or attribute and convert its value to the correct type. Let’s look at the following line of code:

local node = self.xmlFile:getValue(key .. "#node", nil,                                     self.components,                                     self.i3dMappings)

The script reads the node value (in our sample, roofLogo) and uses the passed self.i3dMappings to get the i3d path of it. Then, an internal function uses the i3d path and the self.components table to go through the i3d hierarchy to find the correct entity and then returns the ID of this entity. For each xml entry, we create a table with all of the settings for the object and put it into the self.objects table. This allows us to easily support multiple rotating objects in our mods.

-- @param float dt delta time since last update -- @includeCode function PlaceableObjectRotate:onUpdate(dt)     local spec = self[PlaceableObjectRotate.SPEC_TABLE_NAME]     for _, object in ipairs(spec.objects) do         local rx, ry, rz = getRotation(object.node)         local deltaAngle = object.anglePerMs * dt         rx = rx + (object.rotAxis == 1 and deltaAngle or 0)         ry = ry + (object.rotAxis == 2 and deltaAngle or 0)         rz = rz + (object.rotAxis == 3 and deltaAngle or 0)         rx = rx % (2*math.pi)         ry = ry % (2*math.pi)         rz = rz % (2*math.pi)         setRotation(object.node, rx, ry, rz)     end     self:raiseActive() end

For the sign to be updated, we need the onUpdate() function, which is called repeatedly once the restaurant has been placed and will update the rotation of the sign. That is, each time the function is called, the sign will rotate a small amount around the Y axis, meaning the function must be called quickly and consistently. To do this, the onUpdate() function is bound to each frame. That means each time a frame of the game is rendered by the player’s computer, the function is called with how much time has passed since the last frame was rendered as a parameter called dt, which is short for delta time. For a game running at 60 frames per second (FPS), the value of dt would be 1/60 or about 16.667 milliseconds. The last statement in this function, self:raiseActive(), will ensure that the function is called in the next frame.

Testing the Mod

Now that you have organized and written the assets and scripts required for your mod, you are ready to test it. From the GIANTS Studio, you can run the game without debugging from the Debug application menu. After you begin a new game on the map of your choice, you should be able to see the diner in the Construction Mode. Once placed, the sign on top of the diner should begin to spin, catching the attention of potential customers driving by.

Summary

In this chapter, you learned how to create your first playable mod. With this mod, players will now be able to place down a diner in their game and watch as the sign rotates. You should now feel comfortable implementing specializations yourself as well as making placement systems for any type of building model.

In the next chapter, you will learn to make a more complicated mod that involves both 3D models and programming.