In this chapter, we will be creating a mod that allows players to spawn bales of shapes and types specified by the player (see Figure 9-1). You will work to tie GUI elements into your Lua programs to create new objects the player can interact with on their farm. Let’s jump into it!

Figure 9-1
A screenshot of the Multibale Spawner window. The fill type is Hay, the bale type is round bale, the size is 180 by 120 centimeters, and the number of bales is 3. The background of a fence with trees behind is hazy.

Spawn bales through a new GUI menu

Technical Requirements

Like the previous 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.” 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 small concentric square is on 3 corners.

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

Creating Mod Scripts

This section will explore all of the scripts necessary for this mod. We will start by looking at the .xml files and then cover the .lua files. Please see the “Preparing the Mod Folder Structure” section of Chapter 5, “Making a Diner with a Rotating Sign,” on how to set up a mod project and use the sample files provided on GDN.

Creating XML Files

The first step of making our mod is creating the modDesc.xml file. Let us now cover its contents:

<?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 - Multi Bale Spawner</en>       </title>       <description>             <en>A sample mod</en>       </description>       <iconFilename>icon_multiBaleSpawner.png</iconFilename>       <extraSourceFiles>             <sourceFile filename="scripts/AdditionalGuiProfiles.lua"/>             <sourceFile filename="scripts/MultiBaleSpawnerUtil.lua"/>             <sourceFile filename="scripts/MultiBaleSpawnerScreen.lua"/>             <sourceFile filename="scripts/events/MultiBaleSpawnerEvent.lua"/>             <sourceFile filename="scripts/PlayerExtension.lua"/>       </extraSourceFiles>       <actions>             <action name="OPEN_MULTI_BALE_SPAWNER" axisType="HALF" />       </actions>       <inputBinding>             <actionBinding action="OPEN_MULTI_BALE_SPAWNER">                   <binding device="KB_MOUSE_DEFAULT" input="KEY_b" />             </actionBinding>       </inputBinding>       <l10n filenamePrefix="l10n/l10n" /> </modDesc>

After defining the basic information about our mod, we include five source files in the extraSourceFiles field. These scripts will be created in the following “Creating Lua Files” section.

We also define a new action in the actions field called OPEN_MULTI_BALE_SPAWNER and mark it as a HALF axis. This action will be used by the player to open the GUI menu they will use to make selections and spawn bales. Then, via the inputBinding field, we bind the event to the B key on the keyboard.

Lastly, we create a reference for translation files in the l10n field.

The next file we need to create is guiProfiles.xml. The purpose of the file is to define the appearance of the GUI menu. Let us now look at its contents:

<?xml version="1.0" encoding="utf-8" standalone="no" ?> <GUIProfiles>       <Profile name="baleSpawnerBox" extends="baseReference"                   with="anchorTopCenter">             <Value name="imageColor" value="0 0 0 0" />             <Value name="size" value="1258px 698px"/>             <Value name="position" value="0px 0px"/>             <Value name="fitFlowToElements" value="true"/>             <Value name="flowDirection" value="vertical" />       </Profile>       <Profile name="baleIcon" extends="baseIcon">             <Value name="imageUVs" value="384px 96px 48px 48px" />       </Profile>       <Profile name="multiTextOptionBaleSpawner" extends="multiTextOption">             <Value name="margin" value="0 12px 0 12px"/>             <Value name="size" value="1258px 48px"/>       </Profile>       <Profile name="multiTextOptionLeftBaleSpawner" extends="multiTextOptionLeft"                   with="anchorMiddleLeft">             <Value name="position" value="300px 0px" />       </Profile>       <Profile name="multiTextOptionRightBaleSpawner"                   extends="multiTextOptionRight" with="anchorMiddleLeft">             <Value name="position" value="700px 0" />       </Profile>       <Profile name="multiTextOptionTextBaleSpawner"                   extends="multiTextOptionText" with="anchorMiddleLeft">             <Value name="position" value="350px 0px" />             <Value name="size" value="350px 48px"/>             <Value name="textSize" value="20px"/>             <Value name="textMaxWidth" value="340px" />             <Value name="textAlignment" value="center"/>       </Profile>       <Profile name="multiTextOptionBgBaleSpawner" extends="multiTextOptionBg"                   with="anchorMiddleLeft">             <Value name="size" value="352px 48px" />             <Value name="position" value="348px 0" />       </Profile>       <Profile name="multiTextOptionTitleBaleSpawner" extends="textDefault"                   with="anchorMiddleLeft">             <Value name="textSize" value="20px" />             <Value name="textMaxWidth" value="400px" />             <Value name="size" value="0 48px" />             <Value name="textMaxNumLines" value="2" />             <Value name="textVerticalAlignment" value="middle" />             <Value name="textAutoWidth" value="true" />       </Profile> </GUIProfiles>

