This chapter will explore making a money cheat which will allow players to give themselves unlimited amounts of in-game money (see Figure 10-1). This mod may also be useful for testing other mods you make as you will be able to make any purchases from the shop or upgrades without concern for money. Furthermore, you will need to learn how to make it so players can interact with placeable objects in the environment. Let’s start!

Figure 10-1
A screenshot of money cheat mod.

Running out of money? Fear no more!

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 scan code.

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

As always, we begin the mod by creating modDesc.xml. Let us explore 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 - Money Cheat</en>       </title>       <description>             <en>A sample mod</en>       </description>       <iconFilename>icon_moneyCheat.png</iconFilename>       <placeableSpecializations>             <specialization name="atm" className="PlaceableATM"                   filename="scripts/PlaceableATM.lua" />       </placeableSpecializations>       <placeableTypes>             <type name="atm" parent="simplePlaceable"                         filename="$dataS/scripts/placeables/Placeable.lua">                   <specialization name="atm" />             </type>       </placeableTypes>       <extraSourceFiles>             <sourceFile filename="scripts/events/ATMEvent.lua"/>       </extraSourceFiles>       <storeItems>             <storeItem xmlFilename="placeable/atm.xml"/>       </storeItems>       <l10n filenamePrefix="l10n/l10n" /> </modDesc>

After defining the basic fields of the mod, we define a specialization in the placeableSpecializations field called PlaceableATM. In our mod, players will be able to get the money from a physical ATM, so they will need to buy the ATM and place it down.

We will define the PlaceableATM specialization in the “Creating Lua Files” section of this chapter. Following this, we set the ATM to be placeable by using the placeableTypes field and using the functionality of the Placeable base class.

Additionally, we add our previous defined specialization to add the features of the ATM script. In the extraSourceFiles field, we also include ATMEvent.lua, which will also be defined in the next section. In the storeItems field, we include a reference to atm.xml, which we will define later in this section.

Finally, we define the l10n element which is used for referencing translation files.

Next, we will define the atm.xml file which holds the configurations for the ATM. We will now look at the contents of the file:

<?xml version="1.0" encoding="utf-8" standalone="no" ?> <placeable type="atm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://validation.gdn.giants-software.com/fs22/placeable.xsd">       <storeData>             <name>ATM</name>             <functions>                   <function>An ATM to get Money</function>             </functions>             <image>placeable/store_atm.png</image>             <price>1500</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>

We again start with the XML declaration of the placeable root element. We define ATM to be of the placeable type, so the system knows the feature set of the object. In the storeData field, we include the information relevant to the item being displayed in the store. This includes the price, image, function, and other basic information about the ATM. You can refer to the “Creating XML Files” section of Chapter 5, “Making a Diner with a Rotating Sign,” where we first use this field. Let us continue:

    <base>         <filename>placeable/atm.i3d</filename>     </base>     <placement useRandomYRotation="false" useManualYRotation="true" >         <testAreas>             <testArea startNode="testArea1Start" endNode="testArea1End" />         </testAreas>     </placement>

In the base element, we include the relative path from the mod directory to the .i3d file containing the ATM. Next, we configure placement for the ATM via the placement field. In this field, we reference nodes on the model to determine whether an object obstructs the area the player wants to put the ATM.

<clearAreas>       <clearArea startNode="clearArea1Start" widthNode="clearArea1Width"             heightNode="clearArea1Height"/> </clearAreas> <leveling requireLeveling="true" maxSmoothDistance="10" maxSlope="75"       maxEdgeAngle="30" >       <levelAreas /> </leveling> <indoorAreas />

In the clearAreas field, we set the area the ATM is placed to be clear of foliage and other environmental objects.

<ai>       <updateAreas>             <updateArea startNode="testArea1Start" endNode="testArea1End" />       </updateAreas> </ai>

In the ai field, we mark the area as blocked so autonomous equipment and other AI will avoid the ATM. We will now cover the remaining portion of the file:

