// ==== 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 <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> " +
" 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();
}