This release marks true unified support for Alpha, Beta, and Anvil maps. Along with it comes several breaking changes throughout the API, including renamed classes to better differentiate the different map types. There are likely to be a few other small breaking changes in the future as unique aspects of Anvil maps are further fleshed out (like better biome support).
Alright. So I've finished college (my AA at least) so I get a whole 3 months to relax and get back into programming.
As such, I have begun a total rewrite of my toolset, this time using functions for cleaner code.
I am attempting to put the following code into a function:
static void replace_grass(ChunkRef chunk)
{
int xdim = chunk.Blocks.XDim;
int ydim = chunk.Blocks.YDim;
int zdim = chunk.Blocks.ZDim;
//Replace Grass Blocks with Sand
Console.WriteLine("Removing Grass");
for (int x = 0; x < xdim; x++)
{
for (int z = 0; z < zdim; z++)
{
for (int y = 0; y < ydim; y++)
{
// Replace the block with after if it matches before
if (chunk.Blocks.GetID(x, y, z) == 2)
{
chunk.Blocks.SetData(x, y, z, 0);
chunk.Blocks.SetID(x, y, z, 12);
}
}
}
}
}
Of course, I keep getting errors telling me the "chunk" is undefined, but I am unsure of what "chunk" actually is and how I can define it within my function. My first thought was to pass "chunk" into the function, but I have no clue of what identifier to use with it.
C# is complicated.
Any suggestions?
EDIT: Heh. The experienced programmer (which I am, just not in the C languages) knows that the best way to solve a problem is to keep trying random things until it works. I have updated my code snipped above to show the correct code. Thanks, anyways, you guys. :3
Okay. So I've got my code all cleaned up and readable. Now I want to add some new features.
How do I change the biome data? For example, I want to change the entire map to a desert biome.
Is this supported by the Substrate SDK? I am using the latest release.
Rollback Post to RevisionRollBack
The Internet is a big place, friend. I've been places you've n͍̺e̩v̦e̦̰͍͓̩ͅr̜̭̝̬̬͉̤̬ ͙ịm̖͇a͍͇̤͙̥g̤̘i͔͖̤̼̪̬n͖͔̳̬̯e̩̘ḓ͈͔̠̙͇̼̯.͎
It's not currently well-supported, but I think it can still be done. Here's the basic steps:
- Every ChunkRef has a GetChunkRef() method, which returns an IChunk object (and simultaneously detaches the chunk from the chunkref, so any change you make to one won't be reflected in the other).
- For Anvil worlds, the IChunk can be cast to an AnvilChunk object.
- AnvilChunk objects expose a 'Tree' property, which returns an NbtTree representing the raw underlying chunk data.
- You can directly access the Biome byte array through the Tree property. Any changes are automatically reflected in the chunk.
- Use a ZXByteArray to make the biome data easier to access.
- Reassign the AnvilChunk object back to the ChunkRef using SetChunkRef(), so that it can be saved.
Thank you! I'll be trying that out right now.
This is grand because I just started an arctic converter, but ice and snow don't work so well across biomes. :/
Rollback Post to RevisionRollBack
The Internet is a big place, friend. I've been places you've n͍̺e̩v̦e̦̰͍͓̩ͅr̜̭̝̬̬͉̤̬ ͙ịm̖͇a͍͇̤͙̥g̤̘i͔͖̤̼̪̬n͖͔̳̬̯e̩̘ḓ͈͔̠̙͇̼̯.͎
It doesn't recognize "NbtByteAttay" at all and says that "chunkref" does not exist in the current context.
Sorry I'm so slow with this stuff. Here is my code, stripped down to just the biome code:
namespace Desert_Maker_Anvil
{
class Program
{
static void Main(string[] args)
{
string dest = "test";
if (args.Length != 1)
{
Console.WriteLine("Usage: Desert_Maker_Anvil <world>\nUsing default values, opening world \"test\"");
}
else
{
dest = args[0];
}
// Open our world
NbtWorld world = NbtWorld.Open(dest);
IChunkManager cm = world.GetChunkManager();
foreach (ChunkRef chunk in cm)
{
chunk.Blocks.AutoFluid = true;
//EXPERIMENTAL BIOME EDITING
AnvilChunk anvil_chunk = chunkref.GetChunkRef() as AnvilChunk;
NbtByteArray biomeNode = anvil_chunk.Tree.Root["Level"].ToTagCompound()["Biomes"].ToTagByteArray();
ZXByteArray biomeData = new ZXByteArray(16, 16, biomeNode.Data);
biomeData[x, y] = BiomeType.Taiga;
chunkref.SetChunkRef(anvil_chunk);
// Save the chunk
cm.Save();
}
}
}
}
Rollback Post to RevisionRollBack
The Internet is a big place, friend. I've been places you've n͍̺e̩v̦e̦̰͍͓̩ͅr̜̭̝̬̬͉̤̬ ͙ịm̖͇a͍͇̤͙̥g̤̘i͔͖̤̼̪̬n͖͔̳̬̯e̩̘ḓ͈͔̠̙͇̼̯.͎
"chunkref" referred to some variable containing an instance of a ChunkRef object, such as the one you create in your for-loop. Since it was just a snippet, I did not pre-declare all of the variables. "x" and "y" are also arbitrary variables for coordinate values in the range [0, 15].
NbtByteArray should have actually been TagNodeByteArray, so that's my bad. You'll need a using statement to include the Substrate.Nbt namespace if you don't have one already.
I knew I would have to declare the variables, but I never got that far. I'm not a complete newb. :3
I will try that just as soon as I finish my arctic tool. I'm going to have fun with biomes! I can't thank you enough for everything, bro. You are a boon to the Minecraft modding community.
Rollback Post to RevisionRollBack
The Internet is a big place, friend. I've been places you've n͍̺e̩v̦e̦̰͍͓̩ͅr̜̭̝̬̬͉̤̬ ͙ịm̖͇a͍͇̤͙̥g̤̘i͔͖̤̼̪̬n͖͔̳̬̯e̩̘ḓ͈͔̠̙͇̼̯.͎
I don't see anything that you could be doing wrong. I've reproduced the crash. For giggles I tried upgrading to the latest weekly build and this is what I got:
This actually tipped me off, it looks like the same coordinate is being used for both X and Z. I've uploaded version 1.3.1 which should now fix that bug.
If you need to handle blocks globally because there is no straightforward way to do your processing on a per-chunk basis, then the obvious answer is to use BlockManager. The Maze Generator example is one place I use BlockManager because dealing with chunks would just be painful.
If you need to handle blocks globally because there is no straightforward way to do your processing on a per-chunk basis, then the obvious answer is to use BlockManager. The Maze Generator example is one place I use BlockManager because dealing with chunks would just be painful.
Ok, Yeh I was meaning for setting blocks and such, thank you.
have a question, I am not familiar with C#, and I know that this api can probably do what I would need it to do, just not sure how to go about doing it. I need to remove any water on the map between 255 and 63, I was using world painter and it made areas of water that are 255 blocks high. The map is useless until I can clean up the water. Any suggestions?
I believe the only change that needs to be made is in the inner-most for-loop, where y ranges from 0 to ydim (max). In your case, it should start at 63. When running the program, you would specify the id for water as the before-block, and the id for air as the after-block.
If I have time, I will try updating my much older tool, NBToolkit, which is a Substrate-based command-line tool that contains much more generalized block-replace abilities suited to these kinds of tasks.
I got a working program, thanks to the code below, it still running after almost 24 hours, and over a billion blocks of water replaced... I havent gone into the world yet to see if its really cleaned it up or if I removed every water block on the map...took me a bit to figure out how to get it to build, once I got it working I was surprised how many blocks were replaced. I put a counter just for sanity sake.
using System;
using Substrate;
using Substrate.Core;
// This example replaces all instances of one block ID with another in a world.
// Substrate will handle all of the lower-level headaches that can pop up, such
// as maintaining correct lighting or replacing TileEntity records for blocks
// that need them.
// For a more advanced Block Replace example, see replace.cs in NBToolkit.
namespace BlockReplace
{
class Program
{
static void Main(string[] args)
{
if (args.Length != 3)
{
Console.WriteLine("Usage: BlockReplace <world> <before-id> <after-id>");
return;
}
string dest = args[0];
int before = Convert.ToInt32(args[1]);
int after = Convert.ToInt32(args[2]);
int total = 0;
// Open our world
NbtWorld world = NbtWorld.Open(dest);
// The chunk manager is more efficient than the block manager for
// this purpose, since we'll inspect every block
IChunkManager cm = world.GetChunkManager();
foreach (ChunkRef chunk in cm)
{
// You could hardcode your dimensions, but maybe some day they
// won't always be 16. Also the CLR is a bit stupid and has
// trouble optimizing repeated calls to Chunk.Blocks.xx, so we
// cache them in locals
int xdim = chunk.Blocks.XDim;
int ydim = chunk.Blocks.YDim;
int zdim = chunk.Blocks.ZDim;
// x, z, y is the most efficient order to scan blocks (not that
// you should care about internal detail)
for (int x = 0; x < xdim; x++)
{
for (int z = 0; z < zdim; z++)
{
for (int y = 63; y < ydim; y++)
{
// Replace the block with after if it matches before
if (chunk.Blocks.GetID(x, y, z) == before)
{
chunk.Blocks.SetData(x, y, z, 0);
chunk.Blocks.SetID(x, y, z, after);
//Do I need to verify its working?
// Console.WriteLine(x+" " + y + " " + z +" " + before +" " + after);
total = total + 1;
}
}
}
}
// Save the chunk
cm.Save();
Console.WriteLine("Processed Chunk {0},{1}", chunk.X, chunk.Z);
Console.WriteLine("cleaned up " + total + " Water blocks");
}
}
}
}
If you can get far enough along to build a C# application in Visual C# Express, use the basic Block Replace example as a starting point: https://github.com/j...lace/Program.cs
I believe the only change that needs to be made is in the inner-most for-loop, where y ranges from 0 to ydim (max). In your case, it should start at 63. When running the program, you would specify the id for water as the before-block, and the id for air as the after-block.
If I have time, I will try updating my much older tool, NBToolkit, which is a Substrate-based command-line tool that contains much more generalized block-replace abilities suited to these kinds of tasks.
Wow, that's a long time. I hope it works correctly on the first run. I often like to test a single region first just to make sure.
I would guess most of your time is being spent on lighting recalculations and printing to the console.
If you only filled a small part of your world with water and it's still doing replacement on every chunk, then it's possible 63 is one or two blocks too low, and you're shaving off a block of ocean everywhere.
I ran the program on a copy of the map so I loaded it while it was cleaning and it looked great a few issues where the water doesn't touch land. Thats a rendering issue not a water issue and with the tools installed on the server its easy to fix, as of 2:00 it is still running and cleaning the water, 63 is the height I set the oceans to so its all good.
What you're doing is somewhat the reverse of the example, but the information is relevant. When you've identified a block as being the right type for a chest, you must fetch its TileEntity, and cast it to a TileEntityChest. You can check if the cast failed by testing if the variable is null afterwards. Once you've made the cast, you'll have an object of type TileEntityChest, which will expose an Items array. Empty slots in the chest will correspond with null slots in the array. Filled slots will contain an Item object that you can query for type, count, etc.
Sorry I'm getting to this late. I've been out of town. Perhaps you've already solved your issue.
By global to local, I'm assuming you're taking a global block coordinate and getting its chunk-local coordinate. You just need to apply a mask to it. Assuming that chunkXSize etc. is the chunk dimension and a power of 2 (16):
The bit math happens to work out for negative numbers too.
For chunk coordinates, you can actually cheat if you assume the height and width to be a power of 2 (16 in this case). You can shift by the log2 of the dimension. log2 of 16 is 4.
int chunkX = global[0] >> 4;
int chunkZ = global[2] >> 4;
If you wanted to be more general then I think what you're doing is correct, except for the absolute value. I think you can just take the mod directly on negative numbers but my memory is fuzzy. If not, you would need to get it above 0 with some multiple of 16.
This release marks true unified support for Alpha, Beta, and Anvil maps. Along with it comes several breaking changes throughout the API, including renamed classes to better differentiate the different map types. There are likely to be a few other small breaking changes in the future as unique aspects of Anvil maps are further fleshed out (like better biome support).
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
As such, I have begun a total rewrite of my toolset, this time using functions for cleaner code.
I am attempting to put the following code into a function:
Of course, I keep getting errors telling me the "chunk" is undefined, but I am unsure of what "chunk" actually is and how I can define it within my function. My first thought was to pass "chunk" into the function, but I have no clue of what identifier to use with it.
C# is complicated.
Any suggestions?
EDIT: Heh. The experienced programmer (which I am, just not in the C languages) knows that the best way to solve a problem is to keep trying random things until it works. I have updated my code snipped above to show the correct code. Thanks, anyways, you guys. :3
How do I change the biome data? For example, I want to change the entire map to a desert biome.
Is this supported by the Substrate SDK? I am using the latest release.
- Every ChunkRef has a GetChunkRef() method, which returns an IChunk object (and simultaneously detaches the chunk from the chunkref, so any change you make to one won't be reflected in the other).
- For Anvil worlds, the IChunk can be cast to an AnvilChunk object.
- AnvilChunk objects expose a 'Tree' property, which returns an NbtTree representing the raw underlying chunk data.
- You can directly access the Biome byte array through the Tree property. Any changes are automatically reflected in the chunk.
- Use a ZXByteArray to make the biome data easier to access.
- Reassign the AnvilChunk object back to the ChunkRef using SetChunkRef(), so that it can be saved.
I haven't tested the above code but it should be close. In a future release, AnvilChunk will at least expose Biome data directly.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
This is grand because I just started an arctic converter, but ice and snow don't work so well across biomes. :/
It doesn't recognize "NbtByteAttay" at all and says that "chunkref" does not exist in the current context.
Sorry I'm so slow with this stuff. Here is my code, stripped down to just the biome code:
NbtByteArray should have actually been TagNodeByteArray, so that's my bad. You'll need a using statement to include the Substrate.Nbt namespace if you don't have one already.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
I will try that just as soon as I finish my arctic tool. I'm going to have fun with biomes! I can't thank you enough for everything, bro. You are a boon to the Minecraft modding community.
Below is the functioning code, just in case anyone else wants to use it:
This actually tipped me off, it looks like the same coordinate is being used for both X and Z. I've uploaded version 1.3.1 which should now fix that bug.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
For placing and such.
If you need to handle blocks globally because there is no straightforward way to do your processing on a per-chunk basis, then the obvious answer is to use BlockManager. The Maze Generator example is one place I use BlockManager because dealing with chunks would just be painful.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
Ok, Yeh I was meaning for setting blocks and such, thank you.
I believe the only change that needs to be made is in the inner-most for-loop, where y ranges from 0 to ydim (max). In your case, it should start at 63. When running the program, you would specify the id for water as the before-block, and the id for air as the after-block.
If I have time, I will try updating my much older tool, NBToolkit, which is a Substrate-based command-line tool that contains much more generalized block-replace abilities suited to these kinds of tasks.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
I would guess most of your time is being spent on lighting recalculations and printing to the console.
If you only filled a small part of your world with water and it's still doing replacement on every chunk, then it's possible 63 is one or two blocks too low, and you're shaving off a block of ocean everywhere.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
What you're doing is somewhat the reverse of the example, but the information is relevant. When you've identified a block as being the right type for a chest, you must fetch its TileEntity, and cast it to a TileEntityChest. You can check if the cast failed by testing if the variable is null afterwards. Once you've made the cast, you'll have an object of type TileEntityChest, which will expose an Items array. Empty slots in the chest will correspond with null slots in the array. Filled slots will contain an Item object that you can query for type, count, etc.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate
By global to local, I'm assuming you're taking a global block coordinate and getting its chunk-local coordinate. You just need to apply a mask to it. Assuming that chunkXSize etc. is the chunk dimension and a power of 2 (16):
The bit math happens to work out for negative numbers too.
For chunk coordinates, you can actually cheat if you assume the height and width to be a power of 2 (16 in this case). You can shift by the log2 of the dimension. log2 of 16 is 4.
If you wanted to be more general then I think what you're doing is correct, except for the absolute value. I think you can just take the mod directly on negative numbers but my memory is fuzzy. If not, you would need to get it above 0 with some multiple of 16.
Mods I Develop: Garden Stuff -- Storage Drawers -- Hunger Strike
Tools I Develop: NBTExplorer -- Substrate