Code viewer for World: The San Andreas World - (c...
// 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 );