Code viewer for World: Hunting Season
var start_r = 100 //camera start pos
var max_r = 300  // Max render distance

var p1score = 0;                // Player scores
var p2score = 0;

var max_targets = 3;            // Target count to ensure only 3 targets can spawn

var alltargets = new Array(0);  // array of all targets currently in play

const mixers = [];                      // Store for bird animation
const animClock = new THREE.Clock();    // Clock to time animation cycle
const birdClock = new THREE.Clock();    // Clock to determine when the bird will spawn
const gameClock = new THREE.Clock();    // Clock to determine when the game will end

var bird;
var hitbox;                             // Info about bird: object, hitbox and flight direction.
var dir;
 
const SKYBOX_ARRAY = [										 
    "/uploads/kellyn88/sbleft.jpg",         // left
    "/uploads/kellyn88/sbr.png",            // right 
    "/uploads/kellyn88/test.png",
    "/uploads/kellyn88/skybox_base.png",    //base
    "/uploads/kellyn88/test.png",
    "/uploads/kellyn88/test.png"
    ];
                
var gridSetup = [
    [-160, -100, 70], [-80, -100, 70], [0, -100, 70], [80, -100, 70], [160, -100, 70],
    [-160, -100, -10], [-80, -100, -10], [0, -100, -10], [80, -100, -10], [160, -100, -10], // Layout of poosible target spawn positions
    [-160, -100, -90], [-80, -100, -90], [0, -100, -90], [80, -100, -90], [160, -100, -90]
    ]
                
// Audio files                
const MUSICFILE = '/uploads/kellyn88/ambience.mp3';
AB.backgroundMusic ( MUSICFILE );

const shoot_short = "uploads/kellyn88/shoot_short.wav";
const shoot_long = "uploads/kellyn88/shoot_long.wav";
const squak = "uploads/kellyn88/eagle_call.wav";

