function roundToChunkIndex( %pos )
{
	%x = mFloor( getWord( %pos, 0 ) / 16 ); // 32x Cube
	%y = mFloor( getWord( %pos, 1 ) / 16 ); // 32x Cube

	return %x SPC %y;
}

function chunkGroup()
{
	if ( !strLen( $MC::CurrWorld ) )
	{
		return -1;
	}

	if ( isObject( %obj = nameToID( "chunkGroup" ) ) )
	{
		return %obj;
	}

	%obj = new scriptGroup( "chunkGroup" );
	%obj.loadWorld();
	%obj.saveWorldTick = %obj.schedule( 0, "saveWorldTick" );

	return %obj;
}

function chunkGroup::loadWorld( %this )
{
	if ( !strLen( $MC::CurrWorld ) )
	{
		return false;
	}

	%path = "config/mc/world/" @ $MC::CurrWorld @ "/world.dat";

	if ( !isFile( %path ) )
	{
		return false;
	}

	%fo = new fileObject();
	%fo.openForWrite( %path );

	while ( !%fo.isEOF() )
	{
		%line = %fo.readLine();

		if ( !strLen( %line ) )
		{
			continue;
		}

		if ( getSubStr( %line, 0, 1 ) $= "#" )
		{
			continue;
		}

		%ploc = strPos( %line, "\t" );

		if ( %ploc < 0 )
		{
			continue;
		}

		%field = getSubStr( %line, 0, %ploc );
		%value = getSubStr( %line, %ploc + 1, strLen( %line ) );

		switch$ ( %field )
		{
			case "name": // currently unused
			case "seed": $MC::CurrSeed = %value;
			case "time": mcTickSO().time = %value % 24000;
		}
	}

	%fo.close();
	%fo.delete();

	return true;
}

function chunkGroup::saveWorldTick( %this )
{
	cancel( %this.saveWorldTick );

	if ( !strLen( $MC::CurrWorld ) )
	{
		return false;
	}

	%path = "config/mc/world/" @ $MC::CurrWorld @ "/world.dat";

	if ( !isWriteableFileName( %path ) )
	{
		return false;
	}

	%fo = new fileObject();
	%fo.openForWrite( %path );

	%fo.writeLine( "name" TAB $MC::CurrWorld );
	%fo.writeLine( "seed" TAB $MC::CurrSeed );
	%fo.writeLine( "time" TAB mcTickSO().time % 24000 );

	%fo.close();
	%fo.delete();

	%this.saveWorldTick = %this.schedule( 120000, "saveWorldTick" );
	return true;
}

function chunkSO( %x, %y, %dim )
{
	if ( !strLen( $MC::CurrWorld ) )
	{
		return -1;
	}

	if ( !strLen( %dim ) )
	{
		%dim = 0;
	}

	%ndst = ( %dim != 0 ? "DIM_" @ %dim @ "_" : "" );

	%name = "chunkSO_" @ %ndst @ %x @ "_" @ %y;

	if ( isObject( %obj = nameToID( %name ) ) )
	{
		return %obj;
	}

	%obj = new scriptObject()
	{
		class = "chunkSO";

		dimension = %dim;
		position = %x SPC %y SPC 0;

		initializing = true;
		index = %x SPC %y;
		blockCount = 0;
	};

	%obj.setName( %name );
	chunkGroup().add( %obj );

	if ( !%obj.loadChunk() )
	{
		%obj.generateChunk();
	}
	else
	{
		%obj.unloadCheckTick();
	}

	return %obj;
}

function chunkSO::unloadCheckTick( %this )
{
	cancel( %this.unloadCheckTick );

	if ( vectorDist( %this.position, "0 0 0" ) <= 1 )
	{
		return;
	}

	%near = false;
	%count = clientGroup.getCount();

	for ( %i = 0 ; %i < %count ; %i++ )
	{
		%cl = clientGroup.getObject( %i );

		if ( !isObject( %pl = %cl.player ) )
		{
			continue;
		}

		if ( %pl.dimension !$= %this.dimension )
		{
			continue;
		}

		if ( vectorDist( %this.position, roundToChunkIndex( %pl.getPosition() ) SPC 0 ) <= 3 )
		{
			%near = true;
			break;
		}
	}

	if ( !%near )
	{
		%this.unloadChunk();
		return;
	}

	%this.unloadCheckTick = %this.schedule( 5000, "unloadCheckTick" );
}

function chunkSO::unloadChunk( %this )
{
	if ( %this.unloading )
	{
		return;
	}

	cancel( %this.loadChunkTick );
	cancel( %this.generateChunkTick );

	%this.unloading = true;
	%this.unloadChunkTick = %this.schedule( 100, "unloadChunkTick", 0 );
}

function chunkSO::unloadChunkTick( %this, %idx )
{
	cancel( %this.unloadChunkTick );

	%quotaUsage = 0;
	%quotaLimit = 32;

	for ( %idx ; %idx < %this.blockCount && %quotaUsage < %quotaLimit ; %idx++ )
	{
		if ( !isObject( %obj = %this.blockObject[ %idx ] ) )
		{
			continue;
		}

		%obj.delete();
		%quotaUsage++;
	}

	if ( %idx >= %this.blockCount )
	{
		%this.schedule( 0, "delete" );
		return;
	}

	%this.unloadChunkTick = %this.schedule( 100, "unloadChunkTick", %idx );
}