<atm moneyPerAction="15000">       <trigger node="playerTrigger" />       <sounds>             <action file="sounds/cashRegistry.wav" innerRadius="5.0" outerRadius="15.0"                   fadeOut="0.1" linkNode="playerTrigger">                   <volume indoor="0.45" outdoor="1.1" />                   <pitch indoor="1.0" outdoor="1.0" />             </action>       </sounds> </atm> <i3dMappings>       <i3dMapping id="playerTrigger" node="0>0|0" />       <i3dMapping id="clearArea1Start" node="0>1|0" />       ... </i3dMappings> </placeable>

The file concludes with a custom element for the mod called atm. In this element, we define the amount of money the player receives when they interact with the ATM via the moneyPerAction field, which holds a value of 15000. You can change this value to however much you want players to receive. The trigger element references a trigger shape in our i3d file. It is later used to notify a script if a player is close to the ATM. We also include a satisfying sound when the ATM is used by the player and they receive their money. Lastly, we include the i3d mappings inside of the i3dMappings field.

We will now create translation files for text we want to display to the user about the ATM. For English, we will define a file called l10n_en.xml. Let us now look at the file:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <l10n>       <elements>             <e k="action_atmRequest" v="Draw Money"/>       </elements> </l10n>

In the file, we simply include the text that should be associated with the ATM interaction. In this case, we want to tell the player that by interacting with the ATM they will Draw Money.

Let us now look at the contents of the German translation file, l10n_de.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <l10n>       <elements>             <e k="action_atmRequest" v="Geld abheben"/>       </elements> </l10n>

Like in the file for English, we include the text that describes the action with the ATM. This concludes all of the .xml files for the mod. In the next section, we will look at the .lua files we need to create.

Creating Lua Files

The first file we will create is ATMEvent.lua, which builds on the base Event class. Let us now cover the file’s contents:

ATMEvent = {} local ATMEvent_mt = Class(ATMEvent, Event) InitEventClass(ATMEvent, "ATMEvent") function ATMEvent.emptyNew()       local self = Event.new(ATMEvent_mt)       return self end function ATMEvent.new(placeable, farmId)       local self = ATMEvent.emptyNew()       self.placeable = placeable       self.farmId = farmId       return self end

We start by creating two constructors with one that takes no arguments and one that takes a placeable object and farm ID. If the object and ID are passed to the constructor, then they are assigned to placeable and farmId fields within the class.

function ATMEvent:writeStream(streamId, connection)       NetworkUtil.writeNodeObject(streamId, self.placeable)       streamWriteUIntN(streamId, self.farmId,             FarmManager.FARM_ID_SEND_NUM_BITS) end function ATMEvent:readStream(streamId, connection)       self.placeable = NetworkUtil.readNodeObject(streamId)       self.farmId = streamReadUIntN(streamId, FarmManager.FARM_ID_SEND_NUM_BITS)       self:run(connection) end

Like in other chapters, the readStream() and writeStream() functions are used to send and process updates to the network stream. In the readStream() function, we update the placeable and farmId fields with what has been written to the stream.

function ATMEvent:run(connection)       assert(not connection:getIsServer(), "ATMEvent is client to server only")       if self.placeable ~= nil then             self.placeable:requestMoney(self.farmId)       end end

Lastly, the run() function calls the requestMoney() function of the placeable object associated with the event. This function will be implemented in the PlaceableATM specialization, which we will define in this section.

With ATMEvent.lua complete, we are ready to define PlaceableATM.lua, the main component of the mod. Let us now look through its contents:

local modName = g_currentModName PlaceableATM = {} PlaceableATM.SPEC_TABLE_NAME = "spec_"..modName..".atm" function PlaceableATM.registerXMLPaths(schema, basePath)       schema:setXMLSpecializationType("ATM")       schema:register(XMLValueType.NODE_INDEX, basePath .. ".atm.trigger#node",             "Node index or i3d mapping name of the trigger shape")       schema:register(XMLValueType.INT, basePath .. ".atm#moneyPerAction",             "The amount of money a player gets", 100000)       SoundManager.registerSampleXMLPaths(schema,             basePath .. ".atm.sounds", "action")       schema:setXMLSpecializationType() end function PlaceableATM.prerequisitesPresent(specializations)       return true end

