// Cloned by Martin Derwin on 30 Nov 2022 from World "User-controlled Model World" 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.
// ====================================================================================================================
// User controlled (not Mind controlled) 3D model World with various features:
// - User controlled UP/LEFT/RIGHT arrows move model
// - "First Person View" - Camera rotates with direction you are facing
// Best effect is with "Move with" camera
// - Can have one or multiple lions chasing you
// Smooth movement
// UP moves forward in whatever angle you are at
// LEFT/RIGHT rotate by small angle
// Uses x, y, z rather than grid of squares
// Has collision detection for lion moves
// ===================================================================================================================
// === 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?
/* PROJECT DESCRIPTION:
Authors: Patricija Shalkauskaite, Martin Derwin
How to play:
- You play as a mouse running from lions
- Move Player 1 with the arrow keys and Player 2 with WASD keys
- If a player is caught by a lion, they lose and the other player wins
*/
AB.clockTick = 100; // Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 10000; // 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 = true; // Turn on the Run/Step/Pause controls
ABWorld.drawCameraControls = false; // Scrap the Track/Move with/Normal camera controls
//---- global constants: -------------------------------------------------------
// number of lions, an array for each player:
const NO_LIONS_1 = 30;
const NO_LIONS_2 = 30;
const OBJ_LIONS = '/uploads/martind/Lion-obj.OBJ' ; // credit: https://www.turbosquid.com/3d-models/3d-lioness-animation/1114520
const OBJPATH = "/uploads/martind/"; // path of OBJ and MTL (Peter Parker model)
const OBJNAME = "rat.obj"; // credit: https://www.turbosquid.com/3d-models/mouse-mammal-rodent-max/603885
const MTLNAME = "player2texture.jpg" // rat object mtl file didn't work
// multiply model sizes by some amount:
const SCALE_RAT = 30;
// random size LION with each run:
const SCALE_LIONS = AB.randomFloatAtoB ( 1, 3 );
const TEXTURE_LION = '/uploads/martind/fur.jpg' ;
const MAXPOS = 3500; // length of one side of the arena
const SKYCOLOR = 0xffffcc; // a number, not a string
const LIGHTCOLOR = 0xffffff ;
const startRadiusConst = MAXPOS ; // distance from centre to start the camera at
const maxRadiusConst = MAXPOS * 10 ; // maximum distance from camera we will render things
const AGENT_STEP = 100; // how much agent moves each step
const ENEMY_STEP = 50; // how much enemy moves each step
// this is as close as models come to other models
const CLOSE_LIMIT = 100;
//--- change ABWorld defaults: -------------------------------
ABHandler.MAXCAMERAPOS = maxRadiusConst ;
ABHandler.GROUNDZERO = true; // "ground" exists at altitude zero
// --- Rotations -----------------------
// rotate by some amount of radians from the normal position
// default is 0 which has the model facing DOWN (towards increasing z value) as far as the initial camera sees
const ROTATE_AMOUNT = Math.PI / 20 ; // rotate amount in radians (PI = 180 degrees)
//--- skybox: -------------------------------
// space skybox, credit:
// http://en.spaceengine.org/forum/21-514-1
const SKYBOX_ARRAY = [ // skybox
"/uploads/pat/cheese.png",
"/uploads/pat/cheese.png",
"/uploads/pat/cheese.png",
"/uploads/pat/cheese.png",
"/uploads/pat/cheese.png",
"/uploads/pat/cheese.png"
];
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================
// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.
var THELIONS1 = new Array ( NO_LIONS_1 );
var THELIONS2 = new Array ( NO_LIONS_2 );
var theagent, theagent2, theenemy; // player1, player2, lions
var p1lose = false; // when this is true, player 1 has lost
var p2lose = false; // when this is true, player 2 has lost
var agentRotation = 0 ;
var agentRotation2 = 0 ;
var enemyRotation = 0 ;
var enemyRotation2 = 0 ;
var lion_texture;
function loadResources() // asynchronous file loads - call initScene() when all finished
{
// load lion - OBJ that we will paint with a texture
var loader = new THREE.OBJLoader ( new THREE.LoadingManager() );
loader.load ( OBJ_LIONS, function ( object )
{
theenemy = object; // canonical enemy object - may be copied multiple times
if ( asynchFinished() ) initScene();
});
// load RAT model - OBJ plus MTL (plus TGA files)
THREE.DefaultLoadingManager.addHandler ( /\.tga$/i, new THREE.TGALoader() );
var m = new THREE.MTLLoader();
m.setResourcePath ( OBJPATH );
m.setPath ( OBJPATH );
m.load ( MTLNAME, function ( materials )
{
materials.preload();
var o = new THREE.OBJLoader();
o.setMaterials ( materials );
o.setPath ( OBJPATH );
o.load ( OBJNAME, function ( object )
{
theagent = object;
if ( asynchFinished() ) initScene();
});
});
var n = new THREE.MTLLoader();
n.setResourcePath(OBJPATH);
n.setPath(OBJPATH);
n.load(MTLNAME, function (materials)
{
materials.preload();
var p = new THREE.OBJLoader();
p.setMaterials (materials);
p.setPath(OBJPATH);
p.load(OBJNAME, function (object)
{
theagent2 = object;
if (asynchFinished() ) initScene();
});
});
// load textures
var loader2 = new THREE.TextureLoader();
loader2.load ( TEXTURE_LION, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
lion_texture = thetexture;
if ( asynchFinished() ) initScene();
});
}
function asynchFinished() // all file loads returned
{
if ( lion_texture && theenemy && theagent && theagent2) return true;
else return false;
}
function initScene() // all file loads have returned
{
// add agent model
// start at center
theagent.position.y = 0;
theagent.position.x = 0;
theagent.position.z = 0;
theagent2.position.y = 0;
theagent2.position.x = 300;
theagent2.position.z = 0;
theagent.scale.multiplyScalar ( SCALE_RAT ); // scale it
ABWorld.scene.add( theagent );
theagent2.scale.multiplyScalar ( SCALE_RAT);
ABWorld.scene.add( theagent2 );
// add enemies
// first paint and size the canonical enemy object
theenemy.traverse ( function ( child )
{
if ( child instanceof THREE.Mesh )
child.material.map = lion_texture ;
});
theenemy.scale.multiplyScalar ( SCALE_LIONS ); // scale it
// add perhaps multiple enemy models, starting near outside of arena
// LIONS for player 1
for ( var i = 0; i < NO_LIONS_1 ; i++ )
{
var object = theenemy.clone(); // copy the object multiple times
object.position.y = 0;
object.position.x = AB.randomPick ( AB.randomIntAtoB ( -MAXPOS/2, -MAXPOS/3 ), AB.randomIntAtoB ( MAXPOS/3, MAXPOS/2 ) );
object.position.z = AB.randomPick ( AB.randomIntAtoB ( -MAXPOS/2, -MAXPOS/3 ), AB.randomIntAtoB ( MAXPOS/3, MAXPOS/2 ) );
ABWorld.scene.add( object );
// save in array for later
THELIONS1[i] = object;
}
// LIONS for player 2
for ( var i = 0; i < NO_LIONS_2 ; i++ )
{
var object = theenemy.clone(); // copy the object multiple times
object.position.y = 0;
object.position.x = AB.randomPick ( AB.randomIntAtoB ( -MAXPOS/2, -MAXPOS/3 ), AB.randomIntAtoB ( MAXPOS/3, MAXPOS/2 ) );
object.position.z = AB.randomPick ( AB.randomIntAtoB ( -MAXPOS/2, -MAXPOS/3 ), AB.randomIntAtoB ( MAXPOS/3, MAXPOS/2 ) );
ABWorld.scene.add( object );
// save in array for later
THELIONS2[i] = object;
}
// finally skybox
ABWorld.scene.background = new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY, function()
{
ABWorld.render();
AB.removeLoading();
AB.runReady = true;
});
}
// --- enemy move -----------------------------------
// angle between two points (x1,y1) and (x2,y2)
function angleTwoPoints ( x1, y1, x2, y2 )
{
return Math.atan2 ( x2 - x1, y2 - y1 );
}
function dist ( a, b ) // distance between them when a,b are Three vectors
{
return ( a.distanceTo ( b ) );
}
function collision ( proposedMove, k ) // proposed move FOR lion k
{
if ( dist ( proposedMove, theagent.position ) < CLOSE_LIMIT )
{
p1lose = true; // if a lion touches player 1, they lose
AB.abortRun = true;
}
for ( var i=0 ; i < NO_LIONS_1 ; i++ )
if ( i != k )
if ( dist ( proposedMove, THELIONS1[i].position ) < CLOSE_LIMIT )
return true;
// else
return false;
}
function collision2 ( proposedMove, k ) // proposed move FOR lion k
{
if ( dist ( proposedMove, theagent2.position ) < CLOSE_LIMIT )
{
p2lose = true; // if a lion touches player 2, they lose
AB.abortRun = true;
}
for ( var i=0 ; i < NO_LIONS_2 ; i++ )
if ( i != k )
if ( dist ( proposedMove, THELIONS2[i].position ) < CLOSE_LIMIT )
return true;
// else
return false;
}
function moveEnemy()
{
for ( var i = 0; i < NO_LIONS_1 ; i++ )
{
var e = THELIONS1[i];
// rotate enemy to face agent
enemyRotation = angleTwoPoints ( e.position.x, e.position.z, theagent.position.x, theagent.position.z );
e.rotation.set ( 0, enemyRotation, 0 );
// move along that line, if no collision with any other model
var proposedMove = new THREE.Vector3
(
e.position.x + ( ENEMY_STEP * Math.sin(enemyRotation) ),
e.position.y,
e.position.z + ( ENEMY_STEP * Math.cos(enemyRotation) )
);
if ( ! collision ( proposedMove, i ) )
e.position.copy ( proposedMove );
// else enemy i just misses a turn
}
}
function moveEnemy2()
{
for ( var i = 0; i < NO_LIONS_2 ; i++ )
{
var e2 = THELIONS2[i];
// rotate enemy to face agent
enemyRotation2 = angleTwoPoints ( e2.position.x, e2.position.z, theagent2.position.x, theagent2.position.z );
e2.rotation.set ( 0, enemyRotation2, 0 );
// move along that line, if no collision with any other model
var proposedMove = new THREE.Vector3
(
e2.position.x + ( ENEMY_STEP * Math.sin(enemyRotation2) ),
e2.position.y,
e2.position.z + ( ENEMY_STEP * Math.cos(enemyRotation2) )
);
if ( ! collision2 ( proposedMove, i ) )
e2.position.copy ( proposedMove );
// else enemy i just misses a turn
}
}
//--- key control of agent -------------------------------------------------------------
const KEY_UP = 38;
const KEY_LEFT = 37;
const KEY_RIGHT = 39;
const KEY_A = 65;
const KEY_W = 87;
const KEY_D = 68;
var OURKEYS = [ KEY_UP, KEY_LEFT, KEY_RIGHT, KEY_A, KEY_W, KEY_D ];
function ourKeys(event) { // checks if a key pressed is either WASD or arrow keys
return(OURKEYS.includes(event.keyCode));
}
// UP moves forward in whatever angle you are at
// LEFT/RIGHT rotate by small angle
function keyHandler ( event )
{
if ( ! AB.runReady ) return true; // not ready yet
// if not handling this key, send it to default:
if ( ! ourKeys ( event ) ) return true;
// else handle it and prevent default:
// cases for each different key
// sendData first sends to other worlds what to do, then does it locally using movePlayer
if(event.keyCode == KEY_A) sendData(KEY_A, 2);
if(event.keyCode == KEY_W) sendData(KEY_W, 2);
if(event.keyCode == KEY_D) sendData(KEY_D, 2);
if(event.keyCode == KEY_LEFT)
{
console.log("Player 1 is moving left!!!");
sendData(KEY_LEFT, 1);
}
if(event.keyCode == KEY_UP) sendData(KEY_UP, 1);
if(event.keyCode == KEY_RIGHT) sendData(KEY_RIGHT, 1);
event.stopPropagation(); event.preventDefault(); return false;
}
AB.socketStart(); // start sockets
AB.socketIn = function(data) // incoming data on socket, i.e. key movements of other players
{
action = data[0]
player = data[1]
movePlayer(action, player);
};
function sendData(action, player) // function to send out the move to do, then do the move locally
{
data = [action, player];
AB.socketOut(data);
movePlayer(action, player);
};
function movePlayer(action, player)
{
if (player == 1)
{
if ( action == KEY_UP ) // move a bit along angle we are facing
{
theagent.position.x = theagent.position.x + ( AGENT_STEP * Math.sin(agentRotation) );
theagent.position.z = theagent.position.z + ( AGENT_STEP * Math.cos(agentRotation) );
}
if ( action == KEY_LEFT ) // rotate in place
{
agentRotation = agentRotation + ROTATE_AMOUNT;
theagent.rotation.set ( 0, agentRotation, 0 );
}
if ( action == KEY_RIGHT )
{
agentRotation = agentRotation - ROTATE_AMOUNT;
theagent.rotation.set ( 0, agentRotation, 0 );
}
}
else // Player 2
{
if (action == KEY_W)
{
theagent2.position.x = theagent2.position.x + (AGENT_STEP * Math.sin(agentRotation2));
theagent2.position.z = theagent2.position.z + (AGENT_STEP * Math.cos(agentRotation2));
}
if (action == KEY_A)
{
agentRotation2 = agentRotation2 + ROTATE_AMOUNT;
theagent2.rotation.set ( 0, agentRotation2, 0 );
}
if (action == KEY_D)
{
agentRotation2 = agentRotation2 - ROTATE_AMOUNT;
theagent2.rotation.set ( 0, agentRotation2, 0 );
}
}
}
AB.world.newRun = function()
{
AB.loadingScreen();
AB.runReady = false;
ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
loadResources(); // aynch file loads, returns the function call initScene()
var ambient = new THREE.AmbientLight(); // light
ABWorld.scene.add( ambient );
var thelight = new THREE.DirectionalLight ( LIGHTCOLOR, -1 );
thelight.position.set ( startRadiusConst, startRadiusConst, startRadiusConst );
ABWorld.scene.add(thelight);
document.onkeydown = keyHandler;
};
AB.world.nextStep = function()
{
moveEnemy(); // enemies moves on their own clock
moveEnemy2(); // enemies moves on their own clock
};
AB.world.endRun = function()
{
if ( AB.abortRun && p1lose) AB.msg ( " <br> <font color=red> <B> Player 1 was caught! Player 2 wins. </B> </font> ", 2 );
else if ( AB.abortRun && p2lose) AB.msg ( " <br> <font color=red> <B> Player 2 was caught! Player 1 wins. </B> </font> ", 2 );
};