-
Notifications
You must be signed in to change notification settings - Fork 3
Basic guide for TL Pro modders
- Program
- Mod structure
- Dump
-
Code
4.1. NativeClass
4.2. NativeObject
4.3. NativeMethod
4.4. NativeArray
4.5. require
The first you need to do is to choose a program, where you will write your code. There are two variants: text editor or IDE.
- Notepad++
- Sublime Text
- Atom
- Visual Studio Code
- If you going to write your code on smartphone, Code Editor will be good choice.
- Visual Studio
- Webstorm (30 days trial period)
Most of the code is written in file with main title and js file extension - main.js
All classes, functions and fields are in dump. You will face difficulties with creating mods without the dump. If you're going to create mods, you'll need to download it.
You can download the game's source code here or get it by yourself by opening executable game file with any C# decompilator.
Important notice - code base is big and decompilation of some classes/functions consumes enough of RAM.
Mods are written on JavaScript language, you can find a lot of tutorials of its basics online, for example here. This guide will be superficial, because I won't go into too much detail. TL Pro allows you to get Terraria classes, call/hook functions and get/change variable values.
To start working with class, you need to get it, its can done by object NativeClass
.
const Player = new NativeClass("Terraria", "Player");
This code will write object of Terraria.Player class in variable Player.
The first argument is NameSpace
class, namely classpath. The second argument is Class
- class name. You can find all of this information in dump or in game code.
After getting a class, we can call it's static/public/private fields. Static functions and fields are noted by static
access modifier. Public functions and fields are noted by public
access modifier. Private functions and fields are noted by private
access modifier.
How to work with fields:
Player.defaultItemGrabRange = 10; // Setting defaultItemGrabRange field 10 value
let grabRange = Player.defaultItemGrabRange; // Writing into grabRange values from defaultItemGrabRange
How to work with functions:
At first, you need to get the function itself, there are two ways to do that:
const GetClosestRollLuck = Player["float GetClosestRollLuck(int x, int y, int range)"]; // First way
const GetClosestRollLuck = Player.GetClosestRollLuck; // Second way
After getting the function, we can call it:
let result = GetClosestRollLuck(434, 256, 10);
After calling, the result of function's execution will be written in the variable result
.
The first way is always recommended, because there can be functions with the same titles, but different arguments (function overloading). An example of this function.
const SetDefaults = Item['void SetDefaults(int Type)'];
const SetDefaults = Item["void SetDefaults(int Type, bool noMatCheck)"];
const SetDefaults = Item['void SetDefaults(int Type)']; // Good
const SetDefaults = Item.SetDefaults; // Bad, this option can lead to game crash and make the game stop executing
The second way can be used, if you surely know what you do.
Important notice - if default
values are referred in the function, they should be deleted.
const Dust = new NativeClass('Terraria', 'Dust');
const NewDust = Dust['int NewDust(Vector2 Position, int Width, int Height, int Type, float SpeedX = 0f, float SpeedY = 0f, int Alpha = 0, Color newColor = default(Color), float Scale = 1f)']; // Bad
const NewDust = Dust['int NewDust(Vector2 Position, int Width, int Height, int Type, float SpeedX, float SpeedY, int Alpha, Color newColor, float Scale)']; // Good
NativeClass is needed for working with native classes, it even has it's constructor - new NativeClass('Namespace', 'Class')
. As well as NativeObject, fields depend on classes saved in them, these fields can be regular types of js or NativeArray, NativeMethod, NativeObject or NativeClass.
Native class doesn't store object example, all it's fields are static.
NativeClass has a new()
function.
It doesn't accept arguments and is used for initializing the new example of NativeObject class. Remember, after calling new()
, you need to call constructor .ctor()
to fully initialize the object.
const Vector2 = new NativeClass('Microsoft.Xna.Framework', 'Vector2'); // Getting example of Vector2 class
const newVector = Vector2.new(); // Creating new Vector2 object
newVector['void .ctor(float x, float y)'](5.0, 10.0); // Initializing our new object and referring its values.
This js code is equal to code written in C#
Vector2 newVector = new Vector2(5f, 10f);
NativeObject is needed for working with native objects (class examples), it doesn't have a constructor. NativeObject fields change depending on the examples that is stored in them. Fields can be regular js types, and NativeArray, NativeObject or NativeMethod as well.
NativeMethod is needed for working with native functions, it doesn't have any constructors, and it can be obtained from NativeClass or NativeObject. NativeMethod can be called similarly as regular js function. NativeMethod has the hook()
function, where callback function is passed as an argument. Hooks are needed to change the execution of native functions. The first argument of callback is original - it's NativeMethod, which is needed to call original function version. If we are hooking not the static function, we refer class example (self)
as a second argument.
Recipe.SetupRecipes.hook((original) => { // Hooking static func SetupRecipes()
original();
});
Player.OpenFishingCrate.hook((original, self, crateItemID) => { // Hooking not static func OpenFishingCrate()
original(self, crateItemID);
});
Hook is a function, which allows to change the action of other functions, add your own code to them, change the returned value, etc. Any function can be hooked.
Mod example: Opening the inventory will teleport the player to a random place on the map.
const Player = new NativeClass("Terraria", "Player");
Player.ToggleInv.hook((original, self) => { // Hooking function ToggleInv(), (original) is passed as first argument, instance(self) as second
original(self); // Calling native function
self.TeleportationPotion(); // Calling instance function TeleportationPotion(), which is located in Player class
});
NativeArray is used for working with native arrays, it doesn't have a constructor and works as well as regular js array. It has length parameter, which stores arrays length info. It can be returned by function and field received from NativeClass or NativeObject.
As your mod grows, you usually want to divide it into many files, called «modules». Module usually store class or library with functions. Module is juct file. One script is one module.
require - function, which works here as well as in node.js. It's needed for importing objects from other files.
Let's create a new module, and name it Utilities.js, for example. This module stores one function, which destroys all the enemies projectiles.
function KillHostileProjectile() {
for (let i = 0; i < Main.maxProjectiles; i++) {
const proj = Main.projectile[i];
if (proj.active && proj.hostile && !proj.friendly && proj.damage > 0) {
proj.Kill();
}
}
}
exports.KillHostileProjectile = KillHostileProjectile; // Note function, which will be able outside the current module.
Adding require in main.js
const Utils = require('./Utilities.js'); // Referring module, which imports all of the functions and variables in this module
Utils.KillHostileProjectile(); // Calling function from imported module
const KillProj = require('./Utilities.js').KillHostileProjectile; // Referring module and specific function, which we are importing.
Example of a simple mod, that change firerate and projectile velocity of Minishark.
const Item = new NativeClass('Terraria', 'Item');
const ItemID = new NativeClass('Terraria.ID', 'ItemID');
const SetDefaults = Item["void SetDefaults(int Type, bool noMatCheck)"];
SetDefaults.hook((original, self, type, noMatCheck) => {
original(self, type, noMatCheck);
if (type == ItemID.Minishark) { // If item is Minishark
self.useTime = 4; // How much time is needed for item usage
self.useAnimation = 4; // How long is item animation
self.shootSpeed = 10.0; // How fast projectiles are
}
});