This tutorial was something that I worked out today while working on my mod. I was doing some structure generation, and I wanted a structure that would only be generated once per world.
This will allow you to save any world-global data into your own nbt file (stored with the world), and will not require any modification of base classes. All work is actually done inside your mod_***.java file. The nice part about this as well, is you can schedule reads and writes to and from the NBT file you are using whenever you need to. I haven't found a way to detect the game closing, so I recommend scheduling a write after each change to ensure it persists in the event of a crash.
The following will explain in detail everything you need to do in order to make this work, as well as why it works. For the impatient ones out there, just scroll to the bottom to find the full sample code.
There are 8 functions required to make this work. Below is the detailed explanation:
Working with the flags:
First, you need to tell ModLoader to enable the onTickInGame function call. That is done by putting the following inside your mod_*** constructor:
The variable in there is for later use. It is defined at the top of your class as follows:
private static String lastWorldName;
The reason for this is that your mod is not reloaded when changing worlds; minecraft will keep the mod in memory, so we need a way to determine what world we are working with, and whether or not we need to load a different NBT file.
Once you have that done, you need to create a variable to hold the state information, that is, whether or not a read and/or write has been scheduled. That variable is defined right under the one we just made, like so:
private short DirtyFlag = 0;
Now it's time to write the functions to manipulate this flag. There are six in total.
The first tells the mod that there is a pending read.
private void SetPendingRead(){
DirtyFlag |= 1;
}
The operation being performed here is called a bitwise OR. It will set the first bit (in this case) of the specified variable to 1.
The second function tells the mod that there is a pending write.
private void SetPendingWrite(){
DirtyFlag |= 2;
}
The reason that we use the number 1 for read and the number 2 for write is because when converted to binary, these numbers represent 2 separate bits in the number (0000 0001 and 0000 0010, respectively).
The third and fourth functions are to clear the read/write flag. These functions will be called after the scheduled read or write has been completed.
What these do is perform an AND on the inverse value of the right hand number. The invert is represented by the ~ symbol.
Inverse Explanation:
If you take the inverse of the number, you are reversing the each bit in that number's binary representation. For example, if you have the number 2 (0000 0010 in binary) and you invert it, it becomes 1111 1101.
Basically, every 0 in the binary representation becomes a 1, and every 1 becomes a 0. When combined with bitwise AND/OR, this can be used to set and clear bits in a variable.
So we now have a way to tell the mod that there is a read or write pending, and the mod can clear that status once it has completed the read/write. Now we need a way to check whether or not those values are set. Queue the next 2 functions:
These two functions use a bitwise AND to check if the specified flag is set.
Bitwise AND/OR explanation:
If you have a variable, let's say of type char, then you are working with 8 bits of data (16 in java, but for explanation sake, we will keep it simple. The principles don't change), or 0000 0000 (in binary - one for each bit of data).
When you perform a bitwise OR operation, the original value's bits are compared one by one, and if either of them is equal to one (source OR destination, get it?), then the resulting value will have that bit set to 1.
As an example:
A character with value 128 = 1000 0000 (binary)
If we OR that with the number 2 (0000 0010 binary), the equation will work like so:
As you can see, the bits are compared in order, and if either one is set to 1, the resulting bit is 1.
With an AND operation, it is almost the same. The difference is that instead of either one of the bits needing to be 1, BOTH of the values need to be 1. Using our examples from above, we get completely different results:
When working with bitwise flags, it is very important that you know what bits you want to check, set, or clear. You don't want to accidentally overwrite a bit meant for a different value! The simplest method is to stick to powers of 2 (1,2,4,8,16,32,64,etc.) - this will ensure that each value only affects one bit in your variable.
Alright, moving right along! Now that we have the means to set, check, and clear the read/write status, we need to actually do the read and write!
this last bit of code will place the values into the NBT compound, and then write the data to the file. Once done, it closes the file as we are done with it.
the try/catch block is there to handle any errors we may encounter while trying to read from the file.
Reading from the NBT File:
Now that we're writing to the file, we need to be able to read it back in!
This part looks for the .dat file under the current world's directory on your computer, and exits out of the function if the file isn't there.
FileInputStream fileinputstream = new FileInputStream(file);
NBTTagCompound nbttagcompound = CompressedStreamTools.readCompressed(fileinputstream);
Here we open the file, and read the tag compound.
if (nbttagcompound.hasKey("my_value_a")){
this.my_value_a = nbttagcompound.getBoolean("my_value_a");
}
if (nbttagcompound.hasKey("my_value_b")){
this.my_value_b = nbttagcompound.getInteger("my_value_b");
}
fileinputstream.close();
This part checks the data we read for specific keys, and reads the values from them if they are present. Once it has checked for all the keys we were looking for, it closes the file.
Again, the try/catch block is there to handle any errors we may encounter while trying to read from the file.
Setting up the tick in game:
Alright, so now we have all our utility functions in place, now we just need to set up the tick in game function to handle it all. Add the following into your onTickInGame function in your mod_whatever.java
Falls together pretty nicely, doesn't it? This part is more or less self-explanatory, just read it through.
One thing to note, for this to work, your onTickInGame function MUST return true, or it will not be called again. So make sure that is happening!
So that's it (or welcome back, if you scrolled right past everything else). Below is a sample of what the final source should look like:
Couple of things to note:
Make sure the file name that you are reading from and writing to are the same name.
If you want to manage more than one file, you will need to tweak the flag functions to store more than just the 2 bits that it is working with now.
And that's it!
Let me know if you have any questions, I'll try to help as best as I can.
Will this work for 1.3.2? i'm planning on making a mod in which a user can define an item's texture in the NBT files and I have most of it down I just want to double check so I don't spend weeks doing something that won't work.
Will this work for 1.3.2? i'm planning on making a mod in which a user can define an item's texture in the NBT files and I have most of it down I just want to double check so I don't spend weeks doing something that won't work.
Most of the concepts work, you'll just need to tweak the part where you get the world name, as this won't work on a server.
This tutorial was something that I worked out today while working on my mod. I was doing some structure generation, and I wanted a structure that would only be generated once per world.
This will allow you to save any world-global data into your own nbt file (stored with the world), and will not require any modification of base classes. All work is actually done inside your mod_***.java file. The nice part about this as well, is you can schedule reads and writes to and from the NBT file you are using whenever you need to. I haven't found a way to detect the game closing, so I recommend scheduling a write after each change to ensure it persists in the event of a crash.
The following will explain in detail everything you need to do in order to make this work, as well as why it works. For the impatient ones out there, just scroll to the bottom to find the full sample code.
There are 8 functions required to make this work. Below is the detailed explanation:
Working with the flags:
First, you need to tell ModLoader to enable the onTickInGame function call. That is done by putting the following inside your mod_*** constructor:
The variable in there is for later use. It is defined at the top of your class as follows:
The reason for this is that your mod is not reloaded when changing worlds; minecraft will keep the mod in memory, so we need a way to determine what world we are working with, and whether or not we need to load a different NBT file.
Once you have that done, you need to create a variable to hold the state information, that is, whether or not a read and/or write has been scheduled. That variable is defined right under the one we just made, like so:
Now it's time to write the functions to manipulate this flag. There are six in total.
The first tells the mod that there is a pending read.
The operation being performed here is called a bitwise OR. It will set the first bit (in this case) of the specified variable to 1.
The second function tells the mod that there is a pending write.
The reason that we use the number 1 for read and the number 2 for write is because when converted to binary, these numbers represent 2 separate bits in the number (0000 0001 and 0000 0010, respectively).
The third and fourth functions are to clear the read/write flag. These functions will be called after the scheduled read or write has been completed.
and
What these do is perform an AND on the inverse value of the right hand number. The invert is represented by the ~ symbol.
Inverse Explanation:
If you take the inverse of the number, you are reversing the each bit in that number's binary representation. For example, if you have the number 2 (0000 0010 in binary) and you invert it, it becomes 1111 1101.
Basically, every 0 in the binary representation becomes a 1, and every 1 becomes a 0. When combined with bitwise AND/OR, this can be used to set and clear bits in a variable.
So we now have a way to tell the mod that there is a read or write pending, and the mod can clear that status once it has completed the read/write. Now we need a way to check whether or not those values are set. Queue the next 2 functions:
and
These two functions use a bitwise AND to check if the specified flag is set.
Bitwise AND/OR explanation:
If you have a variable, let's say of type char, then you are working with 8 bits of data (16 in java, but for explanation sake, we will keep it simple. The principles don't change), or 0000 0000 (in binary - one for each bit of data).
When you perform a bitwise OR operation, the original value's bits are compared one by one, and if either of them is equal to one (source OR destination, get it?), then the resulting value will have that bit set to 1.
As an example:
A character with value 128 = 1000 0000 (binary)
If we OR that with the number 2 (0000 0010 binary), the equation will work like so:
Another example, performing a bitwise OR operation on the number 129 with the number 1:
As you can see, the bits are compared in order, and if either one is set to 1, the resulting bit is 1.
With an AND operation, it is almost the same. The difference is that instead of either one of the bits needing to be 1, BOTH of the values need to be 1. Using our examples from above, we get completely different results:
You can read this wikipedia article for more information on bitwise operations.
When working with bitwise flags, it is very important that you know what bits you want to check, set, or clear. You don't want to accidentally overwrite a bit meant for a different value! The simplest method is to stick to powers of 2 (1,2,4,8,16,32,64,etc.) - this will ensure that each value only affects one bit in your variable.
Alright, moving right along! Now that we have the means to set, check, and clear the read/write status, we need to actually do the read and write!
Writing to the NBT File:
The write is done with the following function:
Alright, let's take a quick look at how the function works:
This bit of code will check the minecraft saves folder on your computer for the file we are trying to use. If it doesn't already exist, it is created.
This opens the file for reading, now that we're sure it exists, and instantiates a new instance of the NBTCompound class for us to work with.
this last bit of code will place the values into the NBT compound, and then write the data to the file. Once done, it closes the file as we are done with it.
the try/catch block is there to handle any errors we may encounter while trying to read from the file.
Reading from the NBT File:
Now that we're writing to the file, we need to be able to read it back in!
The read is done with the following function:
Alright, that's a fair bit of code - it's not too bad though. Let's go through it section by section:
This part looks for the .dat file under the current world's directory on your computer, and exits out of the function if the file isn't there.
Here we open the file, and read the tag compound.
This part checks the data we read for specific keys, and reads the values from them if they are present. Once it has checked for all the keys we were looking for, it closes the file.
Again, the try/catch block is there to handle any errors we may encounter while trying to read from the file.
Setting up the tick in game:
Alright, so now we have all our utility functions in place, now we just need to set up the tick in game function to handle it all. Add the following into your onTickInGame function in your mod_whatever.java
Falls together pretty nicely, doesn't it? This part is more or less self-explanatory, just read it through.
One thing to note, for this to work, your onTickInGame function MUST return true, or it will not be called again. So make sure that is happening!
So that's it (or welcome back, if you scrolled right past everything else). Below is a sample of what the final source should look like:
Couple of things to note:
Make sure the file name that you are reading from and writing to are the same name.
If you want to manage more than one file, you will need to tweak the flag functions to store more than just the 2 bits that it is working with now.
And that's it!
Let me know if you have any questions, I'll try to help as best as I can.
Cheers!
Most of the concepts work, you'll just need to tweak the part where you get the world name, as this won't work on a server.