NBTExplorer/NBToolkit/Replace.cs

377 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using NDesk.Options;
using Substrate;
using Substrate.Core;
namespace NBToolkit
{
public class ReplaceOptions : TKOptions, IChunkFilterable, IBlockFilterable
{
private OptionSet _filterOpt = null;
private ChunkFilter _chunkFilter = null;
private BlockFilter _blockFilter = null;
public int? OPT_BEFORE = null;
public int? OPT_AFTER = null;
public int? OPT_DATA = null;
public double? OPT_PROB = null;
// Block coordinate conditions
public int? BL_X_GE = null;
public int? BL_X_LE = null;
public int? BL_Y_GE = null;
public int? BL_Y_LE = null;
public int? BL_Z_GE = null;
public int? BL_Z_LE = null;
// Neighbor conditions
public int? OPT_NEIGHBOR = null;
public int? OPT_NEIGHBOR_SIDE = null;
public int? OPT_NEIGHBOR_E = null;
public int? OPT_NEIGHBOR_W = null;
public int? OPT_NEIGHBOR_N = null;
public int? OPT_NEIGHBOR_S = null;
public int? OPT_NEIGHBOR_T = null;
public int? OPT_NEIGHBOR_B = null;
public ReplaceOptions ()
: base()
{
_filterOpt = new OptionSet()
{
/*{ "b|before=", "Replace instances of block type {ID} with another block type. This option is repeatable.",
v => _includedBlocks.Add(Convert.ToInt32(v) % 256) },*/
{ "a|after=", "Replace the selected blocks with block type {ID}",
v => OPT_AFTER = Convert.ToInt32(v) % 256 },
{ "d|data=", "Set the new block's data value to {VAL} (0-15)",
v => OPT_DATA = Convert.ToInt32(v) % 16 },
/*{ "p|prob=", "Replace any matching block with probability {VAL} (0.0-1.0)",
v => { OPT_PROB = Convert.ToDouble(v, new CultureInfo("en-US"));
OPT_PROB = Math.Max((double)OPT_PROB, 0.0);
OPT_PROB = Math.Min((double)OPT_PROB, 1.0); } },
{ "bxr|BlockXRange=", "Update blocks with X-coord between {0:V1} and {1:V2}, inclusive. V1 or V2 may be left blank.",
(v1, v2) => {
try { BL_X_GE = Convert.ToInt32(v1); } catch (FormatException) { }
try { BL_X_LE = Convert.ToInt32(v2); } catch (FormatException) { }
} },
{ "byr|BlockYRange=", "Update blocks with Y-coord between {0:V1} and {1:V2}, inclusive. V1 or V2 may be left blank",
(v1, v2) => {
try { BL_Y_GE = Convert.ToInt32(v1); } catch (FormatException) { }
try { BL_Y_LE = Convert.ToInt32(v2); } catch (FormatException) { }
} },
{ "bzr|BlockZRange=", "Update blocks with Z-coord between {0:V1} and {1:V2}, inclusive. V1 or V2 may be left blank",
(v1, v2) => {
try { BL_Z_GE = Convert.ToInt32(v1); } catch (FormatException) { }
try { BL_Z_LE = Convert.ToInt32(v2); } catch (FormatException) { }
} },*/
/*{ "nb=", "Update blocks that have block type {ID} as any neighbor",
v => OPT_NEIGHBOR = Convert.ToInt32(v) % 256 },
{ "nbs=", "Update blocks that have block type {ID} as any x/z neighbor",
v => OPT_NEIGHBOR_SIDE = Convert.ToInt32(v) % 256 },
{ "nbxa=", "Update blocks that have block type {ID} as their south neighbor",
v => OPT_NEIGHBOR_S = Convert.ToInt32(v) % 256 },
{ "nbxb=", "Update blocks that have block type {ID} as their north neighbor",
v => OPT_NEIGHBOR_N = Convert.ToInt32(v) % 256 },
{ "nbya=", "Update blocks that have block type {ID} as their top neighbor",
v => OPT_NEIGHBOR_T = Convert.ToInt32(v) % 256 },
{ "nbyb=", "Update blocks that have block type {ID} as their bottom neighbor",
v => OPT_NEIGHBOR_B = Convert.ToInt32(v) % 256 },
{ "nbza=", "Update blocks that have block type {ID} as their west neighbor",
v => OPT_NEIGHBOR_W = Convert.ToInt32(v) % 256 },
{ "nbzb=", "Update blocks that have block type {ID} as their east neighbor",
v => OPT_NEIGHBOR_E = Convert.ToInt32(v) % 256 },*/
};
_chunkFilter = new ChunkFilter();
_blockFilter = new BlockFilter();
}
public ReplaceOptions (string[] args)
: this()
{
Parse(args);
}
public override void Parse (string[] args)
{
base.Parse(args);
_filterOpt.Parse(args);
_chunkFilter.Parse(args);
_blockFilter.Parse(args);
}
public override void PrintUsage ()
{
Console.WriteLine("Usage: nbtoolkit replace -a <id> [options]");
Console.WriteLine();
Console.WriteLine("Options for command 'replace':");
_filterOpt.WriteOptionDescriptions(Console.Out);
Console.WriteLine();
_chunkFilter.PrintUsage();
Console.WriteLine();
_blockFilter.PrintUsage();
Console.WriteLine();
base.PrintUsage();
}
public override void SetDefaults ()
{
base.SetDefaults();
// Check for required parameters
if (OPT_AFTER == null) {
Console.WriteLine("Error: You must specify a replacement Block ID");
Console.WriteLine();
PrintUsage();
throw new TKOptionException();
}
if (_blockFilter.XAboveEq != null) {
int cx = (int)_blockFilter.XAboveEq >> 5;
_chunkFilter.XAboveEq = Math.Max(_chunkFilter.XAboveEq ?? cx, cx);
}
if (_blockFilter.XBelowEq != null) {
int cx = (int)_blockFilter.XBelowEq >> 5;
_chunkFilter.XBelowEq = Math.Min(_chunkFilter.XBelowEq ?? cx, cx);
}
if (_blockFilter.ZAboveEq != null) {
int cx = (int)_blockFilter.ZAboveEq >> 5;
_chunkFilter.ZAboveEq = Math.Max(_chunkFilter.ZAboveEq ?? cx, cx);
}
if (_blockFilter.ZBelowEq != null) {
int cx = (int)_blockFilter.ZBelowEq >> 5;
_chunkFilter.ZBelowEq = Math.Min(_chunkFilter.ZBelowEq ?? cx, cx);
}
}
public IChunkFilter GetChunkFilter ()
{
return _chunkFilter;
}
public IBlockFilter GetBlockFilter ()
{
return _blockFilter;
}
}
public class Replace : TKFilter
{
private ReplaceOptions opt;
private static Random rand = new Random();
private List<BlockKey>[] _sort = new List<BlockKey>[256];
public Replace (ReplaceOptions o)
{
opt = o;
for (int i = 0; i < 256; i++) {
_sort[i] = new List<BlockKey>();
}
}
public override void Run ()
{
NbtWorld world = GetWorld(opt);
IChunkManager cm = world.GetChunkManager(opt.OPT_DIM);
FilteredChunkManager fcm = new FilteredChunkManager(cm, opt.GetChunkFilter());
int affectedChunks = 0;
foreach (ChunkRef chunk in fcm) {
if (opt.OPT_V) {
Console.WriteLine("Processing chunk {0},{1}...", chunk.X, chunk.Z);
}
ApplyChunk(world, chunk);
affectedChunks += fcm.Save() > 0 ? 1 : 0;
}
Console.WriteLine("Affected Chunks: " + affectedChunks);
}
public void ApplyChunk (NbtWorld world, ChunkRef chunk)
{
IBlockFilter opt_b = opt.GetBlockFilter();
int xBase = chunk.X * chunk.Blocks.XDim;
int zBase = chunk.Z * chunk.Blocks.ZDim;
// Determine X range
int xmin = 0;
int xmax = 15;
if (opt_b.XAboveEq != null) {
xmin = (int)opt_b.XAboveEq - xBase;
}
if (opt_b.XBelowEq != null) {
xmax = (int)opt_b.XBelowEq - xBase;
}
xmin = (xmin < 0) ? 0 : xmin;
xmax = (xmax > 15) ? 15 : xmax;
if (xmin > 15 || xmax < 0 || xmin > xmax) {
return;
}
// Determine Y range
int ymin = 0;
int ymax = 127;
if (opt_b.YAboveEq != null) {
ymin = (int)opt_b.YAboveEq;
}
if (opt_b.YBelowEq != null) {
ymax = (int)opt_b.YBelowEq;
}
if (ymin > ymax) {
return;
}
// Determine X range
int zmin = 0;
int zmax = 15;
if (opt_b.ZAboveEq != null) {
zmin = (int)opt_b.ZAboveEq - zBase;
}
if (opt_b.ZBelowEq != null) {
zmax = (int)opt_b.ZBelowEq - zBase;
}
zmin = (zmin < 0) ? 0 : zmin;
zmax = (zmax > 15) ? 15 : zmax;
if (zmin > 15 || zmax < 0 || zmin > zmax) {
return;
}
int xdim = chunk.Blocks.XDim;
int ydim = chunk.Blocks.YDim;
int zdim = chunk.Blocks.ZDim;
// Bin blocks
for (int y = ymin; y <= ymax; y++) {
for (int x = xmin; x <= xmax; x++) {
for (int z = zmin; z <= zmax; z++) {
int id = chunk.Blocks.GetID(x, y, z);
_sort[id].Add(new BlockKey(x, y, z));
}
}
}
// Process bins
for (int i = 0; i < 256; i++) {
if (_sort[i].Count == 0) {
continue;
}
if (opt_b.IncludedBlockCount > 0 & !opt_b.IncludedBlocksContains(i)) {
continue;
}
if (opt_b.ExcludedBlockCount > 0 & opt_b.ExcludedBlocksContains(i)) {
continue;
}
foreach (BlockKey key in _sort[i]) {
chunk.Blocks.SetID(key.x, key.y, key.z, (int)opt.OPT_AFTER);
if (opt.OPT_VV) {
int gx = chunk.X * xdim + key.x;
int gz = chunk.Z * zdim + key.z;
Console.WriteLine("Replaced block {0} at {1},{2},{3}", i, gx, key.y, gz);
}
if (opt.OPT_DATA != null) {
chunk.Blocks.SetData(key.x, key.y, key.z, (int)opt.OPT_DATA);
}
}
}
// Reset bins
for (int i = 0; i < 256; i++) {
_sort[i].Clear();
}
// Process Chunk
/*for (int y = ymin; y <= ymax; y++) {
for (int x = xmin; x <= xmax; x++) {
for (int z = zmin; z <= zmax; z++) {
// Probability test
if (opt_b.ProbMatch != null) {
double c = rand.NextDouble();
if (c > opt_b.ProbMatch) {
continue;
}
}
int lx = x % xdim;
int ly = y % ydim;
int lz = z % zdim;
// Get the old block
int oldBlock = chunk.Blocks.GetID(lx , ly, lz);
// Skip block if it doesn't match the inclusion list
if (opt_b.IncludedBlockCount > 0) {
bool match = false;
foreach (int ib in opt_b.IncludedBlocks) {
if (oldBlock == ib) {
match = true;
break;
}
}
if (!match) {
continue;
}
}
// Skip block if it does match the exclusion list
if (opt_b.ExcludedBlockCount > 0) {
bool match = false;
foreach (int xb in opt_b.ExcludedBlocks) {
if (oldBlock == xb) {
match = true;
break;
}
}
if (match) {
continue;
}
}
// Replace the block
chunk.Blocks.SetID(lx, ly, lz, (int)opt.OPT_AFTER);
if (opt.OPT_VV) {
int gx = chunk.X * xdim + lx;
int gz = chunk.Z * zdim + lz;
Console.WriteLine("Replaced block at {0},{1},{2}", gx, ly, gz);
}
if (opt.OPT_DATA != null) {
chunk.Blocks.SetData(lx, ly, lz, (int)opt.OPT_DATA);
}
}
}
}*/
}
}
}