I'll be very generous here and provide the chunk system from my Minecraft game-mode.
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 );
}
%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 );
getMCBrickGroup().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;
}
$chunkd::XYBase = "0123456789abcdef";
function chunkSO::loadChunkTick( %this, %str, %idx )
{
cancel( %this.loadChunkTick );
%quotaUsage = 0;
%quotaLimit = 32;
%count = getFieldCount( %str );
for ( %idx ; %idx < %count && %quotaUsage < %quotaLimit ; %idx++ )
{
%field = getField( %str, %idx );
%px = strPos( $chunkd::XYBase, getSubStr( %field, 0, 1 ) );
%py = strPos( $chunkd::XYBase, getSubStr( %field, 1, 1 ) );
%pz = mFloor( getSubStr( %field, 2, 3 ) );
%tp = getSubStr( %field, 5, strLen( %field ) );
%this.setBlock( %px, %py, %pz, %tp );
%quotaUsage++;
}
if ( %idx >= %count )
{
%this.initializing = false;
return;
}
%this.loadChunkTick = %this.schedule( 128, "loadChunkTick", %str, %idx );
}
function chunkSO::loadChunk( %this )
{
if ( %this.blockCount )
{
return true;
}
if ( isEventPending( %this.loadChunkTick ) )
{
return true;
}
if ( isEventPending( %this.generateChunkTick ) )
{
return true;
}
if ( !isFile( %path = %this.getFilePath() ) )
{
return false;
}
%fo = new fileObject();
%fo.openForRead( %path );
%str = %fo.readLine();
while( !%fo.isEOF() )
{
%str = %str NL %fo.readLine();
}
%fo.close();
%fo.delete();
%str = strReplace( %str, "\xFA", "\t" );
%this.loadChunkTick( %str, 0 );
}
function chunkSO::saveChunk( %this )
{
if ( %this.unloading )
{
return;
}
if ( getSimTime() - %this.lastSaveTime < 30000 )
{
if ( isEventPending( %this.queuedSavePoint ) )
{
%this.queuedSavePoint = %this.schedule( ( getSimTime() - %this.lastSaveTime ) + 16, "saveChunk" );
}
return false;
}
else if ( isEventPending( %this.queuedSavePoint ) )
{
cancel( %this.queuedSavePoint );
}
if ( isEventPending( %this.loadChunkTick ) )
{
return false;
}
if ( isEventPending( %this.generateChunkTick ) )
{
return false;
}
%path = %this.getFilePath();
%this.lastSaveTime = getSimTime();
if ( !isWriteableFileName( %path ) )
{
return false;
}
%fo = new fileObject();
%fo.openForWrite( %path );
for ( %i = 0 ; %i < %this.blockCount ; %i++ )
{
%type = %this.blockType[ %i ];
%pos = %this.blockPos[ %i ];
%px = getWord( %pos, 0 );
%py = getWord( %pos, 1 );
%pz = getWord( %pos, 2 );
%px = getSubStr( $chunkd::XYBase, %px, 1 );
%py = getSubStr( $chunkd::XYBase, %py, 1 );
if ( strLen( %pz ) == 2 )
{
%pz = "0" @ %pz;
}
else if ( strLen( %pz ) == 1 )
{
%pz = "00" @ %pz;
}
%string = %string @ ( strLen( %string ) ? "\xFA" : "" ) @ %px @ %py @ %pz @ %type;
}
%fo.writeLine( %string );
%fo.close();
%fo.delete();
return true;
}
// Overworld
function chunkSO::getTerrainHeight( %this, %x, %y )
{
if ( strLen( %height = %this.terrainHeight[ %x, %y ] ) )
{
return %height;
}
%height = 63;
if ( %height > 119 )
{
%height = 119;
}
%this.terrainHeight[ %x, %y ] = %height;
return %height;
}
// These functions are currently just dummy functions.
function chunkSO::getBiome( %this, %x, %y )
{
return "forest";
}
function chunkSO::getTemperature( %this, %x, %y )
{
return 50;
}
function chunkSO::getHumidity( %this, %x, %y )
{
return 50;
}
// Nether
// Aether
// The End
function chunkSO::generateChunk( %this )
{
if ( %this.blockCount )
{
return;
}
switch$ ( %this.dimension )
{
case 2: %this.generateTheEnd();
case 1: %this.generateAether();
case 0: %this.generateOverworld();
case -1: %this.generateNether();
}
%this.initializing = false;
%this.saveChunk();
%this.unloadCheckTick();
}
// Overworld
function chunkSO::generateOverworld( %this )
{
// Generate base terrain.
for ( %x = 0 ; %x < 16 ; %x++ )
{
for ( %y = 0 ; %y < 16 ; %y++ )
{
%height = %this.getTerrainHeight( %x, %y );
%this.setBlock( %x, %y, %height, "grass" );
}
}
// Generate trees.
%tree_count = getRandom( -5, 3 );
for ( %i = 0 ; %i < %tree_count ; %i++ )
{
%x = getRandom( 4, 12 );
%y = getRandom( 4, 12 );
%x = mFloor( %x / 2 ) * 2;
%y = mFloor( %y / 2 ) * 2;
if ( %water[ %x, %y ] )
{
continue;
}
if ( %madeTree[ %x, %y ] )
{
continue;
}
%madeTree[ %x, %y ] = true;
%this.generateTree( %x, %y );
}
}
function chunkSO::generateTree( %this, %x, %y )
{
%terr = %this.getTerrainHeight( %x, %y );
%height = getRandom( 4, 10 );
%width = getRandom( 3, 5 );
if ( %width >= %height )
{
%width = %height - 1;
}
for ( %z = 1 ; %z <= %height ; %z++ )
{
%this.setBlock( %x, %y, %terr + %z, "log" );
}
%trp = %x SPC %y SPC %terr + %height;
for ( %xi = %x - %width ; %xi <= %x + %width ; %xi++ )
{
for ( %yi = %y - %width ; %yi <= %y + %width ; %yi++ )
{
for ( %zi = %terr + 1 ; %zi <= %terr + %height + %width ; %zi++ )
{
if ( %xi == %x && %yi == %y && %zi <= %terr + %height )
{
continue;
}
if ( vectorDist( %xi SPC %yi SPC %zi, %trp ) > %width )
{
continue;
}
%this.setBlock( %xi, %yi, %zi, "leaves" );
}
}
}
}
function chunkSO::generateBelowTerrain( %px, %py, %pz, %bl, %th, %obj )
{
if ( $MC::Clearing )
{
return;
}
// px: generate 1 block out from this starting point
// py: generate 1 block out from this starting point
// pz: generate 1 block out from this starting point
// bl: the starting point is this amount of blocks below ground level
// th: ground level at starting point
// obj: an object at starting point
//if ( getRandom() <= 0.02 ) // Create a cave system?
//{
// chunkSO::generateBelowCaveSystem( %px, %py, %pz ); // Generate the cave system.
// return; // Don't do normal underground generation.
//}
for ( %x = -1 ; %x <= 1 ; %x++ )
{
for ( %y = -1 ; %y <= 1 ; %y++ )
{
for ( %z = -1 ; %z <= 1 ; %z++ )
{
%rx = %px + %x;
%ry = %py + %y;
%rz = %pz + %z;
if ( %x == 0 && %y == 0 && %z == 0 )
{
continue;
}
if ( %rz > %th ) // Does this cause us to arrive on ground?
{
continue; // If so, don't screw with ground level; go to next iteration.
}
if ( %rz < 0 ) // Does this cause us to arrive below bedrock?
{
continue; // If so, don't cause index errors; go to next iteration.
}
if ( %rz > 255 ) // Does this cause us to arrive above world?
{ // (note: I'm not sure how this could happen)
continue; // If so, don't cause index errors; go to next iteration.
}
if ( strLen( getBlock( %rx, %ry, %rz ) ) ) // Is there already something here?
{
continue; // If so, don't overwrite it; go to next iteration.
}
if ( generatedPoint( %rx, %ry, %rz ) ) // Did we already generate something here ..
{ // .. which was destroyed by some force?
continue; // If so, don't generate it again; go to next iteration.
}
%dp = %bl - %z; // Determine distance to ground at current point.
%block = "";
if ( %dp < 3 )
{
%block = "dirt";
//if ( getRandom() <= 0.15 )
//{
// %block = "gravel";
//}
}
else
{
if ( %rz <= 7 )
{
%block = "smoothStone";
}
//else if ( %rz <= 10 )
//{
// %block = "lava";
//}
else
{
%block = "smoothStone";
//if ( getRandom() <= 0.1 )
//{
// %block = "gravel";
//}
//else
//{
// if ( %rz <= 63 && getRandom() <= 0.05 )
// {
// %block = "ironOre";
// }
// else if ( %rz <= 31 && getRandom() <= 0.05 )
// {
// %block = "goldOre";
// }
//}
}
}
setBlock( %rx, %ry, %rz, %block );
}
}
}
}
// Nether
function chunkSO::generateNether( %this )
{
%this.generateOverworld();
}
// Aether
function chunkSO::generateAether( %this )
{
%this.generateOverworld();
}
// The End
function chunkSO::generateTheEnd( %this )
{
%this.generateOverworld();
}