The file consists of a GUIProfiles element. The profile elements are used to define some basic settings of the GUI element. Functionally, they are similar to CSS in web development. The GUI’s xml file defines the structure, and the profiles define the look and feel of the UI.

Some elements of this GUI make use of predefined profiles used already in the base game. Some other elements use custom styles – these styles are defined by this file.

GUIProfiles also support inheritance and traits so we can easily reuse existing profiles from the base game and only change a few configurations relating to their style for use with our mod.

You should be made aware that some traits like anchorMiddleLeft or anchorTopCenter are used in the with attribute. They simply define the alignment of the objects within the parent space. Most of the other fields and attributes for the profiles are intuitive.

Let us now cover the contents of multiBaleSpawnerScreen.xml:

<?xml version="1.0" encoding="utf-8" standalone="no" ?> <GUI onOpen="onOpen" onClose="onClose" onCreate="onCreate">       <GuiElement type="dynamicFadedBitmap" profile="uiFullBlurBG"/>       <GuiElement type="bitmap" profile="bgVignette"/>       <GuiElement type="bitmap" profile="bgGlow" />       <GuiElement type="bitmap" profile="uiElementContainerFullScreen">             <GuiElement type="bitmap" profile="uiElementCenter">                   <GuiElement type="bitmap" profile="headerBoxDocked" >                         <GuiElement type="bitmap" profile="baleIcon"/>                         <GuiElement type="text"   profile="headerText"                               text="$l10n_multiBaleSpawner_ui_title"/>                   </GuiElement>                   <GuiElement type="boxLayout" profile="baleSpawnerBox">                   <GuiElement type="multiTextOption"                               profile="multiTextOptionBaleSpawner" onClick="onClickFillType"                               id="fillTypes" focusInit="onOpen">                         <GuiElement type="button" profile="multiTextOptionLeftBaleSpawner" />                         <GuiElement type="button" profile="multiTextOptionRightBaleSpawner"/>                         <GuiElement type="text"   profile="multiTextOptionTextBaleSpawner" />                         <GuiElement type="text"   profile="multiTextOptionTitleBaleSpawner"                               text="$l10n_multiBaleSpawner_ui_fillType"/>                         <GuiElement type="bitmap" profile="multiTextOptionBgBaleSpawner" />                   </GuiElement>                   <GuiElement type="multiTextOption" profile="multiTextOptionBaleSpawner"                               onClick="onClickBaleType" id="baleTypes">                         <GuiElement type="button" profile="multiTextOptionLeftBaleSpawner" />                         <GuiElement type="button" profile="multiTextOptionRightBaleSpawner"/>                         <GuiElement type="text"   profile="multiTextOptionTextBaleSpawner" />                         <GuiElement type="text"   profile="multiTextOptionTitleBaleSpawner"                               text="$l10n_multiBaleSpawner_ui_baleType"/>                         <GuiElement type="bitmap" profile="multiTextOptionBgBaleSpawner" />                   </GuiElement>                   <GuiElement type="multiTextOption" profile="multiTextOptionBaleSpawner"                         id="baleSizes">                         <GuiElement type="button" profile="multiTextOptionLeftBaleSpawner" />                         <GuiElement type="button" profile="multiTextOptionRightBaleSpawner"/>                         <GuiElement type="text"   profile="multiTextOptionTextBaleSpawner" />                         <GuiElement type="text"   profile="multiTextOptionTitleBaleSpawner"                               text="$l10n_multiBaleSpawner_ui_baleSize"/>                         <GuiElement type="bitmap" profile="multiTextOptionBgBaleSpawner" />                   </GuiElement>                   <GuiElement type="multiTextOption" profile="multiTextOptionBaleSpawner"                         id="numBales">                         <GuiElement type="button" profile="multiTextOptionLeftBaleSpawner" />                         <GuiElement type="button" profile="multiTextOptionRightBaleSpawner"/>                         <GuiElement type="text"   profile="multiTextOptionTextBaleSpawner" />                         <GuiElement type="text"   profile="multiTextOptionTitleBaleSpawner"                               text="$l10n_multiBaleSpawner_ui_numBales"/>                         <GuiElement type="bitmap" profile="multiTextOptionBgBaleSpawner" />                   </GuiElement>             </GuiElement>       </GuiElement> </GuiElement> <GuiElement type="flowLayout" profile="buttonBoxDockedOnScreen">       <GuiElement type="button" profile="buttonOK"             text="$l10n_multiBaleSpawner_ui_spawn_button" onClick="onClickOk" />       <GuiElement type="button" profile="buttonBack" text="$l10n_button_back"             onClick="onClickBack" /> </GuiElement> </GUI>