We begin the specialization by defining the default functions. After defining the namespace for the mod, we include the registerXMLPaths() function which registers elements from the XML files with the placeable schema. Next, we add the prerequisitesPresent() function. Because we do not require any prerequisites, we simply return true.

function PlaceableATM.registerEventListeners(placeableType)       SpecializationUtil.registerEventListener(placeableType, "onLoad", PlaceableATM)       SpecializationUtil.registerEventListener(placeableType, "onDelete", PlaceableATM) end function PlaceableATM.registerFunctions(placeableType)       SpecializationUtil.registerFunction(placeableType, "onATMTriggerCallback",             PlaceableATM.onATMTriggerCallback)       SpecializationUtil.registerFunction(placeableType, "requestMoney",             PlaceableATM.requestMoney) end

The registerEventListeners() function registers the onLoad() and onDelete() functions of the mod. These functions will be implemented later in the script. Lastly, the registerFunctions() function registers our custom onATMTriggerCallback() and requestMoney() functions, which will be added later in the file. Let us continue through the script:

      function PlaceableATM:onLoad(savegame)       local spec = self[PlaceableATM.SPEC_TABLE_NAME]       local node = self.xmlFile:getValue("placeable.atm.trigger#node", nil, self.components, self.i3dMappings)       if node ~= nil then             addTrigger(node, "onATMTriggerCallback", self)             spec.triggerNode = node             spec.activatable = ATMActivatable.new(self, node)             spec.moneyPerAction = self.xmlFile:getValue(                   "placeable.atm#moneyPerAction", 100000)             spec.samples = {}             spec.samples.action = g_soundManager:loadSampleFromXML(self.xmlFile,                   "placeable.atm.sounds", "action", self.baseDirectory, self.components, 1,                   AudioGroup.VEHICLE, self.i3dMappings, self)       else             Logging.xmlWarning(self.xmlFile, "Missing atm trigger!")       end end

The onLoad() function starts by referencing the node the player triggers to receive the money. If the node exists, then we add an interaction trigger that calls the onATMTriggerCallback() function when triggered. A shape within the i3d can be marked as a trigger shape in the editor.

Triggers are a part of the physics system, so this shape also has to be a physics shape. Normally, it should have the rigidbody type STATIC or KINEMATIC. It is also important to set the correct collisionMask of the shape; otherwise, the script may not fire any trigger callback because the physics engine does not detect any collisions.

The node is then assigned to the triggerNode field of the class. We also assign an Activatable object to the activatable field of the class. Activatable objects are script objects that are handled by the ActivatableObjectsSystem and used to tell the system that the player is within a given area where they can activate or trigger a specific action. From atm.xml, we retrieve the value for how much money the player should get per interaction and assign it to the moneyPerAction field. Finally, we load the interaction sound into the samples field of the class.

function PlaceableATM:onDelete() local spec = self[PlaceableATM.SPEC_TABLE_NAME] if spec.triggerNode ~= nil then       removeTrigger(spec.triggerNode)       local system = g_currentMission.activatableObjectsSystem       system:removeActivatable(spec.activatable)       g_soundManager:deleteSamples(spec.samples) end end

The onDelete() function will clean up the class when the ATM is deleted by checking that the trigger node exists and if so removing the trigger callback, the activatable object, and the trigger sound.

Let us now explore our custom functions:

function PlaceableATM:onATMTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)       if onEnter or onLeave then             if g_currentMission.player ~= nil and                   otherId == g_currentMission.player.rootNode and                   g_currentMission.player.farmId ~= FarmManager.SPECTATOR_FARM_ID              then                   local spec = self[PlaceableATM.SPEC_TABLE_NAME]                   local activatableSystem = g_currentMission.activatableObjectsSystem                   if onEnter then                         activatableSystem:addActivatable(spec.activatable)                   else                         activatableSystem:removeActivatable(spec.activatable)                   end             end       end end

