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

	return new scriptGroup( "chunkGroup" );
}

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

	return new scriptObject( "chunkLoaderSO" )
	{
		queue = "";
		quota = 32;
		rate = 16;
	};
}

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

	return new scriptObject( "chunkUnloaderSO" )
	{
		queue = "";
		quota = 32;
		rate = 16;
	};
}

function chunkLoaderSO::tick( %this )
{
	cancel( %this.tick );

	if ( $MC::Clearing )
	{
		%this.queue = "";
		return;
	}

	%quotaUsage = 0;
	%quotaLimit = %this.quota;

	while( %quotaUsage < %quotaLimit && !%this.isEmpty() )
	{
		%value = trim( %this.getFront() );

		if ( !strLen( %value ) )
		{
			return;
		}

		%chunk = getWord( %value, 0 );
		%index = getWord( %value, 1 );

		if ( !strLen( %index ) || %index < 0 || %index >= %chunk.blockCount )
		{
			%chunk.loadMobs();
			%chunk.unloadChunkTick = %chunk.schedule( $MC::ChunkTick::Rate::Unload, "unloadChunkTick" );
			%chunk.loaded = true;
			%this.popFront();
			continue;
		}

		for ( %index ; %index < %chunk.blockCount && %quotaUsage < %quotaLimit ; %index++ )
		{
			%chunk.loadBlock( %index );
			%quotaUsage++;
		}

		%this.chunkIndex[ %chunk ] = %index;

		if ( %index >= %chunk.blockCount )
		{
			%chunk.loadMobs();
			%chunk.unloadChunkTick = %chunk.schedule( $MC::ChunkTick::Rate::Unload, "unloadChunkTick" );
			%chunk.loaded = true;
			%this.popFront();
		}
		else
		{
			%this.queue = setField( %this.queue, 0, %chunk SPC %index );
		}
	}

	if ( %this.isEmpty() )
	{
		return;
	}

	%this.tick = %this.schedule( %this.rate, "tick" );
}

function chunkUnloaderSO::tick( %this )
{
	cancel( %this.tick );

	if ( $MC::Clearing )
	{
		%this.queue = "";
		return;
	}

	%quotaUsage = 0;
	%quotaLimit = %this.quota;

	while( %quotaUsage < %quotaLimit && !%this.isEmpty() )
	{
		%value = trim( %this.getFront() );

		if ( !strLen( %value ) )
		{
			return;
		}

		%chunk = getWord( %value, 0 );
		%index = getWord( %value, 1 );

		if ( !strLen( %index ) || %index < 0 || %index >= %chunk.blockCount )
		{
			%chunk.loaded = false;
			%this.popFront();
			continue;
		}

		for ( %index ; %index < %chunk.blockCount && %quotaUsage < %quotaLimit ; %index++ )
		{
			%chunk.unloadBlock( %index );
			%quotaUsage++;
		}

		%this.chunkIndex[ %chunk ] = %index;

		if ( %index >= %chunk.blockCount )
		{
			%chunk.loaded = false;
			%this.popFront();
		}
		else
		{
			%this.queue = setField( %this.queue, 0, %chunk SPC %index );
		}
	}

	if ( %this.isEmpty() )
	{
		return;
	}

	%this.tick = %this.schedule( %this.rate, "tick" );
}

function chunkLoaderSO::isEmpty( %this )
{
	return ( !strLen( %this.queue ) || !getFieldCount( %this.queue ) );
}

function chunkLoaderSO::getFront( %this )
{
	return getField( %this.queue, 0 );
}

function chunkLoaderSO::popFront( %this )
{
	%pos = strPos( %this.queue, "\t" );

	if ( %pos >= 0 )
	{
		%this.queue = trim( getSubStr( %this.queue, %pos + 1, strLen( %this.queue ) ) );
	}
	else
	{
		%this.queue = "";
	}
}

function chunkLoaderSO::pushBack( %this, %chunk )
{
	%this.queue = %this.queue @ ( strLen( %this.queue ) ? "\t" : "" ) @ %chunk SPC 0;
	%this.chunkIndex[ %chunk ] = 0;

	if ( !isEventPending( %this.tick ) )
	{
		%this.tick = %this.schedule( %this.rate, "tick" );
	}
}

function chunkLoaderSO::hasChunk( %this, %chunk )
{
	%count = getFieldCount( %this.queue );

	for ( %i = 0 ; %i < %count ; %i++ )
	{
		if ( getWord( getField( %this.queue, %i ), 0 ) $= %chunk )
		{
			return true;
		}
	}

	return false;
}

function chunkLoaderSO::popChunk( %this, %chunk )
{
	for ( %i = 0 ; %i < %count ; %i++ )
	{
		%field = getField( %this.queue, %i );
		%fchnk = getWord( %field, 0 );

		if ( %fchnk $= %chunk )
		{
			%pos = strPos( %this.queue, %field );
			%pre = getSubStr( %this.queue, 0, %pos );
			%suf = getSubStr( %this.queue, %pos + strLen( %field ) + 1, strLen( %this.queue ) );
			%this.queue = %pre @ %suf;

			return true;
		}
	}

	return false;
}