This file is for managing multiple GuiElement fields within our mod. The most common elements include button, slider, text, textInput, bitmap, multiTextOption, checkedOption, smoothList, listItem, boxLayout, flowLayout, and scrollingLayout.

Each of these elements can be customized or stylized using XML syntax or by using profiles. The GUI layout is done in full HD (1920 x 1080px). When the game is loaded, the GUI elements are rescaled and repositioned to adjust for the player’s game resolution.

We use background elements from the base game to get the class Farming Simulator 2022 look. Then, we create a container that centers the GUI and optimizes it for full HD screens with a 16:9 aspect ratio. In the container, we include a header that holds an icon and text label element.

Next, we include a boxLayout element which automatically adjusts the positions of its child elements. Inside of this layout, we include multiTextOption elements. These elements are similar to a dropdown menu in other UI frameworks but include a left or right toggle. We define these multiTextOption elements for the baleType, fillType, and numBales selections. The id attribute is later used in the code to access the element and set or get its associated values.

Lastly, we include a button at the bottom to close the menu and spawn the bales with the current selections.

Now we will define translation files for text we want to display to the user. We will start by defining l10n_en.xml which is the file for English. Let us look at its contents:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <l10n>       <elements>             <e k="input_OPEN_MULTI_BALE_SPAWNER" v="Open MultiBaleSpawner"/>             <e k="multiBaleSpawner_ui_baleType" v="Bale Type"/>             <e k="multiBaleSpawner_ui_numBales" v="Number of Bales"/>             <e k="multiBaleSpawner_ui_title" v="MultiBaleSpawner"/>             <e k="multiBaleSpawner_ui_spawn_button" v="Spawn"/>             <e k="multiBaleSpawner_ui_fillType" v="Filltype"/>             <e k="multiBaleSpawner_ui_baleSize" v="Size"/>       </elements> </l10n>

We see that we define seven text elements in the file. The first is for the custom OPEN_MULTI_BALE_SPAWNER action we defined earlier and will show Open to the player. Next, when the bale types are shown to the user, we will want to display Bale Type at the beginning of the list. For the number of bales to be spawned, we want to display Number of Bales. Next, for the title of the mod, we include MultiBaleSpawner. For the spawn button, we want it to read as Spawn. For the fill type, which determines the type of bale (straw, hay, cotton, etc.), we set the text to Filltype. Lastly, for bale size we display Size to the user.