// Initial splash screen
AB.newSplash ( splashScreenStart() );
document.getElementById("splashbutton").id = "startsplash";

	AB.world.newRun = function()
	{
	 	
	 	// Camera is pulled back on mobile to allow user to see all targets.
	 	if (AB.onMobile()){
	 	    start_r = 500;
	 	    max_r = 1000;
	 	}
	 	
        AB.socketStart();
        initScene();
        AB.runReady = false;
        cameraControls();
        
        // Alter the size of the run header for mobile devices so user can still see scores and timer.
        if (AB.onMobile()){
            AB.headerLHS();
            AB.runheaderNormal();
            document.getElementById("ab-runheaderbox").style.height = "250px";
            document.getElementById("w2m_loggedin").remove();
            let b = document.getElementsByTagName("b");
            b[0].remove();
            let a = document.getElementsByTagName("a");
            a[1].remove();
        }
        else{
            document.getElementById("ab-runheaderbox").style.width = "200px";
        }
    }
	
	function initScene(){
	    
	    var color = new THREE.Color();
	 	ABWorld.init2d ( start_r, max_r, color );
	 	ABWorld.scene.background = new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY );     // Initiate scene
	 	
	 	var light = new THREE.AmbientLight("white", 0.8);
        ABWorld.scene.add(light);
	}
	
	AB.world.nextStep = function()		 
	{
	    if(Math.trunc(gameClock.getElapsedTime()) == 60){
	        AB.runReady = false;
	        AB.newSplash ( splashScreenEnd() );
            document.getElementById("splashbutton").innerHTML = "Play Again";
            document.getElementById("splashbutton").onclick = reload;           // Splash screen displays when time runs out to display results. Game can be refreshed from here.
	    }
	    
	    if (alltargets.length < max_targets){                                   // Spawn targets
    	    spawnTarget();
	    }
	    if(AB.socket){
	        if(AB.socket.connected){
	            AB.socketOut(p1score);
	            AB.msg("P1 Score = " + p1score + " P2 score = " + p2score + "\n Time remaining: " + Math.trunc(60 - gameClock.getElapsedTime()) );     // Sends player scores to other player.
	        }
	    }
	    
	    if(bird){
	        if(bird.position.x == 25 || bird.position.x == -25){
	            for (var i = 0; i < alltargets.length; i++){
	                if (alltargets[i].name == "bird"){
	                    alltargets.splice(i, 1);
	                }
	            }
	            ABWorld.scene.remove(bird) && ABWorld.scene.remove(hitbox);     // If bird has left the screen remove it
                bird = null;
                max_targets = 3;
	        }
	        else{
	            anim_bird();
	            bird.translateY(-0.5) && hitbox.translateX(dir);                // Move bird
	        }

	    }
	    else{
            var t = birdClock.getElapsedTime();
            if (t >= 15){
                var randomIndex = AB.randomIntAtoB(1, 50);          // Bird has a 1/50 chance of spawning once 20 seconds from the last bird spawn has elapsed
                if(randomIndex == 25){
                    loadBird();
                    birdClock.start();
                }
            }
	    }
	};
	
	function spawnTarget(){
	    var texture = new THREE.TextureLoader().load("/uploads/kellyn88/target.png");
	 	var shape = new THREE.CylinderGeometry(30, 30, 5, 32);
        var cover = new THREE.MeshBasicMaterial({map: texture});
        var target = new THREE.Mesh(shape, cover);                      // Create target mesh
	 	
	 	var randomIndex = AB.randomIntAtoB(0, gridSetup.length);
        var item = gridSetup[randomIndex];
	 	target.position.set(item[0], item[1], item[2]);                 // Select Random spawn location
	 	
	 	ABWorld.scene.add(target);                                      // Add target to scene
	 	gridSetup.splice(randomIndex, 1);                               // Grid position is now occupied                               
	 	alltargets.push(target);                                        // Add to list of targets
	}
	
	function cameraControls()
    {
    		ABHandler.initTouchDrag 	= mouseClick;   // Override default mobile controls
    		ABHandler.touchDrag	        = mouseDrag;
    		ABHandler.touchZoom         = mouseZoom;
    		
    		ABHandler.initMouseDrag 	= mouseClick;   // Override default desktop controls
    		ABHandler.mouseDrag			= mouseDrag;
    		ABHandler.mouseZoom         = mouseZoom;
    		
    }
    
    function mouseClick(x, y)       //Check if mouse click hits target
    {
        audioHandler("shoot");
        targetHit(x, y);
        return;
    }
    
    function mouseDrag(x, y){       // Disable drag
        return;
    }
    
    function mouseZoom(x){          // Disable zoom
        return;
    }
    
    function targetHit(x, y)        // Go through list of targets and check if any are hit by mouse click
    {
        if(alltargets.length > 0){
            for (var i = 0; i < alltargets.length; i++){
                var target = alltargets[i];
                if ( ABWorld.hitsObject ( x, y, target )){
                    if (target.name != "bird"){
                        var pos = [target.position.x, target.position.y, target.position.z];
                        gridSetup.push(pos);                                                    // Grid pos is free again
                        ABWorld.scene.remove(target);                                           // Remove target from scene
                        alltargets.splice(i, 1);                                                // Remove from list of all targets
                        p1score++;                                                              // Increase score
                    }
                    else{
                        ABWorld.scene.remove(bird) && ABWorld.scene.remove(target);
                        bird = null;
                        alltargets.splice(i, 1);
                        max_targets = 3;
                        p1score = p1score + 10;
                    }
                    break;
                }
            }
        }
        return;
    }
    
    function audioHandler(instance){    // Function to play audio
        var a;
        if (instance == "shoot"){
            a = new Audio( shoot_short );		
	        a.play();
        }
        else if(instance == "bird"){
            a = new Audio( squak );
            a.play();
        }
    }
    
    function loadBird()         // Function decides bird's orientation and spawn location and spawns in the object
    {
        var xIndex;
        var zIndex = AB.randomIntAtoB(-5, 5)
        var rotation = new Array (0);
        ran = AB.randomIntAtoB(0, 1);
        if (ran === 0){
            dir = -0.5
            rotation = [0, Math.PI, Math.PI / 2];
            xIndex = 20;
        }
        else{
            dir = 0.5
            rotation = [Math.PI, 0, Math.PI / 2];
            xIndex = -20;
        }
        
        const loader = new THREE.GLTFLoader();
    
        const onLoad = ( gltf, position ) => 
        {
            const model = gltf.scene.children[ 0 ];
            model.position.copy( position );
            
            const animation = gltf.animations[ 0 ];
        
            const mixer = new THREE.AnimationMixer( model );
            mixers.push( mixer );
        
            const action = mixer.clipAction( animation );
            action.play()
        
            model.rotation.set( rotation[0], rotation[1], rotation[2]);
            loadHitbox(position);
            ABWorld.scene.add( model );
            bird = model;
            
        };
        
        
        const birdpos      = new THREE.Vector3 (xIndex, (start_r - (start_r * 0.1)), zIndex);
    
        // can load 3D models of other user:
        audioHandler("bird");
        loader.load ( '/uploads/kellyn88/simple_bird.glb',   gltf => onLoad ( gltf, birdpos )   );
      
    }
    
    function loadHitbox(pos){
	 	var shape = new THREE.BoxGeometry(2, 2, 0.7);
        var cover = new THREE.MeshBasicMaterial(0xfff);     // Create mesh of hitbox for bird
        var target = new THREE.Mesh(shape, cover);
        
        max_targets = 4;
        target.visible = false;
        target.name = "bird";
        target.position.set(pos.x, pos.y, pos.z - 0.1);
        ABWorld.scene.add(target);
        hitbox = target;
        alltargets.push(target);
	}
    
    function anim_bird(){
        const delta = animClock.getDelta();
        mixers.forEach( ( mixer ) => { mixer.update( delta ); } );  // Initiate bird flying animation
    }

	AB.world.endRun = function()
	{
	};
	
	function splashScreenStart()		// HTML format string of instructions for splash screen 
    {
    	var s = "<p>Shoot targets as they appear to earn points.</p>";
    	
    	if ( AB.onDesktop() ) 	s = s + "<p>Desktop instructions: Use your mouse to aim and click to shoot.</p>" ;
    	else 					s = s + "<p>Mobile instructions: Tap to shoot.</p>" ;
    
    	s = s + "<p>1 point is awarded for each target destroyed. Keep an eye out for Hawks. They are more difficult to hit but are worth 10 points. Earn more points than your opponent to win!</p>";
    	return ( s );
    	
    }
    
    function splashScreenEnd()		// HTML format string of instructions for splash screen 
    {
    	var s = "<h2>Game Over!</h2>";
    	
    	if (p1score > p2score){
    	    s = s + "<p>p1 won with a score of:</p>" + p1score
    	}
    	else if (p1score < p2score){
    	    s = s + "<p>p2 won with a score of:</p>" + p2score
    	}
    	else if (p1score == p2score){
    	    s = s + "<p>Draw! Both players had a score of:</p>" + p1score
    	}
    	
    	s = s + "<p>Your score:</p>" + p1score
    	
    	return ( s );
    }
    
    function gameFull(){
        var s = "<p>Game session is currently full. Please try again later.</p>";
        return (s);
    }

	
	$("#startsplash").click(function() {        // If initial splash screen is clicked:
        if(AB.socket){
	        if(AB.socket.connected){
	            AB.socketOut("ready");          // If another player is connected other players game will start simultaneously
	        }
	    }
	    AB.runReady = true;                     // And your game will start
	    AB.removeSplash();
    });
    
    function reload(){
        window.location.reload();               // Function to reload game.
    }
    
	
	AB.socketIn = function (s){                 // Socket functionality
	    if(s == "ready"){
	        AB.runReady = true;                 // Start both games simultaneously
	        AB.removeSplash();
	    }
	    else{
	        p2score = s                         // Receive other players score
	    }
	}
    
    AB.socketUserlist = function ( array ) {        // Retrieve player count
        if (array.length > 2) {                     // If a third player attempts to join:
            AB.runReady = false;
	        AB.newSplash ( gameFull() );            // They will receive a message that the game is full and be prompted to retry.
            document.getElementById("splashbutton").innerHTML = "Try Again";
            document.getElementById("splashbutton").onclick = reload;
            
        }
        AB.socketUserlist = function(){}    // Ensures player count is only retrieved once by unassigning function.
    };