The onATMTriggerCallback() function is passed whether the player is entering or leaving the trigger area. If the player is leaving or entering the area, then we check that the player is actually present and display or remove the trigger accordingly. The second parameter of the callback holds the entity ID of the colliding physics shape. We can use it to determine if the shape is a player or not by simply comparing it with the player rootNode. Let us continue:

function PlaceableATM:requestMoney(farmId)       local spec = self[PlaceableATM.SPEC_TABLE_NAME]       g_soundManager:playSample(spec.samples.action)       if not self.isServer then             g_client:getServerConnection():sendEvent(ATMEvent.new(self, farmId))             return       end       local amount = spec.moneyPerAction       local moneyType = MoneyType.OTHER       local addChange = true       local forceShow = true       g_currentMission:addMoney(amount, farmId, moneyType,             addChange, forceShow) end

The requestMoney() function will play the trigger sound and, if we are on the client, send a request to the server to dispense money. The handling and syncing of money are done on the server only, and the balance is not allowed to be changed on the client. As such, we need to tell the server that we requested money.

We do so by sending the ATMEvent to the server and leave the function by calling return afterward. So, if this function is being run on the server, then it will add the specified amount of money to the player’s balance. The addMoney() function takes the amount of money that should be added or subtracted and the ID of the farm that the change should be applied to.

The money type is mostly used for the statistics of the game. The addChange and forceShow flags tell the system if the money change should be shown immediately using the in-game notifications in the top-right corner of the HUD or if the system should sum all calls up until an explicit call with forceShow = true occurs. We will now cover the remaining contents of the file:

ATMActivatable = {} local ATMActivatable_mt = Class(ATMActivatable) function ATMActivatable.new(placeable, triggerNode)       local self = setmetatable({}, ATMActivatable_mt)       self.placeable = placeable       self.triggerNode = triggerNode       self.activateText = g_i18n:getText("action_atmRequest")       return self end function ATMActivatable:getIsActivatable()       if g_gui.currentGui ~= nil then             return false       end       return g_currentMission.player.farmId ~= FarmManager.SPECTATOR_FARM_ID end function ATMActivatable:getDistance(x, y, z)       local tx, ty, tz = getWorldTranslation(self.triggerNode)       return MathUtil.vector3Length(x-tx, y-ty, z-tz) end function ATMActivatable:run()       self.placeable:requestMoney(g_currentMission.player.farmId) end

We create ATMActivatable to be a new class. For this class, we include a constructor that takes a placeable object and trigger node. The placeable object and trigger node are respectively assigned to the placeable and triggerNodes fields of the ATMActivatable class. We also set the activateText field to the action text defined in the translation file. The getIsActivatable() function checks whether the player can currently request money.

The getDistance() function returns the distance to the activatable object and is used to prioritize actions if multiple activatables are in range.

Finally, we define the run() function which will call the requestMoney() function of the object held in the placeable field. This concludes all of the programming required for this mod. Like with the other chapters, take a moment to review what you have accomplished and written and how these concepts may apply to future mods.

Testing the Mod

With the scripts for your mod now created, you are ready to begin testing. First, start a new game and select a map of your choice. Make sure that you have your mod selected to be used in the game. Once the game had loaded, open the Construction screen. Go to the miscellaneous category and place the ATM. If your mod has been created correctly, you should be prompted to withdraw money in the amount you set in the configuration file.

Summary

In this chapter, you learned how to create an ATM that dispenses a predetermined amount of money to the player. Importantly, you learned how to use activatable objects to trigger functions when a player interacts with an object within a certain distance.

In the next chapter, you will learn to make your mods available to other players and members of the Farming Simulator community by publishing your creations to the ModHub.