To provide a better experience to players who speak other languages, we will provide translations. In this case, we will provide a translation into German in l10n_de.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <l10n>       <elements>             <e k="input_OPEN_MULTI_BALE_SPAWNER" v="MultiBaleSpawner öffnen"/>             <e k="multiBaleSpawner_ui_baleType" v="Ballen Typ"/>             <e k="multiBaleSpawner_ui_numBales" v="Anzahl der Ballen"/>             <e k="multiBaleSpawner_ui_title" v="MultiBaleSpawner"/>             <e k="multiBaleSpawner_ui_spawn_button" v="Erzeugen"/>             <e k="multiBaleSpawner_ui_fillType" v="Fruchttyp"/>             <e k="multiBaleSpawner_ui_baleSize" v="Größe"/>       </elements> </l10n>

Like previous chapters, we provide the same keys for each element but a different text value in the language associated with the file.

In the next section, we will create the .lua files needed for the mod.

Creating Lua Files

The first Lua file we will create is MultiBaleSpawnerUtil.lua. This program serves as a helper for spawning bales at a given position. Let us look at the file:

MultiBaleSpawnerUtil = {} MultiBaleSpawnerUtil.MAX_NUM_BALES = 4 MultiBaleSpawnerUtil.SEND_NUM_BITS = 3 function MultiBaleSpawnerUtil.spawnBales(baleTypeIndex, fillTypeIndex, numBales, x, y, z, dirX, dirZ, farmId)       -- bale creation is only allowed on server and afterwards       -- synched to the client       if not g_currentMission:getIsServer() then             Logging.error("This is function is only allowed on server!")             return       end       local baleInfo = g_baleManager.bales[baleTypeIndex]       if baleInfo == nil then             Logging.error("Could not find bale info for baleTypeIndex '%s'", tostring(baleTypeIndex))             return       end       local baleToBaleOffset = 0.2       local yOffset = baleInfo.height * 0.5       local zOffset = baleInfo.length       if baleInfo.isRoundbale then             yOffset = baleInfo.diameter * 0.5             zOffset = baleInfo.width       end       local ry = MathUtil.getYRotationFromDirection(dirX, dirZ)       x = x + dirX * zOffset*0.5       z = z + dirZ * zOffset*0.5       for i=1, numBales do             local baleObject = Bale.new(g_currentMission:getIsServer(),                   g_currentMission:getIsClient())             y = math.max(y,                   getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, y, z) +                   yOffset)             if baleObject:loadFromConfigXML(baleInfo.xmlFilename, x, y, z, 0, ry, 0) then                   baleObject:setFillType(fillTypeIndex, true)                   baleObject:setWrappingState(0)                   baleObject:setOwnerFarmId(farmId, true)                   baleObject:register()                   x = x + dirX * (zOffset + baleToBaleOffset)                   z = z + dirZ * (zOffset + baleToBaleOffset)             else                   baleObject:delete()             end       end end

We first define two fields, MAX_NUM_BALES and SEND_NUM_BITS. The MAX_NUM_BALES field determines the maximum number of bales a player can spawn at a time. The SEND_NUM_BITS is the number of bits required to represent MAX_NUM_BALES in binary.

You may remember from Chapter 6, “Rotating Mower Mod,” that we want to send as little data across the network as possible – thus, using the binary representation of this value saves on bandwidth. The only function we implement in this program is spawnBales().

This function will spawn a specified number of bales at a given position. Notably, bale creation is only allowed on the server and is later synchronized with the client. After ensuring the function is being called by the server, we get the information about the bale based on the passed baleTypeIndex. Critically, there must be information associated with the passed bale index; otherwise, we will produce an error. We define an offset for how high off the ground and how far away from the player the bale should spawn.

Next, we spawn the number of bales specified by creating new Bale objects and configure them based on the player’s selection. The call of the register() method registers the object as a network object on the server. The server will then sync the bale to the clients on the next update. If for some unknown reason an error occurs in configuring the bale, we delete it and move to the next bale.

