Code viewer for World: MineCraft (clone by Stephe...

// Cloned by Stephen Walsh on 22 Feb 2023 from World "MineCraft" by Starter user 
// Please leave this clone trail here.
 


// ==== Starter World =================================================================================================
// This code is designed for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.
// ====================================================================================================================



// "MineCraft" World 
// User adds blocks using arrows and Page Up/Down 

// --- nextStep not used: ------------------------ 
// we do not define any AB.world.nextStep  
// all actions are taken whenever user hits keys and not any other time

// --- mobile: ------------------------ 
// does not work on mobile (no keyboard)

 
 
// --- Demo of everything to do with "logged in" runs: ---------------------------------------------------------------
// If you run this logged in you can save your work and restore it. 
// Whether logged in or not, splash screen shows "scoreboard" of all users' creations, and allows load of any. 
 


 

// ===================================================================================================================
// === Start of tweaker's box ======================================================================================== 
// ===================================================================================================================

// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?


AB.clockTick       = 100;    

	// Speed of run: Step every n milliseconds. Default 100.
	
AB.maxSteps        = 1000000;    

	// Length of run: Maximum length of run in steps. Default 1000.

AB.screenshotStep  = 50;   
  
	// Take screenshot on this step. (All resources should have finished loading.) Default 50.

 
AB.drawRunControls = false;
	
	// Scrap the Run/Step/Pause controls

 
 const FILE_ARRAY = [
		"/uploads/starter/minecraft.1.jpg",
		"/uploads/starter/minecraft.2.jpg"
		];
		
 const SOUND_BLOCK = '/uploads/starter/chamber.mp3' ;

    // sound effect credit:
    // http://soundbible.com/1399-Chambering-A-Round.html

 const SKYCOLOR 	= 0xffffff;				// a number, not a string 
 
const objectsize = 300 ;

const MAXPOS                = 4000 ;                                 
const startRadiusConst	 	= MAXPOS * 0.5 ;		// distance from centre to start the camera at
const maxRadiusConst 		= MAXPOS * 5 ;		// maximum distance from camera we will render things  


//--- change ABWorld defaults: -------------------------------

ABHandler.MAXCAMERAPOS = MAXPOS * 50 ;			// allow camera go far away 

ABWorld.drawCameraControls = false;

// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================


// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.






// can write CSS to page 
// document.write (of CSS at least) is good at the moment we include the JS, not later
 
 document.write ( ` <style>

    .mybutton 
    {
        border-radius: 0px;
        background-color: darkslategrey;	 
    }
        
    .mybutton:hover { background-color: darkcyan; }

    </style> ` );
 
 


// global array of all user data 

    var allData;
 
	var splashClicked = false;
	

	

	var cx, cy, cz;        // current location 

	var textureArray = new Array ( FILE_ARRAY.length );

// keep array of blocks, can save this to server: 

    var BLOCKARRAY = [];      // start with empty array 
	
	
	
function loadResources()		// asynchronous file loads - call initScene() when all finished 
{
	for ( var i = 0; i < FILE_ARRAY.length; i++ ) 
	  startFileLoad ( i );						// launch n asynchronous file loads
}

	
function startFileLoad ( n )				// asynchronous file load of texture n 
{
	var loader = new THREE.TextureLoader();

	loader.load ( FILE_ARRAY[n], function ( thetexture )  	 
	{
		thetexture.minFilter  = THREE.LinearFilter;
		textureArray[n] = thetexture;
		if ( asynchFinished() ) init();
	});	
}
 
 
function asynchFinished()		// all file loads returned 
{
	for ( var i = 0; i < FILE_ARRAY.length; i++ ) 
		if ( ! textureArray[i] ) 
			return false;
		
	  return true;
}	




function init()		  // called when all textures ready 
{
    // create one box to start 
     
  	var shape = new THREE.BoxGeometry( objectsize, objectsize, objectsize );
  	var theobject  = new THREE.Mesh( shape );

  	theobject.position.x = 0;     	
  	theobject.position.z = 0;     	
  	theobject.position.y = 0;  	
 
	cx = theobject.position.x;     // current position
	cy = theobject.position.y;
	cz = theobject.position.z;
 
 	ABWorld.scene.add(theobject);
 
    BLOCKARRAY.push ( theobject.position );       // add to array of blocks 
    
    paintThis ( theobject );
	
  	if ( splashClicked ) 
		AB.runReady = true;  		// start run loop 

}



 
function drawFromArray (a)      // clear scene and draw blocks restored from array 
{
    // clear scene (remove all scene.children)
    while ( ABWorld.scene.children.length )
    {
        ABWorld.scene.remove ( ABWorld.scene.children[0] );
    }

    for ( var i=0; i < a.length; i++ )
    {
    	var shape = new THREE.BoxGeometry( objectsize, objectsize, objectsize );
    	var theobject  = new THREE.Mesh( shape );

    	theobject.position.x = a[i].x;     	
    	theobject.position.y = a[i].y;     	
    	theobject.position.z = a[i].z;  	
 
    	ABWorld.scene.add(theobject);
 
        paintThis ( theobject );
    }
    
    i = a.length - 1;    // current position will be position of last block 
    
    cx = a[i].x;     
    cy = a[i].y;     
    cz = a[i].z;     
    
    BLOCKARRAY = a;
	  
	playBlockSound(); 
}


function paintThis( object )        // paint objects with random textures 
{
        var t = AB.randomIntAtoB ( 0, textureArray.length - 1 );     // random texture 
    
        object.material =  new THREE.MeshBasicMaterial ( { map: textureArray[t] } );   
}




// key handling function
// we will handle these keys:

var OURKEYS = [ 33, 34, 37, 38, 39, 40 ];

function ourKeys ( event ) { return ( OURKEYS.includes ( event.keyCode ) ); }



function handleKeyDown ( event )
{
	if ( ! AB.runReady ) return true; 		// not ready yet 
 
	// if not one of our special keys, send it to default key handling:
	
	if ( ! ourKeys ( event ) ) return true;
		
	// else handle key and prevent default handling:
	
	// make new block:
	var shape  = new THREE.BoxGeometry( objectsize, objectsize, objectsize );
	var theobject  = new THREE.Mesh( shape );

  theobject.position.x = cx;     // default position is current position - going to then move it 
  theobject.position.y = cy;
  theobject.position.z = cz;
 
  if ( event.keyCode == 37 )  theobject.position.x = cx - objectsize ; 	    // left
  if ( event.keyCode == 39 )  theobject.position.x = cx + objectsize ; 	    // right
 
  if ( event.keyCode == 38 )  theobject.position.z = cz - objectsize ; 	    // forward
  if ( event.keyCode == 40 )  theobject.position.z = cz + objectsize ; 	    // back 
  
  if ( event.keyCode == 34 )  theobject.position.y = cy - objectsize ; 	    
  if ( event.keyCode == 33 )  theobject.position.y = cy + objectsize ;   
  
  cx = theobject.position.x;     // current position is now this 
  cy = theobject.position.y;
  cz = theobject.position.z;
 
 
 	ABWorld.scene.add(theobject);

    BLOCKARRAY.push ( theobject.position );       // add to array of blocks 

    paintThis ( theobject );
      
	playBlockSound();

	event.stopPropagation(); event.preventDefault(); return false;
}



//--- Scoreboard on splash screen --------------------------------------------------------

function shortstring ( str )
// If doing a scoreboard, the data changes as users change.
// You need to consider unusual data, such as very long user name.
{
    return ( str.substring ( 0, 30 ) );        // first n characters of string 
}


// sort the results of getAllData
// this will be World-specific 

function mysort (a,b) 
// how to compare two objects in the getAllData array 
// array of items ( userid, username, object )
// sort by object length (no. of blocks)
{
    var alen = a[2].length;
    var blen = b[2].length;
    if ( alen == blen ) return 0;
    if ( alen >  blen ) return -1;
    if ( alen <  blen ) return 1;
}


function makeSplash ( a )
// replace splash screen with this HTML
// show a "scoreboard" of all users who have saved data 
// the arg is the array returned by getAllData, an array of items ( userid, username, object )
{
    var html = "<div style='max-width:600px; text-align:left;'>" +
        "<h1>  MineCraft &nbsp; <img width=50 src='/uploads/starter/minecraft.1.jpg'> </h1>  ";
 
 	
 	if ( AB.onDesktop() )
	{
 	    html = html + " <p> <b>Instructions:</b>  Draw blocks using Arrow keys and PgUp, PgDn. </p> ";
 	    
		if ( AB.runloggedin )	
		    html = html + "<p> <b> Logged in: </b> " +  
		            // " <a href='https://ancientbrain.com/user.php?userid="+ AB.myuserid + "'>" + shortstring( AB.myusername ) + "</a>.  " +
		            " You are running  " +
		            " <a href='https://ancientbrain.com/docs.runs.php#runloggedin'>\"logged in\"</a>. " +
		            "  You can save your work to the server. </p>";
		else        			
		    html = html + "<p style='background-color:#ffffcc;'> <b> Not logged in: </b> " +
		            " You are not running " +
		            " <a href='https://ancientbrain.com/docs.runs.php#runloggedin'>\"logged in\"</a>. " +
		            " You cannot save your work to the server.   To run logged in, log in and run this from the World page. </p> ";
	}
 	else                   
 	    html = html + " <p> <b> Warning:</b> This World only works fully on desktop. </p> ";

	
	// scoreboard 
	
    if ( a.length === 0 )  
    {
        html = html + "<p> <b> Start: </b> Start MineCraft:   <button  style='vertical-align:text-bottom'  id=splashbutton class=ab-normbutton >Start</button> </p>" +
                    "  <p> No user has saved any creations yet. </p>" ;
    }
    else 
    {
        html = html +  "<p> <b> Start: </b> Start   from scratch:   <button  style='vertical-align:text-bottom'  id=splashbutton class=ab-normbutton >Start</button>  &nbsp; " +
                " Or load   creation of   previous user: </p>" +
                "<div class=ab-horizontalscroll >" +
                "<table class=ab-mytable style='background: rgba(238, 255, 255, 1.0);'	>" +
                "<TR> <TD class=ab-headertd> User </td> <TD class=ab-headertd> Number of blocks </td><td class=ab-headertd> Load creation </td></TR>";

        for ( var i = 0; i < a.length; i++ ) 
        {
          html = html + "<tr><td> <a href='https://ancientbrain.com/user.php?userid="+ a[i][0] + "'>" + shortstring( a[i][1] ) + "</a></td>" +
                    "<td>" + a[i][2].length + "</td>" +
                    "<td> <button onclick='loadCreation(" + i + ");'  class=ab-normbutton >Load</button> </td></tr>";
                    // "Load" button i will call function to load object i (we have saved a list of all objects in memory)
        }
    
        html = html + "</table></div>";
    }
	
    return ( html + "</div>" );
}
  
 
 

AB.world.newRun = function() 
{
	AB.runReady = false;  

	// get all data saved for this World for all users
	// when this returns it makes a splash screen with this info 
	
	AB.getAllData ( processAllData );		// processAllData is the callback function 
	
   // can set up scene while waiting for splash screen to be clicked 
   
	ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 
	
	// if logged in, on desktop, put buttons in run div 
	
	if ( AB.onDesktop() )
		if ( AB.runloggedin )
		{
			// Definitely can save, not sure if can restore:
			AB.msg ( " <button onclick='saveData();' class='ab-normbutton mybutton' >Save work</button> " );
			
			// Check if any data exists, if so make restore button
			AB.queryDataExists ( function ( exists )		// asynchronous - need callback function 
			{
				if ( exists )  makeRestoreButton();
			});
 
		}	
		
	loadResources();		// aynch file loads		
							// calls init() when it returns 
   
	// set up the main key handler:
	document.addEventListener( 'keydown', handleKeyDown   );
};



function makeRestoreButton()
{
 	  AB.msg ( " <button onclick='restoreData();' class='ab-normbutton mybutton' >Restore work</button> ", 2 );
} 


function saveData() 	// save BLOCKARRAY to server 
{
	// if no restore button exists, can make one now 
	// if exists, this just overwrites it 
	makeRestoreButton();
	
    // console.log ( "Saving " + BLOCKARRAY.length + " blocks to server" );
    AB.saveData ( BLOCKARRAY );  
}
 

function restoreData()
{
	 AB.restoreData ( function ( a )            
	 {
		// object returned from server is an array of blocks 
		// console.log ( "Restoring " + a.length + " blocks from server" );
		drawFromArray (a);
	 });
} 
  
 
function processAllData ( a )          
// arg is the array returned by getAllData  
// makes a splash screen with "scoreboard"
{
	AB.newSplash();	
	
	// sort the array to get a sorted "human scoreboard"
	// the sort will be World specific 
	a.sort ( mysort ); 
	
	// build splash contents from the array   
	var html = makeSplash ( a );
	
	// replace splash contents 
	AB.splashHtml ( html );
		
	AB.splashClick ( removeSplash );    // 'start from scratch' button 
	
	allData = a;         // global var - save it for later
}
 
 
// load creation i from saved "allData" 

function loadCreation ( i )
{
    removeSplash();                 // now audio is ready 
    drawFromArray ( allData[i][2] );
}


function removeSplash()        
{		
    // touch/click on splash screen marks audio as good for JS to call without further human interaction  
	audio.play();	audio.pause();		   

	AB.removeSplash();			// remove splash screen 
	splashClicked = true;
	AB.runReady = true;  		// start run loop 
}


 

	
	

// --- audio --------------------------------------------------------------

    var audio = new Audio( SOUND_BLOCK );

function 	playBlockSound()
{
    audio.play();
}