function chunkUnloaderSO::isEmpty(%t){return chunkLoaderSO::isEmpty(%t);}
function chunkUnloaderSO::getFront(%t){return chunkLoaderSO::getFront(%t);}
function chunkUnloaderSO::popFront(%t){chunkLoaderSO::popFront(%t);}
function chunkUnloaderSO::pushBack(%t,%c){%c.unloadMobs();chunkLoaderSO::pushBack(%t,%c);}
function chunkUnloaderSO::hasChunk(%t,%c){chunkLoaderSO::hasChunk(%t,%c);}
function chunkUnloaderSO::popChunk(%t,%c){chunkLoaderSO::popChunk(%t,%c);}

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

	return %x SPC %y;
}

function chunkSO( %x, %y )
{
	%name = "chunkSO_" @ %x @ "_" @ %y;

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

	%obj = new scriptObject()
	{
		class = "chunkSO";
		position = %x SPC %y SPC 0;

		loaded = false;
		blockCount = 0;
	};

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

	return %obj;
}

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

	if ( !%this.loaded )
	{
		return;
	}

	if ( vectorDist( "0 0 0", %this.getPosition() ) < 3 )
	{
		return; // Never unload the spawn.
	}

	%cnt = clientGroup.getCount();
	%pos = %this.position;

	%hasNear = false;

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

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

		%ppos = roundToChunkIndex( %pl.getPosition() );

		if ( vectorDist( %pos, %ppos ) <= 6 )
		{
			%hasNear = true;
			break;
		}
	}

	if ( !%hasNear )
	{
		%this.unloadBlocks();
		return;
	}

	%this.unloadChunkTick = %this.schedule( $MC::ChunkTick::Rate::Unload, "unloadChunkTick" );
}

function chunkSO::loadBlocks( %this )
{
	if ( %this.loaded )
	{
		return false;
	}

	if ( chunkLoaderSO().hasChunk( %this ) )
	{
		return false;
	}

	if ( chunkUnloaderSO().hasChunk( %this ) )
	{
		chunkUnloaderSO().popChunk( %this );
	}

	chunkLoaderSO().pushBack( %this );
}

function chunkSO::unloadBlocks( %this )
{
	if ( !%this.loaded )
	{
		return false;
	}

	if ( chunkUnloaderSO().hasChunk( %this ) )
	{
		return false;
	}

	if ( chunkLoaderSO().hasChunk( %this ) )
	{
		chunkLoaderSO().popChunk( %this );
	}

	cancel( %this.unloadChunkTick );
	chunkUnloaderSO().pushBack( %this );
}

function chunkSO::getPosition( %this )
{
	return vectorScale( %this.position, 16 );
}

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 )
	{
		%this.removeBlock( %curr );
	}

	if ( !isObject( "mc_blockData_" @ %type ) )
	{
		return true;
	}

	%id = %this.blockCount;
	%this.blockCount++;

	%this.blockType[ %id ] = %type;
	%this.blockPos[ %id ] = %x SPC %y SPC %z;
	%this.blockByPos[ %x SPC %y SPC %z ] = %id;

	if ( %this.loaded )
	{
		%obj = %this.loadBlock( %id );
	}

	return true;
}

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

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

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

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

			%this.blockPos[ %i ] = "";
			%this.blockByPos[ %px, %py, %pz ] = "";

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

			%this.blockObject[ %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, %py, %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;
}

function chunkSO::loadBlock( %this, %idx )
{
	if ( %idx < 0 || %idx >= %this.blockCount )
	{
		return -1;
	}

	if ( isObject( %obj = %this.blockObject[ %idx ] ) )
	{
		return %obj;
	}

	if ( !isObject( %db = "mc_blockData_" @ %this.blockType[ %idx ] ) )
	{
		return -1;
	}

	%rpos = vectorAdd( %this.getPosition(), %this.blockPos[ %idx ] );
	%rpos = vectorAdd( %rpos, "0 0" SPC $MC::GlobalBlockOffsetZ );

	%obj = new fxDTSBrick()
	{
		position = %rpos;
		dataBlock = %db;
		colorId = 0;
		// 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_" @ %this.blockType[ %idx ] @ "_onPlant" ) )
	{
		call( %func, %obj, %this );
	}

	return %obj;
}

function chunkSO::unloadBlock( %this, %idx )
{
	if ( %idx < 0 || %idx >= %this.blockCount )
	{
		return false;
	}

	if ( !isObject( %obj = %this.blockObject[ %idx ] ) )
	{
		return false;
	}

	%obj.delete();
	%this.blockObject[ %idx ] = "";

	return true;
}