The next script we need to add is MultiBaleSpawnerEvent.lua. You should be familiar with what functions are required by the Event base class. Try to see if you can recall each of them before you continue. Let us continue:

MultiBaleSpawnerEvent = {} local MultiBaleSpawnerEvent_mt = Class(MultiBaleSpawnerEvent, Event) InitEventClass(MultiBaleSpawnerEvent, "MultiBaleSpawnerEvent") function MultiBaleSpawnerEvent.emptyNew()       local self = Event.new(MultiBaleSpawnerEvent_mt)       return self end function MultiBaleSpawnerEvent.new(baleTypeIndex, fillTypeIndex, numBales, x, y, z, dirX, dirZ, farmId)       local self = MultiBaleSpawnerEvent.emptyNew()       self.baleTypeIndex = baleTypeIndex       self.fillTypeIndex = fillTypeIndex       self.numBales = numBales       self.x = x       self.y = y       self.z = z       self.dirX = dirX       self.dirZ = dirZ       self.farmId = farmId       return self end

For our new event, we include two instructors: one that takes no arguments and one that takes a baleTypeIndex, fillTypeIndex, the number of bales, and information about the spawn position. If the constructor with arguments is used, then each argument is assigned to a corresponding field in the event class.

function MultiBaleSpawnerEvent:writeStream(streamId, connection)       streamWriteUInt8(streamId, self.baleTypeIndex)       streamWriteUIntN(streamId, self.fillTypeIndex,             FillTypeManager.SEND_NUM_BITS)       streamWriteUIntN(streamId, self.numBales,             MultiBaleSpawnerUtil.SEND_NUM_BITS)       streamWriteFloat32(streamId, self.x)       streamWriteFloat32(streamId, self.y)       streamWriteFloat32(streamId, self.z)       streamWriteFloat32(streamId, self.dirX)       streamWriteFloat32(streamId, self.dirZ)       streamWriteUIntN(streamId, self.farmId,             FarmManager.FARM_ID_SEND_NUM_BITS) end

Next, we include the writeStream() function which writes the event data to the network stream. Note we use the SEND_NUM_BITS field defined in the previous program when writing to the network.

function MultiBaleSpawnerEvent:readStream(streamId, connection)       self.baleTypeIndex = streamReadUInt8(streamId)       self.fillTypeIndex = streamReadUIntN(streamId,             FillTypeManager.SEND_NUM_BITS)       self.numBales = streamReadUIntN(streamId,             MultiBaleSpawnerUtil.SEND_NUM_BITS)       self.x = streamReadFloat32(streamId)       self.y = streamReadFloat32(streamId)       self.z = streamReadFloat32(streamId)       self.dirX = streamReadFloat32(streamId)       self.dirZ = streamReadFloat32(streamId)       self.farmId = streamReadUIntN(streamId,             FarmManager.FARM_ID_SEND_NUM_BITS)       self:run(connection) end

The readStream() function will read the event data from the network stream, updating the fields of the class with the data previously written to the network.

function MultiBaleSpawnerEvent:run(connection)       assert(not connection:getIsServer(),             "MultiBaleSpawnerEvent is client to server only")       MultiBaleSpawnerUtil.spawnBales(self.baleTypeIndex, self.fillTypeIndex, self.numBales, self.x, self.y, self.z, self.dirX, self.dirZ, self.farmId) end

Once the fields are updated, we call the run() function. The run() function executes the event by calling the spawnBales() function of MultiBaleSpawnerUtil.lua, passing along the relevant information about the bales and the spawn position.

With the utility and event scripts created, we are ready to create the main component program of the mod, MultiBaleSpawnerScreen.lua. Let us now explore the script:

MultiBaleSpawnerScreen = {} MultiBaleSpawnerScreen.MOD_DIRECTORY = g_currentModDirectory MultiBaleSpawnerScreen.CONTROLS = {       "baleTypes",       "numBales",       "fillTypes",       "baleSizes" } local MultiBaleSpawnerScreen_mt = Class(MultiBaleSpawnerScreen,             ScreenElement) function MultiBaleSpawnerScreen.register()       local screen = MultiBaleSpawnerScreen.new()       if g_gui ~= nil then             -- load the xml layout and assign it to the             -- controller             local filename = Utils.getFilename("gui/MultiBaleSpawnerScreen.xml",                   MultiBaleSpawnerScreen.MOD_DIRECTORY)             g_gui:loadGui(filename, "MultiBaleSpawnerScreen", screen)       end       MultiBaleSpawnerScreen.INSTANCE = screen end

We start by defining a list of GUI elements that should be accessible via the script in a field named CONTROLS. We then create the register() function, which registers and loads our custom GUI. In this function, we create a new instance of the GUI controller and load the appearance from MultiBaleSpawnerScreen.xml into this object. This page is then assigned to the INSTANCE field of the class.

function MultiBaleSpawnerScreen.show(callbackFunc, callbackTarget)       if MultiBaleSpawnerScreen.INSTANCE ~= nil then             local screen = MultiBaleSpawnerScreen.INSTANCE             screen:setCallback(callbackFunc, callbackTarget)             g_gui:changeScreen(nil, MultiBaleSpawnerScreen)       end end

The show() function is a helper function to open the GUI while in-game.

function MultiBaleSpawnerScreen.new(custom_mt)       local self = ScreenElement.new(nil, custom_mt or MultiBaleSpawnerScreen_mt)       self:registerControls(MultiBaleSpawnerScreen.CONTROLS)       self.callbackFunc = nil       self.callbackTarget = nil       self.numBalesTexts = {}       for i=1, MultiBaleSpawnerUtil.MAX_NUM_BALES do             table.insert(self.numBalesTexts, tostring(i))       end       return self end

The new() constructor of the class registers the GUI elements that should be accessible via the script and then creates the list of options for the number of bales that can be spawned by the player. Let us continue through the contents of the file:

function MultiBaleSpawnerScreen.createFromExistingGui(gui, guiName)       MultiBaleSpawnerScreen.register()       local callbackFunc = gui.callbackFunc       local callbackTarget = gui.callbackTarget       MultiBaleSpawnerScreen.show(callbackFunc, callbackTarget) end function MultiBaleSpawnerScreen:setCallback(callbackFunc, callbackTarget)       self.callbackFunc = callbackFunc       self.callbackTarget = callbackTarget end

The createFromExistingGui() function creates a GUI from an existing one. This is used to support in-game GUI hot reloading via the gsGuiReloadCurrent console command. The setCallback() function sets the callback data for the screen, assigning the passed callback function and target to callbackFunc and callbackTarget fields.

function MultiBaleSpawnerScreen:onOpen()       MultiBaleSpawnerScreen:superClass().onOpen(self)       self.numBales:setTexts(self.numBalesTexts)       local fillTypeTexts = {}       self.textIndexToFillTypeIndex = {}       self.fillTypeToBales = {}       for k, baleType in ipairs(g_baleManager.bales) do             baleType.baleTypeIndex = k             for _, fillTypeData in ipairs(baleType.fillTypes) do                   local fillTypeIndex = fillTypeData.fillTypeIndex                   local added = table.addElement(self.textIndexToFillTypeIndex, fillTypeIndex)                   if added then                         local fillTypeTitle = g_fillTypeManager:getFillTypeTitleByIndex(fillTypeIndex)                         table.insert(fillTypeTexts, fillTypeTitle)                   end                   if self.fillTypeToBales[fillTypeIndex] == nil then                         self.fillTypeToBales[fillTypeIndex] = {}                   end                   table.insert(self.fillTypeToBales[fillTypeIndex], baleType)             end       end       self.fillTypes:setTexts(fillTypeTexts)       self.baleTypes:setTexts({g_i18n:getText("fillType_roundBale"),             g_i18n:getText("fillType_squareBale")})       self:updateBaleTypes() end