function chunkSO::getFilePath( %this )
{
	if ( !strLen( $MC::CurrWorld ) )
	{
		return "";
	}

	%index = strReplace( %this.index, " ", "_" );

	if ( %this.dimension != 0 )
	{
		%dim_string = "/DIM_" @ %this.dimension;
	}

	return "config/mc/world/" @ $MC::CurrWorld @ %dim_string @ "/chunk_" @ %index @ ".chk";
}

function chunkSO::getPosition( %this )
{
	return vectorAdd( vectorScale( %this.position, 16 ), "0 0" SPC 1024 * %this.dimension );
}

function chunkSO::setBlock( %this, %x, %y, %z, %type )
{
	%x = mFloor( %x );
	%y = mFloor( %y );
	%z = mfloor( %z );

	if ( %x < 0 )
	{
		return false;
	}

	if ( %y < 0 )
	{
		return false;
	}

	if ( %z < 0 )
	{
		return false;
	}

	if ( %x > 15 )
	{
		return false;
	}

	if ( %y > 15 )
	{
		return false;
	}

	if ( %z > 255 )
	{
		return false;
	}

	%curr = %this.blockByPos[ %x SPC %y SPC %z ];

	if ( strLen( %curr ) && %curr >= 0 )
	{
		if ( isObject( %obj = %this.blockObject[ %curr ] ) )
		{
			%obj.delete();
		}

		%idx = %curr;
	}
	else
	{
		%idx = %this.blockCount;
		%this.blockCount++;
	}

	%this.blockType[ %idx ] = "";
	%this.blockPos[ %idx ] = "";
	%this.blockByPos[ %x SPC %y SPC %z ] = "";
	%this.blockObject[ %idx ] = "";

	if ( !isObject( %db = "mc_blockData_" @ %type ) )
	{
		%this.reflowBlockArray( %idx );
		return true;
	}

	%this.generatedPoint[ %x, %y, %z ] = true;
	%this.blockType[ %idx ] = %type;
	%this.blockPos[ %idx ] = %x SPC %y SPC %z;
	%this.blockByPos[ %x SPC %y SPC %z ] = %idx;

	%rpos = vectorAdd( %this.getPosition(), %x SPC %y SPC %z );
	%rpos = vectorAdd( %rpos, "0 0" SPC $MC::GlobalBlockOffsetZ );

	if ( strLen( %db.mcBlockOffsetZ ) )
	{
		%rpos = vectorAdd( %rpos, "0 0" SPC %db.mcBlockOffsetZ );
	}
	if(%type $= "veggrass")
		%col = 18;
	else
		%col = 0;
	%obj = new fxDTSBrick()
	{
		position = %rpos;
		dataBlock = %db;
		colorId = %col;
		// printId = $printNameTable[ "2x2f/" @ ( strLen( %db.printUI ) ? %db.printUI : "MCNone" ) ];
		// colorFxId = %db.colorFxId;
		// shapeFxId = %db.shapeFxId;
		isPlanted = true;

		chunkSO = %this;
		indexInChunk = %idx;
	};

	%obj.setTrusted( true );
	BrickGroup_888888.add( %obj );
	%obj.plant();

	%this.blockObject[ %idx ] = %obj;

	if ( isFunction( %func = "block_" @ %type @ "_onPlant" ) )
	{
		call( %func, %obj, %this );
	}

	if ( !%this.initializing )
	{
		%this.saveChunk();
	}

	return true;
}

function chunkSO::getBlock( %this, %x, %y, %z )
{
	return %this.blockType[ %this.blockByPos[ %x SPC %y SPC %z ] ];
}

function chunkSO::reflowBlockArray( %this, %index )
{
	if ( !strLen( %index ) || %index < 0 || %index >= %this.blockCount )
	{
		return false;
	}

	for ( %i = %index ; %i < %this.blockCount ; %i++ )
	{
		%this.blockType[ %i ] = %this.blockType[ %i + 1 ];
		%this.blockPos[ %i ] = %this.blockPos[ %i + 1 ];

		%px = getWord( %this.blockPos[ %i ], 0 );
		%py = getWord( %this.blockPos[ %i ], 1 );
		%pz = getWord( %this.blockPos[ %i ], 2 );

		%this.blockByPos[ %px SPC %py SPC %pz ] = %i;
		%this.blockObject[ %i ] = %this.blockObject[ %i + 1 ];

		if ( isObject( %obj = %this.blockObject[ %i ] ) )
		{
			%obj.indexInChunk = %i;
		}
	}

	%this.blockCount--;
	%this.blockType[ %this.blockCount ] = "";
	%this.blockObject[ %this.blockCount ] = "";
	%this.blockPos[ %this.blockCount ] = "";

	return true;
}
