Web Rarely

A method of solution is perfect if we can forsee from the start, and even prove, that following that method we shall attain our aim. -- Leibnitz

Battle Brothers mod kit

2019-01-20

Background

I created a rudimentary mod kit for the game Battle Brothers that allows editing the encrypted Squirrel scripts (.cnut files) within the game. Since almost the entire game is implemented in script, this allows tremendous flexibility for modding. It started when I wanted to know which character backgrounds were best to hire in a "value for money" sense. There was a spreadsheet that the community had painstakingly created over the years based on hundreds of manual tests showing the typical stat ranges of various backgrounds, but it was invalidated by the Beasts & Exploration DLC which rebalanced various parts of the game. I wished I could just take a look at how characters are generated, but like many people trying to peek under the hood I soon saw that the scripts are shipped only in a compiled form, and not only that but they appear to be corrupted or encrypted.

Accessing the files

Before we can start messing with the game data files, we first have to know where they are. If your Battle Brothers installation directory is bbros, then the data files are stored within bbros/data/. Within that directory you'll see files named like data_001.dat. These are actually .zip files, and if you rename it to data_001.zip you'll be able to open it. Images, sound effects, etc., are not encrypted, so you can examine them. (Don't be tempted to modify them inside the .zip file, though. See below for how to make changes.) The scripts are the .cnut files, but if you open them they just look like garbage.

Encryption and decryption

The "corruption" in the compiled scripts is actually an unusual encryption algorithm that encrypts only bits and pieces of the files, making them look like normal compiled scripts at a glance, but preventing them from actually working if you try to run or examine them. After painstakingly reverse engineering parts of the game, I was able to decipher the encryption algorithm. I spent quite a long time discovering how it worked and then a while longer translating the cryptic instructions into C code that I could run. Only after I was done did I realize that one of the constants in the algorithm is well-known, and that the core cipher is XXTEA.

But like I said, only parts of the file are encrypted. The encryption algorithm is unusual in that the format of a Battle Brothers encrypted .cnut file cannot be described in a stand-alone way since it's intimately tied to the internal implementation details of the Squirrel script engine. Specifically, the Squirrel API calls to parse a compiled script take a reader callback function, which gets called with a buffer and a number of requested bytes, and the function is supposed to read those bytes from the application's storage. Basically, it's a byte-oriented stream interface. Now, where it gets tricky is that the game only decrypts runs of 8 or more bytes requested through that stream interface, and it doesn't keep any state between invocations. If the Squirrel engine changed to break one read into two, or to combine two reads into one, or even just changed the size of any encrypted read, the file format would effectively change. So, to be safe, you should use the same version of Squirrel as the game. At the time of writing, this is 3.0.4, but I've also tested with 3.0.7. Also, only multiples of 4 bytes are encrypted, so a 15-byte read has the first 12 bytes encrypted and the last 3 bytes unencrypted.

So, without further ado, here's how you'd go about decrypting a Battle Brothers .cnut file:

// the standard XXTEA block cipher algorithm
#define DELTA 0x9e3779b9
#define MX(p) (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[((p)&3)^e] ^ z)))

void decrypt(uint32_t *values, uint32_t count, const uint32_t key[4])
{
    uint32_t rounds = 6 + 52/count, sum = rounds*DELTA, y = values[0], z;
    do
    {
        uint32_t e = (sum >> 2) & 3;
        for (uint32_t p = count-1; p; p--)
        {
            z = values[p-1];
            y = values[p] -= MX(p);
        }
        z = values[count-1];
        y = values[0] -= MX(0);
        sum -= DELTA;
    } while (--rounds);
}

// the key used by Battle Brothers
static uint32_t bbkey[4] = { 238473842, 20047425, 14005, 978629342 };

// the Squirrel reader function
SQInteger read_encrypted(SQUserPointer file, SQUserPointer buffer, SQInteger length)
{
  length = file_read(file, buffer, length); // read the data from the file
  if (length >= 8) decrypt((uint32_t*)buffer, length / sizeof(uint32_t), bbkey);
  return length;
}

...
sq_readclosure(vm, read_encrypted, file);

To encrypt a file, you can't simply do the analogous approach of using a writer callback function to encrypt all blocks of 8+ bytes, since the Squirrel writer doesn't invoke the callback for the same offsets and lengths as the reader, which is needed for the two to match. (And even if they did match, there's no guarantee that it would stay that way.) So, I implemented encryption using a reader function as well, so you read the file that you want to encrypt, and as a byproduct of reading it the file gets encrypted.

void encrypt(uint32_t *values, uint32_t count, const uint32_t key[4])
{
    uint32_t rounds = 6 + 52/count, sum = 0, y, z = values[count-1];
    do
    {
        sum += DELTA;
        uint32_t e = (sum >> 2) & 3, p;
        for (p = 0; p < count-1; p++)
        {
            y = values[p+1];
            z = values[p] += MX(p);
        }
        y = values[0];
        z = values[count-1] += MX(p);
    } while (--rounds);
}

SQInteger read_and_encrypt(SQUserPointer file, SQUserPointer buffer, SQInteger length)
{
  length = file_read(file, buffer, length); // read the data from the file
  if (length >= 8)
  {
    // encrypt the file in place. you could also write the changes to a separate file
    encrypt((uint32_t*)buffer, length / sizeof(uint32_t), bbkey); // encrypt the data
    file_seek(file, -length, SEEK_CUR); // seek back to the start of the block
    file_write(buffer, length); // write the encrypted bytes back to the file
    file_flush(file); // some C file APIs require a flush when switching from write to read

    // decrypt the data again so we don't return garbage to Squirrel
    decrypt((uint32_t*)buffer, length / sizeof(uint32_t), bbkey);
  }

  return length;
}

Decompiling a script

Now we can decrypt a script file, but it's still in compiled form, so we can't easily edit it. To decompile it, I recommend using NutCracker. I actually don't like that decompiler, because the output is incorrect. It has numerous bugs, some of which I've fixed, and is more verbose than it needs to be. It also doesn't preserve all the information from the .cnut file, so if you decompile a file with NutCracker and then recompile it, the result will not be the same as the original. It still seems to work when loaded into the game, though.

Because of those problems, I wanted to write my own decompiler. I'm sure I can create a better one than NutCracker, but my free time is limited so I probably won't do so unless there's a real demand. The problems above are not fatal, since Battle Brothers is capable of loading plain-text scripts (for the most part) and most scripts decompile correctly. A few scripts don't, though, so be careful.

Anyway, here's an example of how to use NutCracker to decompile a script. By default it prints the script to the console, so you simply redirect the output to a file. (The NutCracker author doesn't distribute binaries for the decompiler, but a binary is included in my mod kit.) From a command-line:

nutcracker decrypted.cnut >decrypted.nut

Getting a modified script loaded by the game

Lets say you decompile scripts/items/weapons/knife.cnut, which starts like this:

this.knife <- this.inherit("scripts/items/weapons/weapon", {
	m = {},
	function create()
	{
		this.weapon.create();
		this.m.ID = "weapon.knife";
		this.m.Name = "Knife";
		this.m.Description = "A short knife, not made for combat.";
		this.m.Categories = "Dagger, One-Handed";
    ...

Just for a test, you change "not" to "totally" within the description. Now how to get the modification back into the game? The most convenient way is to just use the plain-text script. For many scripts (including this one), the game will prioritize a plain-text script named "foo.nut" over a compiled script named "foo.cnut" in the same directory, so you could drop the plain-text script into the directory with the .cnut file. But to be sure you can recompile the .nut file using the standard Squirrel compiler (sq.exe) into a .cnut file and then reencrypt it using the mod kit.

You could simply insert the file into the data_001.dat archive, but that's a bad idea, because your modification may cause file corruption if the game is patched, and any game update would undo your changes. We want the game to load the base data and your modified version. Thankfully, the game uses the PhysFS filesystem abstraction library, and configures it in such a way that the game will load files from subdirectories of the data directory as well as files from the .dat archives. So you could actually place your modified knife.nut (or knife.cnut) file in bbros/data/scripts/items/weapons/ and it will take precedence over knife.cnut from the data_001.dat archive. This is good for testing, but is not a good solution for mods in general because it makes it hard to combine multiple mods. (Their files would be all mixed together.)

A better way is to simply package your mod into its own .dat file. Since the .dat files are really .zip files, just create a .zip file with the scripts/items/weapons/knife.nut file inside and place it in the bbros/data/ directory along with data_001.dat. (It's not necessary to use the .dat extension.) The important thing here is that mods get loaded after the base data, and I believe this happens based on the file name. Since 'm' sorts after 'd', naming the archive "mod_knife.zip" hopefully ensures that it gets loaded after the file named "data_001.dat". If order among mods is important (to resolve some conflicts), you could tweak the names to get the right ordering.

Coding guidelines for better mods

Now we can mod the game, but mods created as above will not play well together either with other mods or with future official updates. The reason is that you are overwriting entire files rather than just tweaking the bits you want to change. If an official update changes the file, your copy won't have the update unless you recreate your mod based on the latest official version. And, two mods that update the same file will overwrite each other's changes even if the changes don't conflict. So, we need a way to express the changes relevant to us without duplicating the rest of the file. Consider the following script.

local super = this.knife.create;
this.knife.create = function()
{
  super();
  this.m.Description = "A short knife, totally made for combat.";
}

Something like this would allow us to change just the description while keeping the rest of the knife code the same, and we could put this script into its own file rather than overwriting the base knife file. The difficult part is getting this code loaded at the right time. By placing your file in a folder named !mod_mymod you can make a script load before most others, and by placing it in a folder named ~mod_mymod you can make it load after most others, but neither of those work in this case. That said, I did create a small mod called "modding script hooks" that allows something like this:

::mods_hookNewObject("items/weapons/knife", function(obj)
{
  obj.m.Description = "A short knife, totally made for combat.";
});

On a side note, any sizeable mod will have to deal with official updates. You wouldn't want to redo your mod from scratch after each official update, nor would you want to continue to use outdated script files in your mod. The solution here is to set up version control. Extract the game data, decompile the scripts, and put it into a Mercurial or Git repository. Create a branch for the mod and edit your mod in the new branch. After each official update, switch back to the main branch, extract and decompile the game data again, and check in any changes. Then merge the changes into your mod branch. This way you keep your mod up to date with official changes without having to redo it from scratch. Also, the repository should of course just be on your local disk. Don't go putting all the game files up on GitHub. This also helps if you need to merge multiple mods together due to conflicts.

The mod kit

I packaged together a few tools into a very basic mod kit to get people started modifying the scripts for Battle Brothers. See the README.txt file within the package for details.

Download: bbros.zip (874kb) Version 9 - released February 12, 2019

Changelog

  • Version 1, January 20, 2019 - Initial release
  • Version 2, January 21, 2019 - Attempted to fix a serious bug in NutCracker. I'm not sure if the fix is correct. Hopefully the author will make an official fix.
  • Version 3, January 22, 2019 - Attempted to fix another bug in NutCracker.
  • Version 4, January 23, 2019 - Attempted to fix another serious bug in NutCracker.
  • Version 5, January 26, 2019 - Added a program (bbrusher) that can unpack and repack the game's sprite sheets
  • Version 6, January 27, 2019 - Fixed two more serious bugs in NutCracker
  • Version 7, January 28, 2019 - Expanded the types of sprites that can be added with bbrusher. (Should be all types now.) Made Nutcracker's floating point output prettier. Added a masscompile.bat tool.
  • Version 8, February 3, 2019 - Fixed four more serious bugs in NutCracker
  • Version 9, February 12, 2019 - Improved Nutcracker's handling of non-ANSI strings by using UTF-8

Postscript

So in the end, I got my updated character backgrounds spreadsheet. :-) I wrote a program to scan the script files and make a spreadsheet out of it. I guess that's mission accomplished!

Comments

Who are you? 2019-01-20 11:34PM
What have you done?

you hab changed the construct of the universe for generations to come.

However you have wrote instructions at a high-level skilled programmers voice.

Are you able to dumb down the words in the Battle Brothers mod kit for us without the proper training?

Thank you and still can't believe you mod Sid Meiers Pirates game one of my favorites.

Wish we can get attacked by pirate hunters a full fledge fleet like maybe 1-3 ships trying to sink you or something.
an anonymous vinilly
RE: Who are you? 2019-01-21 05:42AM
I do hope people use the mod kit to make something cool. I'm probably too old now to make actual mods with it. I just wanted to enable other people to do so.

On those lines with Pirates! I remember I was once working on a mod to allow you to have up to 4 ships in combat. (The enemy could also have up to 4 ships.) I never finished it but it would have been interesting...

http://www.hookedonpirates.com/forums/viewtopic.php?t=6771

Then there's this craziness:

http://www.hookedonpirates.com/forums/viewtopic.php?p=91296#91296

Fun times. :-)
my hero 2019-01-21 06:25AM
Hi mate, i have spent literally hundreds of hours battling with those encrypted cnuts. I made Battle Sisters and the ork mod. This work is an absolute life saver. Thank you so very much.
an anonymous Poss
RE: my hero 2019-01-21 09:05AM
Hi Poss. I checked out your mods. Nice work on that art! I wish I was more artistically capable. I think it should be possible to flesh out your mods a bit now that you can edit scripts. For instance, making sure heads and bodies have the same sex, no beards on women, etc. Or, modifying character gear and traits to be more greenskin-like.

There's already some support you could latch on to, since the game has different head, body, and sound sets for the different races, and also for subsets like "old men", "smart men", "tidy men", "untidy men", etc. Women could just be another race or perhaps a different subset of humans. You also wouldn't have to lose any of male art by overwriting it. It could be in addition.

Anyway, I'm looking forward to seeing what you can make. :-)
Error decompiler 2019-01-21 11:57AM
Hi. I get an error when trying to decrypt files.

bbsq.exe -d orc_trophy_item.cnut a.nut
File a.nut had error the index 'std_stream' does not exist

Any idea how to get it working? Btw you are incredible for getting this done. Thank you
an anonymous Rafa
RE: RE: my hero 2019-01-21 01:22PM
I have edited every image in the game, and read through the bytecode in every line in every cnut and js file, looking for anything i could use. I had a rough idea of the structure of the game logic including races, units and how they call the different heads and hair. I just couldn't actually change any of it until now. It was like peering through a thick mist filled with "TRAP" sprinkled through the byte code.

I'll be able to do so much with the knowledge you've posted here, although those bugs you've found in the decompiler are like landmines in the path of any large mod. This is more progress than any modder has made before you.

Many won't be able to follow in your footesteps though, so if you're willing to do more work on this I'm happy to offer whatever support i can to help you. The complexity of this solution greatly limits how many modders will able to try it out.

I have heard many people say they would pay for mod support on this game, including myself. We can get a patreon or crowd funding campaign going if you're interested. I would personally be willing to pay your hourly rate directly to work on this if it got us to a single UI mod tool, or even a better decompiler
an anonymous Poss
RE: Error decompiler 2019-01-21 05:10PM
Hi Rafa. bbsq.exe is not the decompiler. It's just the decrypter. NutCracker is the decompiler. Normally you'd just use massdecompile.bat to decrypt and decompile all the game scripts at once, but if you just want a single script the pattern is:

bbsq.exe -d orc_trophy_item.cnut
nutcracker orc_trophy_item.cnut >orc_trophy_item.nut

Note that since you already ran bbsq.exe it's already been decrypted. Running bbsq.exe a second time will corrupt it. So you'd best start with a fresh copy of the .cnut file.
RE: RE: my hero 2019-01-21 05:16PM
I admire your persistence. I "only" spent about 8-9 hours cracking the encrypted .cnut format, but I was getting pretty sick of it by the end. :-P

I think I fixed the major bug I found in NutCracker, so I hope we are good to go. There are other bugs, though... I hope the author patches them, or else I might have to do it.

I'm not sure what you mean by a "UI mod tool". Theoretically you can do everything with the mod kit I posted, but I realize it can be a pain to edit scripts and HTML and CSS by hand. I suppose one could create a UI tool to let people edit simple things like item stats and character backgrounds. That wouldn't be hard. But any serious mod will probably have to proceed by writing code...
DONATION LINK 2019-01-22 05:03AM
Where can I deposit my donations for this awesome work?
an anonymous vinilly
Thank You 2019-01-22 07:00AM
Adam,

Thank you very much for these mod tools. This is fantastic. Already knee deep into the code with some ideas I've always wanted to do.

Again, greatly appreciated!

Any chance you have been able to get the log file statements in the scripts to print out to file? So far no luck.
an anonymous Rule
Problem with modifying item Script 2019-01-22 07:55AM
Hello There Adam,

first of all Thx a bunch for all the effort you put into this. Finally seeing some mod support was hell of a good news for me today ;)

I tried to follow your Guide to get into all this but unfortunally it didnt work out for me.
I became kind of stuck on point 4 of your suggested workflow in the Readme.txt.
I have all the ".nut" files ready in clear text by using the "Massdecompiler.bat" and for a simple testing purpose i just wanted to creat a modiefied "knife.nut" and change the description of the knife just as you have stated it in your example.
I modified the knife.nut file and placed it in my installation folder that looks now like this:

<InstallationFolder>\Data\scripts\items\weapons\knife.nut
The actually file that i used for the test is this:

https://nofile.io/f/jPU3UFEfo1r/knife.nut

Problem is, when i start a new game, im stuck in the loading screen. Waited 10min+ but nothing. No error massege visible. It just wont load the game.

I tried a different approach by packing the folder into an zip archive named data_005.dat but in this state the game wont even launch at all.

Im running the 1.2.0.25 GOG version of the game with Beats&Exploration DLC installed if this matters but i think i clearly miss somthing here.

Any kind of help would be much appriciated.
im desperete to get starting on that stuff. ;)

But for now saying Thx again and keep up the great work !
an anonymous Julian
RE: Problem with modifying item Script 2019-01-22 08:56AM
Interesting. Looks like another bug in NutCracker. I will file another bug against it. My guess is that the problem is caused by the way numbers are rendered in your culture. Instead of 32.0 the number appears like "32,00000000.0". That's not valid Squirrel code, of course.

If the NutCracker author doesn't fix it soon, I will fix it myself.
RE: Problem with modifying item Script 2019-01-22 10:24AM
Hi Julian, I tried fixing the problem in NutCracker. Please download version 3 of the mod kit and give it a try.
RE: Thank You 2019-01-22 10:26AM
Hi Rule, I think the log statements go into My Documents\Battle Brothers\log.html
Some thoughts and questions on modding scripts 2019-01-22 10:53PM
I guess one way to get along with the script loading order issue (mentioned in "Coding guidelines for better mods") is to write everything in dlc\dlc_unhold_supporter.nut. This file is located in data_005.dat and I guess it shall be loaded by the engine after all other scripts are finished.

Also I have some questions: lots of scripts start with a list (or tuple?) named "m" to define some constants. For example:

this.asset_manager <- {
m = {
Stash = null,
OverflowItems = [],
CampaignID = 0,
Name = "Battle Brothers",
Banner = "banner_01",
BannerID = 1,
Look = 1,
...

How can I modify those values other than making a copy of the entire file?

Thanks a lot for your time and critical insights.
an anonymous Liaoran
RE: Some thoughts and questions on modding scripts 2019-01-23 02:48AM
Hi Liaoran, the problem with script load order is that I think many scripts are loaded on demand the first time they are accessed. E.g. the first time a character gets spawned with a warbow, that might be when the warbow script gets loaded. So it's not a fixed order.

As for how to modify them without making a copy of the entire file, see my example for the knife above. If you can get it to load at the right time, you can modify them without copying the file.
RE: Some thoughts and questions on modding scripts 2019-01-23 02:50AM
Specifically, you'd make a script like like:
this.asset_manager.m.Name = "Battle Dudes"";
Compile and decompile 2019-01-23 04:35AM
Hi Adam,

When I compile an edited .nut file and then decompile the resulting .cnut file I notice there are a few issues. Besides the floating point one, it throws away iterator increments

- for( local i = 0; i != this.Const.Attributes.COUNT; i = ++i )
+ for( local i = 0; i != this.Const.Attributes.COUNT; i = i )^M
an anonymous Rule
RE: compile and decompile 2019-01-23 05:07AM
To be more specific, in the for loop constructor it does move the "++"/"--" increment to the end of the loop. But variables that are incremented/decremented are lost.
an anonymous Rule
RE: RE: Problem with modifying item Script 2019-01-23 06:53AM
Hello and thank you for your quick replie on that matter Adam,

im not that much of a coder myself but i think i see what the problem was here.

im from germany and this is absolutly not the first time i ran into problems with the "," and "." seperators when it comes to modding stuff so thanks for your explanation.

I also testet your Version 3 of the ModKit and as far as i can tell everything works like a charm now ;)

Guess i can now stop to hunt down and correct those floats manually in every script i wish to modify. That makes life a little easier over here :)
an anonymous Julian
Dev response 2019-01-23 09:04AM
Amazing work Adam (and bread!).

You might be interested to see one of the developers positive response to the mod-kit.

https://www.reddit.com/r/BattleBrothers/comments/ainxfh/i_asked_the_battle_brothers_devs_what_they/
an anonymous Al
RE: Compile and decompile 2019-01-23 09:14AM
Hi Rule. Yeah, the NutCracker decompiler isn't that great. I didn't write it, but any bug that changes the logic is a serious issue and I'll try to fix it myself if the NutCracker author doesn't.

Now, is it actually changing the logic or just changing the style? What is "lost" in your example? You said it moves the update to the end of the loop, which might be okay.

for(local i=0; i<10; i++) { body; }
for(local i=0; i<10; i = i) { body; i++; }

These are equivalent, as long as 'body' doesn't have a "break" or "continue" statement in it.

Anyway, please contact me by email and give me an example.
RE: Dev response 2019-01-23 09:51AM
Hi Al, thanks for the link. I did see the developers' response, but I didn't know word had traveled to Reddit. It was interesting to read the threads and comments and I wanted to respond to some of them, but I'll probably never create Reddit account. :-P Anyway, thanks again.
Is it possible to use .js files to hook scripts? 2019-01-23 05:26PM
As we know the .js files are easy to modify. Is it possible to call some functions or modules defined in scripts using those .js files? If so then the load order issue can possibly be solved.
an anonymous Liaoran
RE: Is it possible to use .js files to hook scripts? 2019-01-23 09:31PM
Yes, the JavaScript and Squirrel code can call each other to some degree.
RE: RE: Is it possible to use .js files to hook scripts? 2019-01-23 09:59PM
Then could you please show us a simple example? Personally I'm not an experienced programmer but I think I can learn by seeing how this is realized.
an anonymous Liaoran
Running modkit on Linux? 2019-02-01 09:24AM
Has anyone managed to get this running on Wine or compile it on Linux? I've tried the former but bbsq does nothing when called and gives me no error output with "bbsq -d whatever.cnut", if I instead run "bbsq -d whatever.cnut > whatever.nut" or massdecompile.bat I get "File $filename had the error the index 'std_stream' does not exist." in the case of massdecompile.bat this refers to tmp.cnut. Attempting to use nutcracker gives me "Error: Accessing non valid stack variable.".

Any help or should I resign myself to the fact an unofficial modkit for a Windows only game isn't going to be running on Linux?
an anonymous Faeren
RE: Running modkit on "Linux"? 2019-02-01 01:46PM
Hi Faerun, I assume by "Linux" you mean the GNU operating system (AKA "GNU/Linux"). Anyway, it sounds like you've gotten them running to me, since the error messages are normal. But the usage is incorrect.

"bbsq -d whatever.cnut" doesn't do nothing. It decrypts the file. It simply doesn't print anything in the success case. "Error: accessing not valid stack variable" from NutCracker indicates that the .cnut file is corrupt, either because you didn't decrypt it, or because you decrypted it multiple times, or something like that.

Try starting with a fresh .cnut file taken from the game's data file, and run these commands:
bbsq -d foo.cnut
nutcracker foo.cnut
It should work.

As for getting the mod kit running natively on GNU, it is entirely possible. The source code is included, but you'll have to compile it. bbsq and sq should compile without much difficulty. Follow the instructions from the Squirrel project. bbrusher is a .NET executable so you can run it directly under Mono without recompilation. The two .bat files could be replaced with bash scripts.
Hi 2019-02-05 03:10PM
Hi Adam, if not too much trouble could you upload your mods here? I don't have an account with nexusmods, thanks
an anonymous elderly gamer
RE: Hi 2019-02-09 07:49PM
Hi, elderly gamer. I'll note that a NexusMods account is free, but I don't mind hosting my mods on my website as well. That said, I will probably wait until they've stabilized before uploading them here, because it's not an easy thing to do and I only want to do it once per mod. :-)
Question for the modding script hook 2019-02-13 08:27PM
I am trying to modify the "tryout_talent" mod using mod script hook. This mod requires adding a new function (getHiringTalents) to player.nut. Here is what I did:

::mods_hookNewObject("entity/tactical/player", function(o) {
o.getHiringTalents = function()
//copy the code from original mod
});

::mods_hookNewObjectOnce("ui/global/data_helper", function(o) {
o.convertEntityHireInformationToUIData = function( _entity )
//copy the code from original mod
});

However the games says: Script Error
the index 'getHiringTalents' does not exist

Could you tell me what's the problems in my code? Thanks a lot.
an anonymous Liaoran
RE: Question for the modding script hook 2019-02-14 07:18PM
A bit off topic, but the problem is that the 'player' class in the base game does not have a getHiringTalents function. If you're trying to add a new function, you need to use the <- syntax, as in o.getHiringTalents <- function() { ... }

Add a comment

Note: The information you enter (including your name and email address) will be displayed publicly.




Line breaks are converted to <br>'s, and all HTML tags except b, u, i, tt, and pre are filtered out.