The onOpen() function handles the event of the GUI opening. In this function, we set the text for the options for the number of bales that can be spawned by the player. The options for the bale type and fill type depend on the map chosen and the mods being used by the player. Because these options are not fixed, we need to set them when the page is opened. Once the list of options for bale and fill types is created, the text for the corresponding GUI elements is set. We will now continue through the script:

function MultiBaleSpawnerScreen:updateBaleTypes()       local hasRoundBale = false       local hasSquareBale = false       local index = self.fillTypes:getState()       local fillTypeIndex = self.textIndexToFillTypeIndex[index]       local bales = self.fillTypeToBales[fillTypeIndex]       for _, bale in ipairs(bales) do             if bale.isRoundbale then                   hasRoundBale = true             else                   hasSquareBale = true             end       end       self.baleTypes:setState(hasRoundBale and 1 or 2)       self.baleTypes:setDisabled(not hasRoundBale or not hasSquareBale)       self:updateBaleSizes() end

The updateBaleTypes() function updates the text for the available bale types. The function checks if round and square bale types are available for the current fill type. If so, we select the round bale – we always select the round bale if it’s available; otherwise, we select the square bale. We disable the multi-option menu if only one bale type can be selected. At the end of the function, we call updateBaleSizes().

function MultiBaleSpawnerScreen:updateBaleSizes()       local index = self.fillTypes:getState()       local fillTypeIndex = self.textIndexToFillTypeIndex[index]       local bales = self.fillTypeToBales[fillTypeIndex]       local useRoundBale = self.baleTypes:getState() == 1       local baleSizeTexts = {}       self.baleSizeIndexToBale = {}       for _, bale in ipairs(bales) do             if useRoundBale == bale.isRoundbale then                   local size                   if bale.isRoundbale then                         size = string.format("%dx%d cm", bale.diameter*100, bale.width*100)                   else                         size = string.format("%dx%dx%d cm", bale.length*100, bale.width*100,                               bale.height*100)                   end                   table.insert(baleSizeTexts, size)                   table.insert(self.baleSizeIndexToBale, bale)             end       end       self.baleSizes:setTexts(baleSizeTexts)       self.baleSizes:setDisabled(#baleSizeTexts < 2) end

The updateBaleSizes() function is used to update the text for the available bale sizes. The function checks the available bales for the selected fill type and iterates over them. If a given bale matches the bale type selection, then its size option is included in the list of bale size options. Finally, the relevant text GUI elements are updated with the list of available bale sizes. We again disable the option menu if only one bale size is available. Let us look at the final section of the script:

function MultiBaleSpawnerScreen:onClickOk()       g_gui:changeScreen(nil)       local numBales = tonumber(self.numBales:getState())       local fillTypeIndex = self.textIndexToFillTypeIndex[self.fillTypes:getState()]       local bale = self.baleSizeIndexToBale[self.baleSizes:getState()]       local baleTypeIndex = bale.baleTypeIndex       if self.callbackFunc ~= nil then             if self.callbackTarget ~= nil then                   self.callbackFunc(self.callbackTarget, baleTypeIndex, fillTypeIndex, numBales)             else                   self.callbackFunc(baleTypeIndex, fillTypeIndex, numBales)             end       end end function MultiBaleSpawnerScreen:onClickBack()       g_gui:changeScreen(nil) end function MultiBaleSpawnerScreen:onClickBaleType()       self:updateBaleSizes() end function MultiBaleSpawnerScreen:onClickFillType()       self:updateBaleTypes() end MultiBaleSpawnerScreen.register()

The onClickOk() function handles the click event for the interaction with the GUI. When pressed, the GUI is closed and the player’s selections are recorded. We then perform the callback function, passing along the player’s selections so the bales can be spawned. The onClickBack() function simply closes the GUI. The onClickBaleType() will call the updateBaleSizes() function to ensure the options displayed to the player are accurate. The onClickFileType() function will call the updateBaleTypes() function to similarly make sure the list of available bale types is accurate. Finally, we call the register() function we implemented earlier to register the GUI and make it available to the player.

We now only have two shorter scripts to add for the mod. We will start with PlayerExtension.lua:

Player.registerActionEvents = Utils.appendedFunction(Player.registerActionEvents,       function(self)             g_inputBinding:beginActionEventsModification(Player.INPUT_CONTEXT_NAME)             local inputAction = InputAction.OPEN_MULTI_BALE_SPAWNER             local callbackTarget = self             local callbackFunc = self.openMultiBaleSpawner             local triggerUp = false             local triggerDown = true             local triggerAlways = false             local startActive = true             local _, eventId = g_inputBinding:registerActionEvent(inputAction,                   callbackTarget, callbackFunc, triggerUp, triggerDown, triggerAlways,                   startActive)             g_inputBinding:setActionEventText(eventId,                   g_i18n:getText("input_OPEN_MULTI_BALE_SPAWNER"))             g_inputBinding:setActionEventTextVisibility(eventId, true)             g_inputBinding:endActionEventsModification() end)

This script is responsible for appending an anonymous function to the registerActionEvents() function of the player class. Much like prepending functions to existing library functions, causing our function to run before the library code, we can add functionality after a library function by appending it.

The function we are appending registers the new input action to open the multibale spawner menu. We register this action event for the player context without switching – this is important when called from within the UI context. The input action is then registered in the player context.

The first value returned by the registerActionEvent() function is isActive which tells you whether the action was registered successfully. We do not need this value, so we will use the underscore (_) as traditionally used to ignore values in a tuple return. The second value returned by the function is the eventId. We will use this value later to set the help text and activate the input in the upper-left help box. Lastly, we reset registration context, which updates event data in the input system.

function Player:openMultiBaleSpawner(actionName, inputValue, callbackState, isAnalog, isMouse, deviceCategory)       local callback = function(baleTypeIndex, fillTypeIndex, numBales)             local x, y, z = getWorldTranslation(self.rootNode)             local dirX, dirZ = -math.sin(self.rotY), -math.cos(self.rotY)             x = x + dirX * 4             z = z + dirZ * 4             local farmId = self.farmId             g_client:getServerConnection():sendEvent(MultiBaleSpawnerEvent.new(                   baleTypeIndex, fillTypeIndex, numBales, x, y, z, dirX, dirZ, farmId))       end       MultiBaleSpawnerScreen.show(callback) end

The last function we add in this program is openMultiBaleSpawner() which handles user input of clicking the button and opening the GUI. In this function, we define a callback function which sends the event to spawn the bales with the player’s current selections. This callback is passed to the show() function of MultiBaleSpawnerScreen.lua which binds the function to the spawn button.

Our final script, AdditionalGuiProfiles.lua, loads an additional GUI profiles file. This is needed to access our mod-defined profiles in the multiBaleSpawnerScreen.xml:

if g_gui ~= nil then       g_gui:loadProfiles(g_currentModDirectory .. "gui/guiProfiles.xml") end

With this small program, you have finished creating all of the XML and Lua files required for the multibale spawner mod. As always, you should take some time to review what you have learned and accomplished in this chapter.

Testing the Mod

With the scripts for your mod now created, you are ready to begin testing. Start by running the game without debugging from the Debug application menu of the GIANTS Studio. After you begin a new game on the map of your choice with your mod selected, press the B key on your keyboard while controlling your character. This should open the GUI and allow you to select the bale type, size, and fill type. Once you have made your selections, the bale should appear in front of you after clicking the Spawn button.

Summary

In this chapter, you gained experience working with GUI elements and giving players the ability to interactively spawn items. Additionally, you learned how to generally create behavior in the environment with GUI buttons.

In the next chapter, you will learn how to create a machine that gives players money based on their own numeric input.