// Cloned by Effa Al Bulushi on 1 Dec 2022 from World "Websockets boxes" by Starter user
// Please leave this clone trail here.
//This world is Created by Effa Issa Faqir Al Bulushi. The code is Obfuscated and you are not allowed to use it as it is part of a CA.
//Any changes you make without permission from the author will be complained immediatly to the lecturer.
//These are some of the extra features I have added to the world after cloning it from starter:
//1->Music
//2->password
//3 ->SkyBox
//4 ->added new models
//5-> multi-world
//6-> chatting feature
//7-> make a 3d model move
// ==== 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.
// ====================================================================================================================
// Demo of Websockets functionality added to a 3D World
// Run with two or more users
// Click button to change a random box's texture on all clients running this World
// Pick a side and compete against an opponent!
// You can annoy the other player by reloading the page!
$('body').css( "margin", 'auto' );
$('body').css( "padding", 'auto' );
AB.clockTick = 100; // Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 100000; // Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep = 100; // Take screenshot on this step. (All resources should have finished loading.) Default 50.
AB.drawRunControls = false
const FILE_ARRAY = [
"/uploads/issafae2/logo2.jpeg", // array 0
"/uploads/issafae2/logo3.png", //array 1
"/uploads/issafae2/cj.jpeg", //array 2
"/uploads/issafae2/bigsmoke.jpeg",// array 3
"/uploads/issafae2/ryder.jpeg", // array 4
"/uploads/issafae2/sweet.jpeg", // array 5
];
const FILE_ARRAY2 = [
"/uploads/issafae2/carl_jonhson_cj.glb"];
const SKYCOLOR = 0xa3ccd6; // a number, not a string
const LIGHTCOLOR = 0xffffff ;
const ARMYSIZE = 150; // an "army" of objects
const objectsize = 1;
const WALKSTEP = 0.7; // bounds of the random move per timestep
// try 50 (vibrating sheet) versus 1000 (cloud)
const MAXPOS = 11; // start things within these bounds
const startRadiusConst = MAXPOS * 3 ; // distance from centre to start the camera at
const maxRadiusConst = MAXPOS * 10 ; // maximum distance from camera we will render things
ABHandler.MAXCAMERAPOS = MAXPOS * 10 ; // allow camera go far away
ABWorld.drawCameraControls = false;
var THEARMY = new Array( ARMYSIZE );
var textureArray = new Array ( FILE_ARRAY.length );
var characterArray = new Array ( FILE_ARRAY2.length );
const mixers = [];
const clock = new THREE.Clock();
ABHandler.MAXCAMERAPOS = maxRadiusConst;
ABHandler.GROUNDZERO = true;
const ROTATE_AMOUNT = Math.PI / 20 ;
const CHAR_STEP = 0.5;
const SCALE_HERO = 1 ;
const CAMERASHIFT = - SCALE_HERO * 2 ;
const FOLLOW_Y = SCALE_HERO * 4;
var character;
var characterRotation = 0 ;
//======SkyBox=======
//Sources used to implement this part: https://threejs.org/docs/#api/en/loaders/CubeTextureLoader and https://ancientbrain.com/world.php?world=complex
const SKYBOX_ARRAY = [
"/uploads/issafae2/right1.png",
"/uploads/issafae2/left1.png",
"/uploads/issafae2/top1.png",
"/uploads/issafae2/bottom1.png",
"/uploads/issafae2/front1.png",
"/uploads/issafae2/back1.png"
];
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() ) initArmy();
});
THREE.DefaultLoadingManager.addHandler ( /\.tga$/i, new THREE.TGALoader() );
var m = new THREE.MTLLoader();
m.setResourcePath ( "/uploads/issafae2/" );
m.setPath ( "/uploads/issafae2/" );
m.load ( "carljonhson.mtl", function ( materials )
{
materials.preload();
var o = new THREE.OBJLoader();
o.setMaterials ( materials );
o.setPath ( "/uploads/issafae2/" );
o.load ( "carljonhson.obj", function ( object )
{
character = object;
if ( asynchFinished() ) initArmy(); //HERE
});
});
}
function loadothermodels ()
{
const loader = new THREE.GLTFLoader();
const onLoad = ( gltf, position ) =>
{
const model = gltf.scene.children[ 0 ];
model.position.copy( position );
ABWorld.scene.add( model );
};
const balles = new THREE.Vector3( -6.5, 2.2, -6.7 );
const ogloc = new THREE.Vector3( -15.5, 2.2, 0 );
const cj = new THREE.Vector3( -15.5, 2.2, 21 );
const family = new THREE.Vector3( -10.5, 2.2, 15 );
const car = new THREE.Vector3( -17.5, 2.0, 0);
const car2 = new THREE.Vector3( -17.5, 2.0, 20 );
const pizza = new THREE.Vector3( 0, 8, 0 );
loader.load ( '/uploads/issafae2/ballas1.glb', gltf => onLoad ( gltf, balles ) );
loader.load ( '/uploads/issafae2/ogloc.glb', gltf => onLoad ( gltf, ogloc ) );
loader.load ( '/uploads/issafae2/carl_jonhson_cj.glb', gltf => onLoad ( gltf, cj ));
loader.load ( '/uploads/issafae2/grove_families_gang.glb', gltf => onLoad ( gltf, family ));
loader.load ( '/uploads/issafae2/the_well_stacked_pizza_co.glb', gltf => onLoad ( gltf, pizza ));
loader.load ( '/uploads/issafae2/greenwood_gta_sa.glb', gltf => onLoad ( gltf, car ));
loader.load ( '/uploads/issafae2/blade_gta_sa.glb', gltf => onLoad ( gltf, car2 ));
}
function asynchFinished() // all file loads returned
{
if ( character ) return true;
else return false;
for ( var i = 0; i < FILE_ARRAY.length; i++ )
if ( ! textureArray[i] )
return false;
return true;
}
function initArmy() // called when all textures ready
{
character.position.y = 2.2;
character.position.x = 0;
character.position.z = -20;
character.scale.multiplyScalar ( SCALE_HERO ); // scale it
ABWorld.scene.add( character );
var t = 0;
for ( var c=1 ; c <= ARMYSIZE ; c++ )
{
// var shape = new THREE.SphereGeometry ( objectsize, 30, 30 );
var shape = new THREE.BoxGeometry( objectsize, objectsize, objectsize );
var theobject = new THREE.Mesh( shape );
theobject.position.x = AB.randomIntAtoB ( -MAXPOS, MAXPOS );
theobject.position.z = AB.randomIntAtoB ( -MAXPOS, MAXPOS );
theobject.position.y = 0;
var r = AB.randomIntAtoB ( 0, 1 ); // first and second pictures are used
theobject.material = new THREE.MeshBasicMaterial ( { map: textureArray[r] } );
ABWorld.scene.add(theobject);
THEARMY[t] = theobject; // save it for later
t++;
}
// can start the run loop
ABWorld.render();
AB.runReady = true;
AB.msg (
` <hr> <p> Multi-user world. Click buttons to change boxes on all users' machines. Drag the camera. <p>
<button onclick='cj();' class=ab-largenormbutton > CJ </button>
<button onclick='bigsmoke();' class=ab-largenormbutton > Big Smoke </button> <p>
<button onclick='ryder();' class=ab-largenormbutton > Ryder </button>
<button onclick='sweet();' class=ab-largenormbutton > Sweet </button> <p>
<hr> <h1> Chat Here! </h1>
<div style="width:20vw; margin: 'auto'; padding:'auto';">
<h3> Me </h3>
<p><INPUT style="width:13vw;" id=me >
<button onclick="sendchat();" class=ab-normbutton > Send </button><p>
<br><hr>
<h3> Them </h3>
<div id=them > </div>
<br><hr>
<h3> Who's online: </h3>
<div id=themlist > </div>
<br>
</div>
<p> `);
const me = document.getElementById('me').onkeydown = function(event) { if (event.keyCode == 13) sendchat(); };
console.log('me');
AB.removeLoading();
}
function moveArmy() // move all the objects
{
for ( var i = 0; i < THEARMY.length; i++ )
{
if ( THEARMY[i] ) // in case initArmy() not called yet
{
THEARMY[i].position.x = THEARMY[i].position.x + AB.randomIntAtoB(-WALKSTEP,WALKSTEP) ;
THEARMY[i].position.z = THEARMY[i].position.z + AB.randomIntAtoB(-WALKSTEP,WALKSTEP) ;
THEARMY[i].position.y = THEARMY[i].position.y + AB.randomIntAtoB(-WALKSTEP,WALKSTEP) ;
ABWorld.scene.add( THEARMY[i] );
}
}
}
// UP moves forward in whatever angle you are at
// LEFT/RIGHT rotate by small angle
//
const KEY_UP = 38;
const KEY_LEFT = 37;
const KEY_RIGHT = 39;
var KEYS = [ KEY_UP, KEY_LEFT, KEY_RIGHT ];
function ourKeys ( event ) { return ( KEYS.includes ( event.keyCode ) ); }
function keyHandler ( event )
{
if ( ! AB.runReady ) return true; // not ready yet
if ( ! ourKeys ( event ) ) return true;
// else handle it and prevent default:
if ( event.keyCode == KEY_UP ) // move a bit along angle we are facing
{
character.position.x = character.position.x + ( CHAR_STEP * Math.sin(characterRotation) );
character.position.z = character.position.z + ( CHAR_STEP * Math.cos(characterRotation) );
}
if ( event.keyCode == KEY_LEFT ) // rotate in place
{
characterRotation = characterRotation + ROTATE_AMOUNT;
character.rotation.set ( 0, characterRotation, 0 );
}
if ( event.keyCode == KEY_RIGHT )
{
characterRotation = characterRotation - ROTATE_AMOUNT;
character.rotation.set ( 0, characterRotation, 0 );
}
event.stopPropagation(); event.preventDefault(); return false;
}
AB.world.newRun = function()
{
AB.loadingScreen();
AB.newSplash();
AB.splashHtml ( `
<h1> Enter Password </h1>
Only certain users can join. <br>
You only join with users who enter the same password (not with all users of the World). <br>
The first user to run the World can enter any password! But other users must know what password they will use.
<p>
Enter password:
<input style='width:25vw;' maxlength='2000' NAME="p" id="p" VALUE='' >
<button onclick='start();' class=ab-normbutton >Start</button>
<p>
<div id=errordiv name=errordiv> </div> ` );
AB.runReady = false;
ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
loadResources(); // aynch file loads
// calls initArmy() when it returns
loadothermodels();
initLights();
/*var thelight = new THREE.DirectionalLight ( LIGHTCOLOR, 3 );
thelight.position.set ( startRadiusConst, startRadiusConst, startRadiusConst );
ABWorld.scene.add(thelight);*/
ABWorld.scene.background = new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY, function()
{
ABWorld.render();
AB.removeLoading();
AB.runReady = true; // start the run loop
});
document.onkeydown = keyHandler;
};
AB.world.nextStep = function()
{
const delta = clock.getDelta();
mixers.forEach( ( mixer ) => { mixer.update( delta ); } );
moveArmy();
};
function initLights()
{
const ambientLight = new THREE.AmbientLight( 0xffffff, 1 );
ABWorld.scene.add( ambientLight );
const frontLight = new THREE.DirectionalLight( 0xffffff, 1 );
frontLight.position.set( 100, 100, 100 );
const backLight = new THREE.DirectionalLight( 0xffffff, 1 );
backLight.position.set( -50, 50, -50 );
ABWorld.scene.add( frontLight, backLight );
}
//========= Socket functionality=============
//=========start Socket============
function alphanumeric ( str ) //check if the password is alphanumeric
{
var pass = str.match(/^[0-9a-zA-Z]+$/); // start of line "[0-9a-zA-Z]+" end of line
if ( pass ) return true;
else return false;
}
function start() // start the game if the user have entered a password
{
var password = jQuery("input#p").val();
password = password.trim();
//If the password is not alphanumeric, show an error statment to the user
if ( ! alphanumeric ( password ) )
{
$("#errordiv").html( "<font color=red> <B> Error: Password must be alphanumeric. </b></font> " );
return;
}
// else we have a password, start the socket run with this password
AB.socketStart ( password );
AB.removeSplash();
}
//=======Buttons work==========
// functions called by buttons
function cj() { changeBox(2); AB.socketOut (2); }
function bigsmoke() { changeBox(3); AB.socketOut (3); }
function ryder() { changeBox(4); AB.socketOut (4); }
function sweet() { changeBox(5); AB.socketOut (5); }
function changeBox(n) // change a random box to texture n (5 or 6)
{
var i = AB.randomIntAtoB ( 0, THEARMY.length - 1 ); // pick a random box to change
THEARMY[i].material = new THREE.MeshBasicMaterial ( { map: textureArray[n] } );
}
AB.socketIn = function(n) // incoming data on socket, i.e. clicks of other player
{
if ( ! AB.runReady ) return;
changeBox(n);
//moveCharacter(n);
};
//=====Chatting Feature=======
function sendchat()
{
var theline = $("#me").val();
var data =
{
userid: AB.myuserid,
username: AB.myusername,
line: theline
};
AB.socketOut ( data ); // server gets this, and sends the data to all clients running this World
}
// given AB userid, username, construct a link to that user
// if not run logged in, userid = "none"
function userlink ( userid, username )
{
if ( userid == "none" ) return ( "Unknown user" );
else return ( "<a target='_blank' href='https://ancientbrain.com/user.php?userid=" + userid + "'>" + username + "</a>" );
}
// When someone else enters text, server will trigger AB.socketIn.
AB.socketIn = function(data)
{
var userhtml = userlink ( data.userid, data.username );
$("#them").html ( userhtml + ": " + data.line );
};
// At startup, and when list of users changes, server will trigger AB.socketUserlist.
// Here I define what to do for it.
AB.socketUserlist = function ( a )
{
console.log ( "Got user list: " );
console.log ( JSON.stringify ( a, null, ' ' ) );
if ( a.length < 2 )
{
$("#themlist").html ( "<h3 style='color:red'> You are alone. Get someone else to run this World. </h3>");
return;
}
// else
var str = " <ol> ";
for (var i = 0; i < a.length; i++)
{
var userhtml = userlink ( a[i][0], a[i][1] );
str = str + " <li> " + userhtml ;
}
$("#themlist").html ( str + " </ol> " );
};
//======Music=======
const music = '/uploads/issafae2/GTAV-SanAndreasIntroRemake.mp3'; // adding music in the background
AB.backgroundMusic ( music );