사용자:Senouis/자바스크립트

리버티게임, 모두가 만들어가는 자유로운 게임
//---------------------------------------------------------------------------------------------
// UncycloEngine 1 by Epic Ungames, 2019-2020
//---------------------------------------------------------------------------------------------

//----------------------------------mapinfo and object definition------------------------------
// Objects must also be defined in server-side javascript
function GameRule(RedClock, BlueClock, initialstate) {
	this.state = initialstate; // 0: game ceased, 1: game setup, 2: game on!, 3: round end
	this.RedClock = RedClock;
	this.BlueClock = BlueClock;
	this.currentdominating = 0; // 0: neutral, 1: red, 2:blue
	this.reddominatingprogress = 0;
	this.bluedominatingprogress = 0;
}

let redteamnum = 0;
let blueteamnum = 0;

function GameObject (pos, size, direction, team, collidetype, flag, health = 100) { 
	// pos: array[x, y], size: array(cube)[x, y]/integer(cylinder), direction: integer(degree)
	this.pos = pos;
	this.size = size;
	this.rotation = [0, 0 ,0];
	this.direction = direction; // define camera angle
	this.height = 0;
	this.scale = [1, 1, 1];
	this.collidetype = collidetype; // 0: statical cubic(no direction), 1: cylinder
	this.flag = flag; // free flag
	this.team = team; // team : integer(0: not defined, 1: Red Yangachi, 2: Blue Yingeo, 3: Neutral)
	this.state = 0; // state depends on type of Game Object(0 is usually initial state)
	this.healthpoint = health;
	this.maxhealthpoint = health;
	this.canbedamaged = false;
	this.cameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
	this.cameraAngleRadians = 0;
	this.updateCameraAngle = (event, value) => {
		this.cameraAngleRadians = degToRad(value);
	 };
	this.updatePosition = (index, value)=> {
		switch (index) {
			case 0:
				this.pos[0] = value;
				break;
			case 2:
				this.pos[1] = value;
				break;
			case 1:
				this.height = value;
				break;
			default:
				break;
		}
	  };
	this.vertexlist = [ 
			// wall 1
			2*pos[0],   32,  2*pos[1],
			2*pos[0],   -32,  2*pos[1],
			2*pos[0],   -32,  2*pos[1] + 2*size[1],
			2*pos[0],   32,  2*pos[1],
			2*pos[0],   -32,  2*pos[1] + 2*size[1],
			2*pos[0],   32, 2*pos[1] + 2*size[1],
			// wall 2
			2*pos[0]+ 2*size[0],   32,  2*pos[1],
			2*pos[0]+ 2*size[0],   -32,  2*pos[1] + 2*size[1],
			2*pos[0]+ 2*size[0],   -32,  2*pos[1],
			2*pos[0]+ 2*size[0],   32,  2*pos[1],
			2*pos[0]+ 2*size[0],   32,  2*pos[1] + 2*size[1],
			2*pos[0]+ 2*size[0],   -32,  2*pos[1] + 2*size[1],
			// wall 3
			2*pos[0],		32,  2*pos[1],
			2*pos[0]+ 2*size[0],	-32,  2*pos[1],
			2*pos[0],		-32,  2*pos[1],
			2*pos[0],		32,  2*pos[1],
			2*pos[0]+ 2*size[0],	32,  2*pos[1],
			2*pos[0]+ 2*size[0], -32,  2*pos[1],
			// wall 4
			2*pos[0],		32,  2*pos[1] + 2*size[1],
			2*pos[0],		-32,  2*pos[1] + 2*size[1],
			2*pos[0]+ 2*size[0],	-32,  2*pos[1] + 2*size[1],
			2*pos[0],		32,  2*pos[1] + 2*size[1],
			2*pos[0]+ 2*size[0], -32,  2*pos[1] + 2*size[1],
			2*pos[0]+ 2*size[0],	32,  2*pos[1] + 2*size[1]
		];
	this.updateRotation = (index, value) => {
		var angleInDegrees = value;
		var angleInRadians = (angleInDegree % 360) * Math.PI / 180;
		this.rotation[index] = angleInRadians;
	  };

	this.updatescale = (index, value) =>{
		  this.scale[index] = value;
	  };
	this.GetCenterPosition = () => {
		if (this.collidetype === 0) {
			return [this.pos[0] + this.size[0] /2, this.pos[1] + this.size[1] / 2];
		} else {
			return [this.pos[0] + this.size, this.pos[1] + this.size];
		}
	};
	this.SetCenterPosition = (position) => {
		if (this.collidetype === 0) {
			this.pos = [position[0] -this.size[0] /2, position[1] -this.size[1] / 2];
		} else {
			this.pos = [position[0] -this.size, position[1] - this.size];
		}
	};
	this.IsColliding = (target) => {
		if (this.collidetype === 0 && target.collidetype === 0) { // cube vs cube
			if (this.GetCenterPosition()[0] + this.size[0]/2 >= target.GetCenterPosition()[0] - target.size[0]/2 && this.GetCenterPosition()[0] - this.size[0]/2 <= target.GetCenterPosition()[0] + target.size[0]/2 && this.GetCenterPosition()[1] + this.size[1]/2 >= target.GetCenterPosition()[1] - target.size[1]/2 && this.GetCenterPosition()[1] - this.size[1]/2 <= target.GetCenterPosition()[1] + target.size[1]/2) return true;
			else return false;
		} else if (this.collidetype === 0 && target.collidetype === 1) {
			if (target.GetCenterPosition()[0] + target.size >= this.GetCenterPosition()[0] - this.size[0] / 2 && target.GetCenterPosition()[0] - target.size <= this.GetCenterPosition()[0] + this.size[0] / 2) { // horizontal collision
				if (target.GetCenterPosition()[1] >= this.GetCenterPosition()[1] - this.size[1]/2 - target.size && target.GetCenterPosition()[1] <= this.GetCenterPosition()[1] + this.size[1]/2 + target.size ) 	return true;
			}
			if (target.GetCenterPosition()[1] + target.size >= this.GetCenterPosition()[1] - this.size[1] /2 && target.GetCenterPosition()[1] - target.size <= this.GetCenterPosition()[1] + this.size[1] /2) {
				// vertical collision
				if (target.GetCenterPosition()[0]+ target.size >= this.GetCenterPosition()[0] - this.size[0]/2 && target.GetCenterPosition()[0] - target.size <= this.GetCenterPosition()[0] + this.size[0] /2) {
					return true;
				}
			}
			// corner collision
			let equation1 = (target.GetCenterPosition()[0]-(this.GetCenterPosition()[0] - this.size[0] / 2)) * (target.GetCenterPosition()[0]-(this.GetCenterPosition()[0]- this.size[0] / 2))+ (target.GetCenterPosition()[1]-(this.GetCenterPosition()[1]- this.size[1] / 2)) * (target.GetCenterPosition()[1]-(this.GetCenterPosition()[1]- this.size[1] / 2));
			let equation2 = (target.GetCenterPosition()[0]-(this.GetCenterPosition()[0] - this.size[0] / 2)) * (target.GetCenterPosition()[0]-(this.GetCenterPosition()[0]- this.size[0] / 2))+ (target.GetCenterPosition()[1]-(this.GetCenterPosition()[1]+this.size[1]/2)) * (target.GetCenterPosition()[1]-(this.GetCenterPosition()[1]+this.size[1]/2));
			let equation3 = (target.GetCenterPosition()[0]-(this.GetCenterPosition()[0]+this.size[0]/2)) * (target.GetCenterPosition()[0]-(this.GetCenterPosition()[0]+this.size[0]/2))+ (target.GetCenterPosition()[1]-(this.GetCenterPosition()[1]- this.size[1] / 2)) * (target.GetCenterPosition()[1]-(this.GetCenterPosition()[1]- this.size[1] / 2));
			let equation4 = (target.GetCenterPosition()[0]-this.GetCenterPosition()[0]-this.size[0]/2) * (target.GetCenterPosition()[0]-this.GetCenterPosition()[0]-this.size[0]/2)+ (target.GetCenterPosition()[1]-this.GetCenterPosition()[1]-this.size[1]/2) * (target.GetCenterPosition()[1]-this.GetCenterPosition()[1]-this.size[1]/2);
			if (equation1 <= target.size* target.size || equation2 <= target.size* target.size || equation3 <= target.size* target.size || equation4 <= target.size* target.size) {
				return true;
			} else {
				return false;
			}
		} else if (this.collidetype === 1 && target.collidetype === 0) {
			if (this.GetCenterPosition()[0] + this.size >= target.GetCenterPosition()[0] - target.size[0] / 2 && this.GetCenterPosition()[0] - this.size <= target.GetCenterPosition()[0] + target.size[0]/2) { // horizontal collision
				if (this.GetCenterPosition()[1] + this.size>= target.GetCenterPosition()[1] - target.size[1] / 2 && this.GetCenterPosition()[1] - this.size <= target.GetCenterPosition()[1] + target.size[1]/2 ) return true;
			}
			if (this.GetCenterPosition()[1] + this.size >= target.GetCenterPosition()[1] - target.size[1] / 2 && this.GetCenterPosition()[1] - this.size <= target.GetCenterPosition()[1] + target.size[1]/2 ) {
				// vertical collision
				if (this.GetCenterPosition()[0] + this.size>= target.GetCenterPosition()[0] - target.size[0] / 2  && this.GetCenterPosition()[0]- this.size <= target.GetCenterPosition()[0] + target.size[0] /2) {
					return true;
				}
			}
			// corner collision
			let equation1 = (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0]+target.size[0]/2)) * (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0]+target.size[0]/2))+ (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1]+target.size[1]/2)) * (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1]+target.size[1]/2));
			let equation2 = (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0] - target.size[0] / 2)) * (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0]- target.size[0] / 2))+ (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1]+target.size[1]/2)) * (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1]+target.size[1]/2));
			let equation3 = (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0]+target.size[0]/2)) * (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0]+target.size[0]/2))+ (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1] - target.size[1] / 2)) * (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1] - target.size[1] / 2));
			let equation4 = (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0] - target.size[0] / 2)) * (this.GetCenterPosition()[0]-(target.GetCenterPosition()[0] - target.size[0] / 2))+ (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1]- target.size[1] / 2)) * (this.GetCenterPosition()[1]-(target.GetCenterPosition()[1]- target.size[1] / 2));
			if (equation1 <= this.size* this.size || equation2 <= this.size* this.size || equation3 <= this.size* this.size || equation4 <= this.size* this.size) {
				return true;
			} else {
				return false;
			}
		} else { // cylinder vs cylinder
			if ((this.GetCenterPosition()[0] - target.GetCenterPosition()[0])*(this.GetCenterPosition()[0] - target.GetCenterPosition()[0]) + (this.GetCenterPosition()[1] - target.GetCenterPosition()[1])*(this.GetCenterPosition()[1] - target.GetCenterPosition()[1]) <= (this.size + target.size) * (this.size + target.size)) return true;
			else return false;
		}
		return false; // this could not be happened
	};
}
function Projectile(pos, size, direction, team,  shooter, imgsrc, type = 0) {
	GameObject.call(this, pos, size, direction, team, 1, 0, 1);
	this.movespeed = 4;
	this.shooter = shooter;
	this.imgsrc = imgsrc;
	this.visible = false;
	this.type = type;
	this.cameraMatrix = m4.lookAt([this.GetCenterPosition()[0],0,this.GetCenterPosition()[1]],[this.GetCenterPosition()[0]+Math.cos(this.direction), 0, this.GetCenterPosition()[1]+Math.sin(this.direction)], up );
	this.vertexlist = [
	];
}
Projectile.prototype = Object.create(GameObject.prototype);
Projectile.prototype.constructor = Projectile;
const MAX_PROJECTILE_PER_PLAYER = 100;
const MAX_PROJECTILE_NUMBER = 100 * MAX_PROJECTILE_PER_PLAYER;
let projectilelist = [];

function Explosion(pos, size, type = 0) {
	GameObject.call(this, pos, size, 0, 0, 1, 0, 1);
	this.type = type;
	this.counter = 0;
	this.active = true;
	this.cameraMatrix = m4.lookAt([this.GetCenterPosition()[0],0,this.GetCenterPosition()[1]],[this.GetCenterPosition()[0]+Math.cos(this.direction), 0, this.GetCenterPosition()[1]+Math.sin(this.direction)], up );
	this.vertexlist = GeneratePlaneVertex(this.GetCenterPosition(), size * 2, this.cameraMatrix);
}
Explosion.prototype = Object.create(GameObject.prototype);
Explosion.prototype.constructor = Explosion;

const MAX_EXPLOSION_NUMBER = 100 * MAX_PROJECTILE_PER_PLAYER;
let explosionlist = [];
function Player(pos, size, direction, team, health, id = 0, isdead = false, playername = "unknown player", classnum = 0) {
	GameObject.call(this, pos, size, direction, team, 1, 0, health);
	this.playername = playername;
	this.playerID = id;
	this.movespeed = 1;
	this.classnumber = classnum;
	this.canbedamaged = true;
	this.isdead = isdead;
	this.deadtimer;
	this.secondweapon = undefined; // weapon 0
	this.firstweapon = undefined; // weapon 1
	this.currentweapon = 0;
	this.ReloadWeapon = () => {
		console.log("Reloaded");
	};
	this.FireWeapon = () =>{
		console.log("Fired");
	};
	this.DoSpecialSkill = () => {
		console.log("Did SpecialSkill");
	};
	this.ReleaseFire = () => {
		return;
	}
	this.reloadDelaying = false;
	this.raylist = []; // remember, this only has MAX_RAY_PER_PLAYER number of elements
	this.moveSomething = () => {
		let camMatrix = m4.lookAt([this.GetCenterPosition()[0],this.height,this.GetCenterPosition()[1]],[this.GetCenterPosition()[0]+Math.cos(this.direction), 0, this.GetCenterPosition()[1]+Math.sin(this.direction)], up );
		let forwardvector = m4.getForward(camMatrix);
		let rightvector = m4.getRight(camMatrix);
		if (keystate[0] && keystate[2] || keystate[0] && keystate[3] || keystate[1] && keystate[2] || keystate[1] && keystate[3]) {
			rightvector[0] *= 0.7071;
			rightvector[2] *= 0.7071;
			forwardvector[0] *= 0.7071;
			forwardvector[2] *= 0.7071;
		}
		// remember, the map has reversed axis
		if (keystate[0]){ // a
			let freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0] - this.movespeed*rightvector[0], mapobject.pos[1] ], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team && this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(0, this.pos[0] + this.movespeed*rightvector[0]);
				
			}
			freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0], mapobject.pos[1] - this.movespeed*rightvector[2]], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team &&this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(2, this.pos[1] + this.movespeed*rightvector[2]);
			}
			ws.send(JSON.stringify({
				type:5,
				id: this.playerID,
				pos: this.pos,
				direction: this.direction
			}));
		}
		if (keystate[1]){ //d
			let freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0] + this.movespeed*rightvector[0], mapobject.pos[1] ], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team &&this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(0, this.pos[0] - this.movespeed*rightvector[0]);
				
			}
			freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0], mapobject.pos[1] + this.movespeed*rightvector[2]], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team &&this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(2, this.pos[1] - this.movespeed*rightvector[2]);
			}
			ws.send(JSON.stringify({
				type:5,
				id: this.playerID,
				pos: this.pos,
				direction: this.direction
			}));
		}
		if (keystate[2]) { //s
			let freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0] - this.movespeed*forwardvector[0], mapobject.pos[1] ], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team &&this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(0, this.pos[0] + this.movespeed*forwardvector[0]);
			}
			freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0], mapobject.pos[1] - this.movespeed*forwardvector[2]], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team &&this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(2, this.pos[1] + this.movespeed*forwardvector[2]);
			}
			ws.send(JSON.stringify({
				type:5,
				id: this.playerID,
				pos: this.pos,
				direction: this.direction
			}));
		}
		if (keystate[3]) { // w
			let freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0] + this.movespeed*forwardvector[0], mapobject.pos[1] ], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team &&this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(0, this.pos[0] -this.movespeed*forwardvector[0]);
			}
			freeToMove = true;
			for (let mapobject of mapinfo.mapobjectlist) {
				let tempmapobject = new GameObject([mapobject.pos[0], mapobject.pos[1] + this.movespeed*forwardvector[2]], mapobject.size, mapobject.direction, mapobject.team, mapobject.collidetype, mapobject.flag, mapobject.healthpoint);
				if (tempmapobject.team != this.team &&this.IsColliding(tempmapobject)) freeToMove = false;
			}
			if (freeToMove){
				this.updatePosition(2, this.pos[1] - this.movespeed*forwardvector[2]);
			}
			ws.send(JSON.stringify({
				type:5,
				id: this.playerID,
				pos: this.pos,
				direction: this.direction
			}));
		}
		if (keystate[0] || keystate[1] || keystate[2] || keystate[3]) {
			if ( heightcount >= 0 && heightcount <20) this.updatePosition(1, 0.1* heightcount);
			else this.updatePosition(1, 2-0.1*heightcount);
			if (heightcount >= 39) heightcount = 0;
			else heightcount = heightcount +1;
		}
		if (keystate[4] && this.reloadDelaying === false) { // q
			if (this.currentweapon === 0) {
				this.secondweapon.isFiring = false;
				this.currentweapon = 1;
			} else {
				this.firstweapon.isFiring = false;
				this.currentweapon = 0;
			}
			ws.send(JSON.stringify({
				type:16,
				id: this.playerID,
				currentweapon: this.currentweapon
			}));
			this.reloadDelaying = true;
			setTimeout(this.keydelayEnd,400);
		}
		if (keystate[5]) { // e
		}
		if (keystate[6]) { // r
			this.ReloadWeapon();
		}
		if (keystate[7]) { // f
			
		}
	  }
	this.keydelayEnd = () => {
			this.reloadDelaying = false;
		}
	this.vertexlist = GeneratePlaneVertex(this.GetCenterPosition(), 32, this.cameraMatrix);
}
Player.prototype = Object.create(GameObject.prototype);
Player.prototype.constructor = Player;
let playerlist = [];
function Building(pos, size, direction, team, health, id = 0) {
	Player.call(this, pos, size, direction, team, health, id)
}
Building.prototype = Object.create(Player.prototype);
Building.prototype.constructor = Building;
const mapobjectsizeconstant = 32;
function MapInfo (text) { // map infomation
	  this.rawmapdata = text;
	  let mapdataarray = this.rawmapdata.split('\n');
	  let mapsize = mapdataarray[0].split(' ');
	  this.mapheight = parseInt(mapsize[0]);
	  this.mapwidth = parseInt(mapsize[1]);
	  this.mapversion = parseInt(mapsize[2]);
	  this.mapdata = mapdataarray;
	  this.mapwallcounter = 0;
	  this.mapobjectlist = [];
	  this.maphealthkitlist = [];
	  let mapX = 64* this.mapwidth;
	  let mapY = 64* this.mapheight;
	  this.polygons = [
			  // ground
			  0,   -32,  0,
			  mapX,   -32,  mapY,
			  mapX,   -32,  0,
			  mapX,   -32,  mapY,
			  0,   -32,  0,
			  0,   -32,  mapY,

			  // ceiling
			  0,   32,  0,
			  mapX,   32,  0,
			  mapX,   32,  mapY,
			  mapX,   32,  mapY,
			  0,   32,  mapY,
			  0,   32,  0
			  
			  // walls
			  
			  ];
	  this.mapColors = [
        // ground and ceiling
        0,	0,	0,
        0,	0,	0,
        0,	0,	0,
        0,	0,	0,
        0,	0,	0,
        0,	0,	0,

        0,	0,	0,
        0,	0,	0,
        0,	0,	0,
        0,	0,	0,
        0,	0,	0,
        0,	0,	0
        
        // other wall
		];
	  for (let i = 1; i <= this.mapheight; i++) {
		for (let j = 0; j < this.mapwidth; j++){
			switch (this.mapdata[i][j]) {
				case 'R':
					this.RedSpawn = [ mapobjectsizeconstant*j,  mapobjectsizeconstant*(i-1)];
					break;
				case 'B':
					this.BlueSpawn = [ mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)];
					break;
				case 'W':
					this.mapobjectlist.push(new GameObject([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],0, 0, 0, 0)); 
					break;
				case 'Q':
					this.mapobjectlist.push(new GameObject([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],0, 0, 0, 2)); 
					break;
				case 'A':
					this.mapobjectlist.push(new GameObject([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],0, 0, 0, 1)); 
					break;
				case 'H':
					this.maphealthkitlist.push(new Healthkit([mapobjectsizeconstant*j + 8, mapobjectsizeconstant*(i-1) +8], 8, this.maphealthkitlist.length));
					break;
				case 'Z':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],270, 2));
					break;
				case 'X':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],90, 2));
					break;
				case 'C':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],180, 2));
					break;
				case 'V':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],0, 2));
					break;
				case 'U':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],270, 1));
					break;
				case 'I':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],90, 1));
					break;
				case 'O':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],180, 1));
					break;
				case 'P':
					this.mapobjectlist.push(new Door([mapobjectsizeconstant*j, mapobjectsizeconstant*(i-1)], [mapobjectsizeconstant, mapobjectsizeconstant],0, 1));
					break;
				default:
					break; // do nothing
			}
		}
	  }
	  for (let object of this.mapobjectlist) {
		  if (object.constructor === GameObject) {
			  this.mapwallcounter += 4;
		  } else if (object.constructor === Door) {
			  this.mapwallcounter += 2;
		  }
		  this.polygons = this.polygons.concat(object.vertexlist);
	  }
}
function Healthkit(pos, size, id) {
	GameObject.call(this, pos, size, 0, 0, 1, 1, 0);
	this.id = id;
	this.respawntime = 8000;
	this.isSpawned = true;
	this.respawntimer = undefined;
	this.vertexlist = GenerateProjectile(this.GetCenterPosition(), size, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
	this.respawn = () => {
		this.isSpawned = true;
		return;
	}
}
Healthkit.prototype = Object.create(GameObject.prototype);
Healthkit.prototype.constructor = Healthkit;

function Door(pos, size, direction, team) {
	GameObject.call(this, pos, size, direction, team, 0, 0, 100000);
	switch (direction) {
		case 0:
			this.vertexlist = [ 
			// wall (right)
			2*pos[0] + 2* size[0],   32,  2*pos[1],
			2*pos[0] + 2* size[0],   -32,  2*pos[1]+2*size[1],
			2*pos[0] + 2* size[0],   -32,  2*pos[1],
			2*pos[0] + 2* size[0],   32,  2*pos[1],
			2*pos[0] + 2* size[0],   32,  2*pos[1]+2*size[1],
			2*pos[0] + 2* size[0],   -32,  2*pos[1]+2*size[1],
			// wall (left)
			2*pos[0] + 2* size[0] - 4,   32,  2*pos[1],
			2*pos[0] + 2* size[0] - 4,   -32,  2*pos[1],
			2*pos[0] + 2* size[0] - 4,   -32,  2*pos[1]+2*size[1],
			2*pos[0] + 2* size[0] - 4,   32,  2*pos[1],
			2*pos[0] + 2* size[0] - 4,   -32,  2*pos[1]+2*size[1],
			2*pos[0] + 2* size[0] - 4,   32,  2*pos[1]+2*size[1]
			];
			break;
		case 90:
			this.vertexlist = [
			// wall (down)
			2*pos[0],		32,  2*pos[1]+2*size[1],
			2*pos[0],		-32,  2*pos[1]+2*size[1],
			2*pos[0] + 2* size[0],	-32,  2*pos[1]+2*size[1],
			2*pos[0],		32,  2*pos[1]+2*size[1],
			2*pos[0] + 2* size[0], -32,  2*pos[1]+2*size[1],
			2*pos[0] + 2* size[0],	32,  2*pos[1]+2*size[1],
			// wall (Up)
			2*pos[0],		32,  2*pos[1]+2*size[1]-4,
			2*pos[0] + 2* size[0],	-32, 2*pos[1]+2*size[1]-4,
			2*pos[0],		-32,  2*pos[1]+2*size[1]-4,
			2*pos[0],		32,  2*pos[1]+2*size[1]-4,
			2*pos[0] + 2* size[0],	32,  2*pos[1]+2*size[1]-4,
			2*pos[0] + 2* size[0], -32,  2*pos[1]+2*size[1]-4
			];
			break;
		case 180:
			this.vertexlist = [
			// wall (left)
			2*pos[0],   32,  2*pos[1],
			2*pos[0],   -32,  2*pos[1],
			2*pos[0],   -32,  2*pos[1]+2*size[1],
			2*pos[0],   32,  2*pos[1],
			2*pos[0],   -32,  2*pos[1]+2*size[1],
			2*pos[0],   32,  2*pos[1]+2*size[1], 
			// wall (right)
			2*pos[0] + 4,   32,  2*pos[1],
			2*pos[0] + 4,   -32,  2*pos[1]+2*size[1],
			2*pos[0] + 4,   -32,  2*pos[1],
			2*pos[0] + 4,   32,  2*pos[1],
			2*pos[0] + 4,   32,  2*pos[1]+2*size[1],
			2*pos[0] + 4,   -32,  2*pos[1]+2*size[1],
			];
			break;
		case 270:
			this.vertexlist = [
			// wall (Up)
			2*pos[0],		32,  2*pos[1],
			2*pos[0] + 2* size[0],	-32,  2*pos[1],
			2*pos[0],		-32,  2*pos[1],
			2*pos[0],		32,  2*pos[1],
			2*pos[0] + 2* size[0],	32,  2*pos[1],
			2*pos[0] + 2* size[0], -32,  2*pos[1],
			// wall (down)
			2*pos[0],		32,  2*pos[1]+4,
			2*pos[0],		-32,  2*pos[1]+4,
			2*pos[0] + 2* size[0],	-32,  2*pos[1]+4,
			2*pos[0],		32,  2*pos[1]+4,
			2*pos[0] + 2* size[0], -32,  2*pos[1]+4,
			2*pos[0] + 2* size[0],	32,  2*pos[1]+4
			];
			break;
		default:
			break;
	}
}
Door.prototype = Object.create(GameObject.prototype);
Door.prototype.constructor = Door;

function AreaEquation(centerpos, firstcornerpos, secondcornerpos) { // 3 dimensional position
	this.point = centerpos;
	this.normalvector = m4.cross([centerpos[0]-firstcornerpos[0],centerpos[1]-firstcornerpos[1],centerpos[2]-firstcornerpos[2]], [centerpos[0]-secondcornerpos[0],centerpos[1]-secondcornerpos[1],centerpos[2]-secondcornerpos[2]]);
}
function LineEquation(startpos, endpos) { // 3 dimensional position
	this.point = startpos;
	this.linevector = [endpos[0] - startpos[0],endpos[1] - startpos[1],endpos[2] - startpos[2]];
}
function CylinderEquation(center, radius) {
	this.centerpos = center;
	this.radius = radius;
}
const MAX_RAY_PER_PLAYER = 10;
const MAX_RAY_NUM = 32 * MAX_RAY_PER_PLAYER;
function Ray (startpos, endpos) {
	this.visible = false;
	this.startpos = startpos; // array
	this.endpos = endpos; // array
	this.collidedobjectsinfo = []; // will contain list of GameObject objects and their penetrated positions
	this.AddPenetratingObjects = (objectlist) => {
		for (let object of objectlist){
			let lineeq = new LineEquation(this.startpos, this.endpos);
			if (object.collidetype === 0) { // cubic -> doing collision check 4 times
				let areaeq1 = new AreaEquation([object.GetCenterPosition()[0]-object.size[0]/2, object.GetCenterPosition()[1],0], [object.GetCenterPosition()[0]-object.size[0]/2, object.GetCenterPosition()[1]-object.size[1]/2,mapobjectsizeconstant/2],[object.GetCenterPosition()[0]-object.size[0]/2, object.GetCenterPosition()[1]-object.size[1]/2,-mapobjectsizeconstant/2]);
				let resultArray = LineTrace_Sub1(lineeq, areaeq1);
				if (resultArray.length > 0 && resultArray[1] >= object.GetCenterPosition()[1]-object.size[1]/2 && resultArray[1] <= object.GetCenterPosition()[1]+object.size[1]/2) this.collidedobjectsinfo.push([object, resultArray]);
				let areaeq2 = new AreaEquation([object.GetCenterPosition()[0], object.GetCenterPosition()[1]-object.size[1]/2,0], [object.GetCenterPosition()[0]-object.size[0]/2, object.GetCenterPosition()[1]-object.size[1]/2,mapobjectsizeconstant/2],[object.GetCenterPosition()[0]-object.size[0]/2, object.GetCenterPosition()[1]-object.size[1]/2,-mapobjectsizeconstant/2]);
				resultArray = LineTrace_Sub1(lineeq, areaeq2);
				if (resultArray.length > 0 && resultArray[0] >= object.GetCenterPosition()[0]-object.size[0]/2 && resultArray[0] <= object.GetCenterPosition()[0]+object.size[0]/2) this.collidedobjectsinfo.push([object, resultArray]);
				let areaeq3 = new AreaEquation([object.GetCenterPosition()[0]+object.size[0]/2, object.GetCenterPosition()[1],0], [object.GetCenterPosition()[0]+object.size[0]/2, object.GetCenterPosition()[1]+object.size[1]/2,mapobjectsizeconstant/2],[object.GetCenterPosition()[0]+object.size[0]/2, object.GetCenterPosition()[1]+object.size[1]/2,-mapobjectsizeconstant/2]);
				resultArray = LineTrace_Sub1(lineeq, areaeq3);
				if (resultArray.length > 0 && resultArray[1] >= object.GetCenterPosition()[1]-object.size[1]/2 && resultArray[1] <= object.GetCenterPosition()[1]+object.size[1]/2) this.collidedobjectsinfo.push([object, resultArray]);
				let areaeq4 = new AreaEquation([object.GetCenterPosition()[0], object.GetCenterPosition()[1]+object.size[1]/2,0], [object.GetCenterPosition()[0]+object.size[0]/2, object.GetCenterPosition()[1]+object.size[1]/2,mapobjectsizeconstant/2],[object.GetCenterPosition()[0]+object.size[0], object.GetCenterPosition()[1]+object.size[1],-mapobjectsizeconstant/2]);
				resultArray = LineTrace_Sub1(lineeq, areaeq4);
				if (resultArray.length > 0 && resultArray[0] >= object.GetCenterPosition()[0]-object.size[0]/2 && resultArray[0] <= object.GetCenterPosition()[0]+object.size[0]/2) this.collidedobjectsinfo.push([object, resultArray]);
			} else if (object.collidetype === 1){ // cylinder -> only find success/fail
				let cylindereq = new CylinderEquation(object.GetCenterPosition(), object.size);
				let hitpoint = LineTrace_Sub2(lineeq, cylindereq);
				if ((hitpoint[0] - this.startpos[0]) * (hitpoint[0] - this.startpos[0]) + (hitpoint[1] - this.startpos[1]) + (hitpoint[2] - this.startpos[2]) * (hitpoint[2] - this.startpos[2]) < (this.endpos[0] - this.startpos[0]) * (this.endpos[0] - this.startpos[0]) + (this.endpos[1] - this.startpos[1]) * (this.endpos[1] - this.startpos[1]) + (this.endpos[2] - this.startpos[2]) * (this.endpos[2] - this.startpos[2]) ) this.collidedobjectsinfo.push([object, hitpoint]);
			}
		}
	}
	this.GetClosestCollidedPosition = () => { // only find cube collided position;
		let collidedposition = this.endpos;
		for (let objectinfo of this.collidedobjectsinfo) {
			if (objectinfo.length > 1) {
				let line1 = new LineEquation(this.startpos, objectinfo[1]);
				let criterionline = new LineEquation(this.startpos, this.endpos);
				if (m4.normalize(criterionline.linevector)[0] * m4.normalize(line1.linevector)[0] >0 && GetLineLength(this.startpos, objectinfo[1]) <= GetLineLength(this.startpos, collidedposition)) collidedposition = objectinfo[1];
			}
		}
		return collidedposition;
	}
	this.GetClosestCollidedObject = (rayownerid = -1) => { // only find cube collided position;
		let collidedposition = this.endpos;
		let collidedobject = undefined;
		for (let objectinfo of this.collidedobjectsinfo) {
			if (objectinfo[0].constructor === Player && objectinfo[0].playerID === rayownerid) continue;
			if (objectinfo.length > 1) {
				let line1 = new LineEquation(this.startpos, objectinfo[1]);
				let criterionline = new LineEquation(this.startpos, this.endpos);
				if (m4.normalize(criterionline.linevector)[0] * m4.normalize(line1.linevector)[0] >0 && GetLineLength(this.startpos, objectinfo[1]) <= GetLineLength(this.startpos, collidedposition)) {
					collidedposition = objectinfo[1];
					collidedobject = objectinfo[0];
				}
			}
		}
		return collidedobject;
	}
	this.DrawRay = () => {
		this.visible = true;
		setTimeout(this.DrawRay_timeout, 50);
		return;
	}
	this.DrawRay_timeout = () => {
		this.visible = false;
		return;
	}
};
// -----------------------------------------------global data-------------------------------------------
const up = [0, 1, 0];

// player state
let fieldOfViewRadians = degToRad(90);
let heightcount = 0;
// --------------------------------------------global function -----------------------------------------------
String.format = function() {
	// The string containing the format items (e.g. "{0}")
	// will and always has to be the first argument.
	var theString = arguments[0];
	// start with the second argument (i = 1)
	for (var i = 1; i < arguments.length; i++) {
		// "gm" = RegEx options for Global search (more than one instance)
		// and for Multiline search
		var regEx = new RegExp("\\{" + (i - 1) + "\\}", "gm");
		theString = theString.replace(regEx, arguments[i]);
	}
	return theString;
}

function radToDeg(r) {
    return r * 180 / Math.PI;
}

function degToRad(d) {
return d * Math.PI / 180;
}

function LineTrace_Sub1(lineeq, areaeq) {
	if (lineeq.linevector[0] * areaeq.normalvector[0] + lineeq.linevector[1] * areaeq.normalvector[1] === 0) return [];
	let t = -(areaeq.normalvector[0]*(lineeq.point[0]-areaeq.point[0]) + areaeq.normalvector[1]*(lineeq.point[1]-areaeq.point[1]) + areaeq.normalvector[2]*(lineeq.point[2]-areaeq.point[2])) / (areaeq.normalvector[0]*lineeq.linevector[0] + areaeq.normalvector[1]*lineeq.linevector[1] +areaeq.normalvector[2]*lineeq.linevector[2]);
	return [lineeq.linevector[0]*t + lineeq.point[0], lineeq.linevector[1]*t + lineeq.point[1],lineeq.linevector[2]*t + lineeq.point[2]];
}
function LineTrace_Sub2(lineeq, cylindereq) {
	let a = lineeq.linevector[0];
	let b = lineeq.linevector[1];
	let c = cylindereq.centerpos[0] - lineeq.point[0];
	let d = cylindereq.centerpos[1] - lineeq.point[1];
	let t1 = (a*c + b* d + Math.sqrt( (a*c + b*d) * (a*c + b*d) - (a*a + b*b) * (c*c + d* d - cylindereq.radius * cylindereq.radius) )) / (a*a + b*b);
	let t2 = (a*c + b* d - Math.sqrt( (a*c + b*d) * (a*c + b*d) - (a*a + b*b) * (c*c + d* d - cylindereq.radius * cylindereq.radius) )) / (a*a + b*b);
	let dx1 = a * t1;
	let dy1 = b * t1;
	let dx2 = a * t2;
	let dy2 = b * t2;
	return dx1*dx1+ dy1*dy1 < dx2*dx2 + dy2*dy2 ? [dx1 + lineeq.point[0], dy1 + lineeq.point[1], lineeq.linevector[2]* t1 + lineeq.point[2]] : [dx2 + lineeq.point[0], dy2 + lineeq.point[1], lineeq.linevector[2]* t2 + lineeq.point[2]];
}
function GetLineLength(startpos, endpos) {
	return Math.sqrt((endpos[0] - startpos[0])* (endpos[0] - startpos[0]) + (endpos[1] - startpos[1]) * (endpos[1] - startpos[1]) + (endpos[2] - startpos[2]) * (endpos[2] - startpos[2]));
}
function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.log(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
}
function updateFieldOfView(event, value) {
    fieldOfViewRadians = degToRad(value);
  }
function GenerateProjectile(pos, size, cameraMatrix) { // pos: [x, y](center), size: integer, cameraMatrix: 4x4 matrix
	return [
		//plane 1
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		
		//plane 2
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		
		// plane 3
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		
		// plane 4
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2]
		
	];
}
function GeneratePlaneVertex(pos, size, cameraMatrix) { // pos: [x, y](center), size: integer, height: interger, cameraMatrix: 4x4 matrix
	return [
		//plane
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		
		//plane 2
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		
		// plane 3
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]-size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]-size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]-size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		
		// plane 4
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] -size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		-size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] -size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] - size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] - size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] - size *m4.getUp(cameraMatrix)[2],
		2*pos[0] +size* m4.getForward(cameraMatrix)[0]+size* m4.getRight(cameraMatrix)[0] + size *m4.getUp(cameraMatrix)[0],
		size* m4.getForward(cameraMatrix)[1]+size* m4.getRight(cameraMatrix)[1] + size *m4.getUp(cameraMatrix)[1],
		2*pos[1] +size* m4.getForward(cameraMatrix)[2]+size* m4.getRight(cameraMatrix)[2] + size *m4.getUp(cameraMatrix)[2]
	];
}

// ------------------------------------------------ input control ------------------------------------------------
let keystate = [];
function Input(screen) {
	this.screen = screen;
	this.target;
	this.keyboardinputtimer;
	this.havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
	this.InitInput = () => {
		if (this.havePointerLock){
			  this.screen.onclick = () => { // requestPointerLock must be in callback
				  this.screen.requestPointerLock = this.screen.requestPointerLock || this.screen.mozRequestPointerLock || this.screen.webkitRequestPointerLock;
				  // Ask the browser to lock the pointer
				  this.screen.requestPointerLock();
			  }
			  // Hook pointer lock state change events
			  
			  document.addEventListener('pointerlockchange', this.changeCallback, false);
			  document.addEventListener('mozpointerlockchange', this.changeCallback, false);
			  document.addEventListener('webkitpointerlockchange', this.changeCallback, false);
			  
			  document.addEventListener('pointerlockerror', this.errorCallback, false);
			  document.addEventListener('mozpointerlockerror', this.errorCallback, false);
			  document.addEventListener('webkitpointerlockerror', this.errorCallback, false);
		}
		return;
	}
	 // ----------------------mouse management----------------------------------
	this.GrabInput = (target) => {
		this.target = target;
		this.keyboardinputtimer = setInterval(this.target.moveSomething, 10);
		return;
	};
	this.manageKeyboardInputDown = () => {
			if (event.keyCode === 65 || event.keyCode === 97) //a
				keystate[0] = true;
			if (event.keyCode === 68 || event.keyCode === 100) //d
				keystate[1] = true;
			if (event.keyCode === 83 || event.keyCode === 115) //s
				keystate[2] = true;
			if (event.keyCode === 87 || event.keyCode === 119) // w
				keystate[3] = true;
			if (event.keyCode === 81 || event.keyCode === 113) // q
				keystate[4] = true;
			if (event.keyCode === 69 || event.keyCode === 101) //e
				keystate[5] = true;
			if (event.keyCode === 82 || event.keyCode === 114) //r
				keystate[6] = true;
			if (event.keyCode === 70 || event.keyCode === 102) // f
				keystate[7] = true;
	  };
	  this.manageKeyboardInputUp = () => {
			if (event.keyCode === 65 || event.keyCode === 97) // a
				keystate[0] = false;
			if (event.keyCode === 68 || event.keyCode === 100) //d
				keystate[1] = false;
			if (event.keyCode === 83 || event.keyCode === 115) //s
				keystate[2] = false;
			if (event.keyCode === 87 || event.keyCode === 119) //w
				keystate[3] = false;
			if (event.keyCode === 81 || event.keyCode === 113) //q
				keystate[4] = false;
			if (event.keyCode === 69 || event.keyCode === 101) //e
				keystate[5] = false;
			if (event.keyCode === 82 || event.keyCode === 114) //r
				keystate[6] = false;
			if (event.keyCode === 70 || event.keyCode === 102) // f
				keystate[7] = false;
	  };
	  this.errorCallback = (e) => {
		  alert( "Fail to initialize point lock process" );
	  }
	  this.moveCallback = (e) => {
		  let movementX = 0;
		  let movementY = 0;
		  movementX = e.movementX ||
			  e.mozMovementX          ||
			  e.webkitMovementX       ||
			  0,
		  movementY = e.movementY ||
			  e.mozMovementY      ||
			  e.webkitMovementY   ||
			  0;
		  if (this.target != undefined) {
			  this.target.direction = this.target.direction + degToRad(movementX);
			  ws.send(JSON.stringify({
				type:5,
				id: this.target.playerID,
				pos: this.target.pos,
				direction: this.target.direction
			  }));
		  }
	  }
	  this.ReleasePlayer = () => {
		clearInterval(this.keyboardinputtimer);
		return;
	  }
	  
	  this.changeCallback = (e) => {
		if (document.pointerLockElement === this.screen ||
		  document.mozPointerLockElement === this.screen ||
		  document.webkitPointerLockElement === this.screen) {
		  // Pointer was just locked
		  // Enable the mousemove listener
		  document.addEventListener("mousemove", this.moveCallback);
		  document.addEventListener("keydown", this.manageKeyboardInputDown);
		  document.addEventListener("keyup", this.manageKeyboardInputUp);
		  document.onmousedown = () => {
			switch(event.button) {
				case 0:
					this.target.FireWeapon();
					break;
				case 2:
					this.target.DoSpecialSkill();
					break;
				default:
					break;
			}
		  }
		  document.onmouseup = () => {
			  if (event.button === 0) this.target.ReleaseFire();
			  return;
		  }
		} else {
		  // Disable the mousemove listener
		  document.removeEventListener("mousemove", this.moveCallback);
		  document.removeEventListener("keydown", this.manageKeyboardInputDown);
		  document.removeEventListener("keyup", this.manageKeyboardInputUp);
		  document.onmousedown = () => {
			return;
		  }
		  document.onmouseup = () => {
			return;
		  }
		}
	  }
	  
}

// ------------------------------------------ Engine Main ----------------------------------------------
let mapdataurl = "http://libertyga.me/index.php?title=%EC%82%AC%EC%9A%A9%EC%9E%90:Senouis/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A7%B5&action=raw";

let mapinfo; // map information object
let player;
let gamerule;

function EngineMain() {
  // init player
  player = new Player([0, 0], 8, degToRad(0), 1);
  gamerule = new GameRule(0, 0, 0);
  // ----------------------------Procedure Start--------------------------------
  // Get A WebGL context
  let canvas = document.getElementById("MainCanvas");
  
  let gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }
  // setup GLSL program
  let vertexshader = createShader(gl, gl.VERTEX_SHADER, document.getElementById("3d-vertex-shader").text);
  let fragmentshader = createShader(gl, gl.FRAGMENT_SHADER, document.getElementById("3d-fragment-shader").text);
  
  let program = createProgram(gl, vertexshader, fragmentshader);
  
  let planevertexshader = createShader(gl, gl.VERTEX_SHADER, document.getElementById("2d-vertex-shader").text);
  let planefragmentshader = createShader(gl, gl.FRAGMENT_SHADER, document.getElementById("2d-fragment-shader").text);
  
  let textureprogram = createProgram(gl, planevertexshader, planefragmentshader);
  
  // load map data
  mapinfo = new MapInfo ("25 45 1\n\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n\
AFFFFFPFFFFFFFFFFFFAFFFFFFFFFAFFFFFFFFFFFFFFW\n\
AFFFFFPFFFFFFFFFFFFWFFFFFFFFFWFFFFFFFFFFFFFFW\n\
AFFRFFAAAFFFFFFFFFFFFFFFFFFFFFFFFFFFAAAAAFFFW\n\
AFFFFHAFFFFFFFFFFFFWFFFFHFFFFWFFFFFFFFFFWFFFW\n\
AFFFHHAFFFFFFFFWAAAAAAAAAAAAAWFFFFFFFFFFWFFFW\n\
AIIAAAAFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFWFFFW\n\
AFFAFFFFFFFFFFFWFFFAAAAAAAFFFWWWWWWFFFFFWFFFW\n\
AFFAFFFFFFFAAAAWFFFFFFFFFFFFFFFFFFFFFFFFFFFFW\n\
AFFFFFFFFFFFFFFWFFFFFFFFFFFFFFFFFFFFFFFFFFFFW\n\
AFFFFFFFFFFFFFFWWWWWFFFFFWWWFFFFFFAWWWWHFFFFW\n\
WWWWFFFFFFAFFFFFFWFFFFFFFFFWFFFFFFAFFFFFFFFFW\n\
WFFFFFFFFFAFFFFFFFFFFFTFFFFFFFFFFFQFFFFFFFFFW\n\
WFFFFFFFFFQFFFFFFWFFFFFFFFFWFFFFFFQFFFFFFWWWW\n\
WFFFFHWWWWQFFFFFFWWWFFFFFWWWWWFFFFFFFFFFFFFFQ\n\
WFFFFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFFQ\n\
WFFFFFFFFFFFFFFFFFFFFFFFFFFFFWQQQQFFFFFFFQFFQ\n\
WFFFWFFFFFWWWWWWFFFQQQQQQQFFFWFFFFFFFFFFFQFFQ\n\
WFFFWFFFFFFFFFFWFFFFFFFFFFFFFFFFFFFFFFQQQQZZQ\n\
WFFFWFFFFFFFFFFWQQQQQQQQQQQQQWFFFFFFFFQHHFFFQ\n\
WFFFWFFFFFFFFFFWFFFFHFFFFWFFFFFFFFFFFFQHFFFFQ\n\
WFFFQQQQQFFFFFFFFFFFFFFFFFFFFFFFFFFFQQQFFBFFQ\n\
WFFFFFFFFFFFFFFWFFFFFFFFFWFFFFFFFFFFFFCFFFFFQ\n\
WFFFFFFFFFFFFFFQFFFFFFFFFQFFFFFFFFFFFFCFFFFFQ\n\
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ"); 
  ProcedureAfterMapDataload();
  DoSomethingAfterMapLoad();
  // ----------------------Procedure End--------------------------------------
  //---------------------------------- function used inside the procedure -----------------------------------------
  function ProcedureAfterMapDataload () {
	  
	  // -------------------------- initialize web socket ----------------------------------------
	  ws = new WebSocket("ws://" + ip );
	  ws.addEventListener("error" , function() {
		alert("Failed to connect to server...");
	  })
	  ws.addEventListener("open", function() {
		
	  });
	  ws.addEventListener("message", messageHandler);
	  ws.addEventListener("close", closeHandler);
	  
	  
	  //------------------------ get attribute from 3D Shader program ----------------------------
	  // look up where the vertex, color data needs to go.
	  var positionLocation = gl.getAttribLocation(program, "a_position");
	  let colorLocation = gl.getAttribLocation(program, "a_color");
	
	  // lookup uniforms
	  var matrixLocation = gl.getUniformLocation(program, "u_matrix");
	  
	  //------------------------ get attribute from 2D Shader program ----------------------------
	  // look up where the vertex, color data needs to go.
	  let texpositionLocation = gl.getAttribLocation(textureprogram, "a_position");
	  let texcoordLocation = gl.getAttribLocation(textureprogram, "a_texcoord");
	
	  // lookup uniforms
	  let texmatrixLocation = gl.getUniformLocation(textureprogram, "u_matrix");
	  let textureLocation = gl.getUniformLocation(textureprogram, "u_texture");
	  
	  // ----------------------- Create Buffer ------------------------------
	  let positionBuffer = gl.createBuffer();
	  // Put geometry data into buffer
	  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
	  setMapGeometry(gl, mapinfo);
	  
	  // Put color data into buffer
	  let colorBuffer = gl.createBuffer();
	  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
	  setMapColors(gl, mapinfo);
	  
	  // Put coordinate for texture
	  let texcoordBuffer = gl.createBuffer();
	  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
	  let texcoords = new Array(2* 6 * 32* MAX_PROJECTILE_PER_PLAYER);
	  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW);
	  
	  var tex = gl.createTexture();
	  gl.bindTexture(gl.TEXTURE_2D, tex);
	 
	  let img = new Image();
	  img.src = "https://static.miraheze.org/libertygamewiki/7/7e/UF_Rocket_Red.png";
	  img.crossOrigin = "anonymous";
	  img.addEventListener('load', function() {
		gl.bindTexture(gl.TEXTURE_2D, tex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	  });
	  
	  let redplayertex = gl.createTexture();
	  gl.bindTexture(gl.TEXTURE_2D, redplayertex);
	  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
	  let redplayerimg = new Image();
	  redplayerimg.src = "https://static.miraheze.org/libertygamewiki/c/c7/UF_Player_Red.png";
	  redplayerimg.crossOrigin = "anonymous";
	  redplayerimg.addEventListener('load', function() {
		gl.bindTexture(gl.TEXTURE_2D, redplayertex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, redplayerimg);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	  });
	  
	  let blueplayertex = gl.createTexture();
	  gl.bindTexture(gl.TEXTURE_2D, blueplayertex);
	  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));
	  let blueplayerimg = new Image();
	  blueplayerimg.src = "https://static.miraheze.org/libertygamewiki/d/d7/UF_Player_Blue.png";
	  blueplayerimg.crossOrigin = "anonymous";
	  blueplayerimg.addEventListener('load', function() {
		gl.bindTexture(gl.TEXTURE_2D, blueplayertex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, blueplayerimg);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	  });
	   // dead image
	  let redplayerdeadtex = gl.createTexture();
	  gl.bindTexture(gl.TEXTURE_2D, redplayerdeadtex);
	  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
	  let redplayerdeadimg = new Image();
	  redplayerdeadimg.src = "https://static.miraheze.org/libertygamewiki/f/f4/UF_Player_Dead_Red.png";
	  redplayerdeadimg.crossOrigin = "anonymous";
	  redplayerdeadimg.addEventListener('load', function() {
		gl.bindTexture(gl.TEXTURE_2D, redplayerdeadtex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, redplayerdeadimg);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	  });
	  
	  let blueplayerdeadtex = gl.createTexture();
	  gl.bindTexture(gl.TEXTURE_2D, blueplayerdeadtex);
	  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));
	  let blueplayerdeadimg = new Image();
	  blueplayerdeadimg.crossOrigin = "anonymous";
	  blueplayerdeadimg.src = "https://static.miraheze.org/libertygamewiki/6/6a/UF_Player_Dead_Blue.png";
	  blueplayerdeadimg.addEventListener('load', function() {
		gl.bindTexture(gl.TEXTURE_2D, blueplayerdeadtex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, blueplayerdeadimg);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	  });
	  
	  let explosiontex = gl.createTexture();
	  gl.bindTexture(gl.TEXTURE_2D, explosiontex);
	  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));
	  let explosionimg = new Image();
	  explosionimg.src = "https://static.miraheze.org/libertygamewiki/f/f0/UF_Explosion.png";
	  explosionimg.crossOrigin = "anonymous";
	  explosionimg.addEventListener('load', function() {
		gl.bindTexture(gl.TEXTURE_2D, explosiontex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, explosionimg);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	  });
	  
	  var healthkittex = gl.createTexture();
	  gl.bindTexture(gl.TEXTURE_2D, healthkittex);
	  let healthkitimg = new Image();
	  healthkitimg.src = "https://static.miraheze.org/libertygamewiki/4/46/UF_Healthkit.png";
	  healthkitimg.crossOrigin = "anonymous";
	  healthkitimg.addEventListener('load', function() {
		gl.bindTexture(gl.TEXTURE_2D, healthkittex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, healthkitimg);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	  });
	  
	  // Draw screen
	  requestAnimationFrame(drawScene);
	  
	  // Draw the scene.
	  function drawScene() {
	
	    // Tell WebGL how to convert from clip space to pixels
	    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
		
		gl.enable(gl.CULL_FACE);
		
		gl.enable(gl.BLEND);
		
		gl.enable(gl.DEPTH_TEST);
		
		gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
		// Clear the canvas.
	    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
	    // Tell it to use our program (pair of shaders)
	    gl.useProgram(program);
	
	    // Turn on the attribute
	    gl.enableVertexAttribArray(positionLocation);
		
		gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
		
	    // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
	    var size = 3;          // 3 components per iteration
	    var type = gl.FLOAT;   // the data is 32bit floats
	    var normalize = false; // don't normalize the data
	    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
	    var offset = 0;        // start at the beginning of the buffer
	    gl.vertexAttribPointer( positionLocation, size, type, normalize, stride, offset);
	
		// Turn on the color attribute
	    gl.enableVertexAttribArray(colorLocation);
	
		gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
		
	    // Tell the attribute how to get data out of colorBuffer (ARRAY_BUFFER)
	    var size = 3;                 // 3 components per iteration
	    var type = gl.UNSIGNED_BYTE;  // the data is 8bit unsigned values
	    var normalize = true;         // normalize the data (convert from 0-255 to 0-1)
	    var stride = 0;               // 0 = move forward size * sizeof(type) each iteration to get the next position
	    var offset = 0;               // start at the beginning of the buffer
	    gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
		
	    // Compute the matrix
	    var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
	    var zNear = 1;
	    var zFar = 10000;
	    var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);
	    
	    projectionMatrix = m4.xRotate(projectionMatrix, player.rotation[0]);
	    projectionMatrix = m4.yRotate(projectionMatrix, player.rotation[1]);
	    projectionMatrix = m4.zRotate(projectionMatrix, player.rotation[2]);
	    projectionMatrix = m4.scale(projectionMatrix, player.scale[0], player.scale[1], player.scale[2]);
		
		// Compute a matrix for the camera
		player.cameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
	    player.cameraMatrix = m4.yRotation(player.direction);
	    player.cameraMatrix = m4.translate(player.cameraMatrix, -player.GetCenterPosition()[0], player.height, -player.GetCenterPosition()[1]);
	
	    // Compute a view projection matrix
	    var viewProjectionMatrix = m4.multiply(projectionMatrix, player.cameraMatrix);
	
		var matrix = m4.translate(viewProjectionMatrix, -player.GetCenterPosition()[0], player.height, -player.GetCenterPosition()[1]);
	    // Set the matrix.
	    gl.uniformMatrix4fv(matrixLocation, false, matrix);
		// --------------------------------------------------------draw!!!!-------------------------------------------------
		// Draw map objects
		// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
		offset = 0;
	    var count = (2+mapinfo.mapwallcounter) * 6;
		
		gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
		let tempArray = mapinfo.polygons;
		
		let polygonArray = new Float32Array(tempArray);
		gl.bufferSubData(gl.ARRAY_BUFFER, 0, polygonArray);
	    // Bind the color buffer.
		gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
		tempArray = mapinfo.mapColors;
		let mapColorArray = new Uint8Array( tempArray );
		gl.bufferSubData(gl.ARRAY_BUFFER, 0, mapColorArray);
		
	    gl.drawArrays(gl.TRIANGLES, offset, count);
		
		// Draw rays
		for (let playeritem of playerlist) {
			for (let ray of playeritem.raylist) {
				if (playeritem.raylist.indexOf(ray) > 10) continue;
				else if (ray.visible) {
					tempArray = [];
					gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
					tempArray = tempArray.concat([2*ray.startpos[0], ray.startpos[2], 2*ray.startpos[1]]);
					tempArray = tempArray.concat([2*ray.endpos[0], 0, 2*ray.endpos[1]]);
					polygonArray = new Float32Array(tempArray);
					gl.bufferSubData(gl.ARRAY_BUFFER, 0, polygonArray);
					gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
					tempArray =[255, 255, 0, 255, 255, 0];
					mapColorArray = new Uint8Array( tempArray );
					gl.bufferSubData(gl.ARRAY_BUFFER, 0, mapColorArray);
					// we have already set the matrix, so directly draw
					gl.drawArrays(gl.LINES, 0, 2);
				}
			}
		}
		// Draw projectiles
		// Tell WebGL to use our shader program pair
		gl.useProgram(textureprogram);
		gl.enableVertexAttribArray(texpositionLocation);
		gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
		// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
		var size = 3;          // 3 components per iteration
		var type = gl.FLOAT;   // the data is 32bit floats
		var normalize = false; // don't normalize the data
		var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
		var offset = 0;        // start at the current position
		gl.vertexAttribPointer( texpositionLocation, size, type, normalize, stride, offset);
		
		// Setup the attributes to pull data from our buffers
		gl.enableVertexAttribArray(texcoordLocation);
		gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
		gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0);
		
		// fill the polygon buffer
		gl.bindTexture(gl.TEXTURE_2D, tex);
		gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
		tempArray = [];
		count = 0;
		for (let proj of projectilelist) {
			tempArray = tempArray.concat(proj.vertexlist);
			count += 4*6;
		}
		polygonArray = new Float32Array(tempArray);
		gl.bufferSubData(gl.ARRAY_BUFFER, 0, polygonArray);
		// Tell the shader to get the texture from texture unit 0
		gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
		tempArray = [];
		for (let proj of projectilelist) {
			tempArray = tempArray.concat([0.5, 0, 0.5, 0.5, 1, 0.5, 0.5, 0, 1, 0.5, 1, 0]);
			tempArray = tempArray.concat([0.5, 0.5, 0.5, 1.0, 1, 1, 0.5, 0.5, 1, 1, 1, 0.5]);
			tempArray = tempArray.concat([0, 0, 0.5, 0, 0, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5]);
			tempArray = tempArray.concat([0, 0.5, 0, 1, 0.5, 1, 0, 0.5, 0.5, 1, 0.5, 0.5]);
		}
		let texcoordinates = new Float32Array( tempArray );
		gl.bufferSubData(gl.ARRAY_BUFFER, 0, texcoordinates);
		
		// Set the matrix.
		gl.uniformMatrix4fv(texmatrixLocation, false, matrix);
		gl.uniform1i(textureLocation, 0);
		gl.drawArrays(gl.TRIANGLES, 0, count);
		
		// Draw Player
		
		for (let playeritem of playerlist) {
			if (playeritem.playerID === player.playerID) continue;
			tempArray = [];
			count = 4*6;
			// vertex
			if (playeritem.team === 1) {
				if (playeritem.isdead) gl.bindTexture(gl.TEXTURE_2D, redplayerdeadtex);
				else gl.bindTexture(gl.TEXTURE_2D, redplayertex);
			} else {
				if (playeritem.isdead) gl.bindTexture(gl.TEXTURE_2D, blueplayerdeadtex);
				else gl.bindTexture(gl.TEXTURE_2D, blueplayertex);
			}
			gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
			playeritem.vertexlist = GeneratePlaneVertex(playeritem.GetCenterPosition(), 32, m4.lookAt([playeritem.GetCenterPosition()[0], playeritem.height, playeritem.GetCenterPosition()[1]], [player.GetCenterPosition()[0], player.height, player.GetCenterPosition()[1]], up));
			tempArray = tempArray.concat(playeritem.vertexlist);
			polygonArray = new Float32Array(tempArray);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, polygonArray);
			// texture coordinate
			gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
			tempArray = [];
			if (radToDeg(playeritem.direction - player.direction) % 360 >= 315 || ( radToDeg(playeritem.direction - player.direction) % 360 >= -45 && radToDeg(playeritem.direction - player.direction) % 360 < 45 ) || radToDeg(playeritem.direction - player.direction) % 360 < -315 ) {
				tempArray = tempArray.concat([0.5, 0, 0.5, 0.5, 1, 0.5, 0.5, 0, 1, 0.5, 1, 0]);
				tempArray = tempArray.concat([0.5, 0, 0.5, 0.5, 1, 0.5, 0.5, 0, 1, 0.5, 1, 0]);
				tempArray = tempArray.concat([0.5, 0, 0.5, 0.5, 1, 0.5, 0.5, 0, 1, 0.5, 1, 0]);
				tempArray = tempArray.concat([0.5, 0, 0.5, 0.5, 1, 0.5, 0.5, 0, 1, 0.5, 1, 0]);
			} else if ((radToDeg(playeritem.direction - player.direction) % 360 >= 45 && radToDeg(playeritem.direction - player.direction) % 360 < 135) ||(radToDeg(playeritem.direction - player.direction) % 360 < -225 && radToDeg(playeritem.direction - player.direction) % 360 >= -315)) {
				tempArray = tempArray.concat([0, 0.5, 0, 1, 0.5, 1, 0, 0.5, 0.5, 1, 0.5, 0.5]);
				tempArray = tempArray.concat([0, 0.5, 0, 1, 0.5, 1, 0, 0.5, 0.5, 1, 0.5, 0.5]);
				tempArray = tempArray.concat([0, 0.5, 0, 1, 0.5, 1, 0, 0.5, 0.5, 1, 0.5, 0.5]);
				tempArray = tempArray.concat([0, 0.5, 0, 1, 0.5, 1, 0, 0.5, 0.5, 1, 0.5, 0.5]);
			} else if ((radToDeg(playeritem.direction - player.direction) % 360 >= 135 && radToDeg(playeritem.direction - player.direction) % 360 < 225) || (radToDeg(playeritem.direction - player.direction) % 360  < -135 && radToDeg(playeritem.direction - player.direction) % 360 >= -225)) {
				tempArray = tempArray.concat([0, 0, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 0.5, 0.5, 0]);
				tempArray = tempArray.concat([0, 0, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 0.5, 0.5, 0]);
				tempArray = tempArray.concat([0, 0, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 0.5, 0.5, 0]);
				tempArray = tempArray.concat([0, 0, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 0.5, 0.5, 0]);
			} else {
				tempArray = tempArray.concat([0.5, 0.5, 0.5, 1, 1, 1, 0.5, 0.5, 1, 1, 1, 0.5]);
				tempArray = tempArray.concat([0.5, 0.5, 0.5, 1, 1, 1, 0.5, 0.5, 1, 1, 1, 0.5]);
				tempArray = tempArray.concat([0.5, 0.5, 0.5, 1, 1, 1, 0.5, 0.5, 1, 1, 1, 0.5]);
				tempArray = tempArray.concat([0.5, 0.5, 0.5, 1, 1, 1, 0.5, 0.5, 1, 1, 1, 0.5]);
			}
			texcoordinates = new Float32Array( tempArray );
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, texcoordinates);
			// Set the matrix and draw.
			gl.drawArrays(gl.TRIANGLES, 0, count);
		}
		
		// Draw Healthkit
		gl.bindTexture(gl.TEXTURE_2D, healthkittex);
		gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
		tempArray = [];
		count = 0;
		for (let kit of mapinfo.maphealthkitlist) {
			if (kit.isSpawned) {
				tempArray = tempArray.concat(kit.vertexlist);
				count += 4*6;
			}
		}
		polygonArray = new Float32Array(tempArray);
		gl.bufferSubData(gl.ARRAY_BUFFER, 0, polygonArray);
		// Tell the shader to get the texture from texture unit 0
		gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
		tempArray = [];
		for (let kit of mapinfo.maphealthkitlist) {
			if (kit.isSpawned) {
				tempArray = tempArray.concat([0.5, 0, 0.5, 0.5, 1, 0.5, 0.5, 0, 1, 0.5, 1, 0]);
				tempArray = tempArray.concat([0.5, 0.5, 0.5, 1.0, 1, 1, 0.5, 0.5, 1, 1, 1, 0.5]);
				tempArray = tempArray.concat([0, 0, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 0.5, 0.5, 0]);
				tempArray = tempArray.concat([0, 0.5, 0, 1, 0.5, 1, 0, 0.5, 0.5, 1, 0.5, 0.5]);
			}
		}
		texcoordinates = new Float32Array( tempArray );
		gl.bufferSubData(gl.ARRAY_BUFFER, 0, texcoordinates);
		
		// Set the matrix.
		gl.uniformMatrix4fv(texmatrixLocation, false, matrix);
		gl.uniform1i(textureLocation, 0);
		gl.drawArrays(gl.TRIANGLES, 0, count);
		
		// Draw Explosion
		for (let explosion of explosionlist) {
			if (explosion.active === false) {
				continue;
			}
			tempArray = [];
			count = 4*6;
			gl.bindTexture(gl.TEXTURE_2D, explosiontex);
			// vertex
			gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
			explosion.vertexlist = GeneratePlaneVertex(explosion.GetCenterPosition(), 32, m4.lookAt([explosion.GetCenterPosition()[0], explosion.height, explosion.GetCenterPosition()[1]], [player.GetCenterPosition()[0], player.height, player.GetCenterPosition()[1]], up));
			tempArray = tempArray.concat(explosion.vertexlist);
			polygonArray = new Float32Array(tempArray);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, polygonArray);
			// texture coordinate
			gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
			tempArray = [];
			if ( explosion.counter === 0 ) {
				tempArray = tempArray.concat([0, 0, 0, 1, 0.25, 1, 0, 0, 0.25, 1, 0.25, 0]);
				tempArray = tempArray.concat([0, 0, 0, 1, 0.25, 1, 0, 0, 0.25, 1, 0.25, 0]);
				tempArray = tempArray.concat([0, 0, 0, 1, 0.25, 1, 0, 0, 0.25, 1, 0.25, 0]);
				tempArray = tempArray.concat([0, 0, 0, 1, 0.25, 1, 0, 0, 0.25, 1, 0.25, 0]);
			} else if (explosion.counter === 1) {
				tempArray = tempArray.concat([0.25, 0, 0.25, 1, 0.5, 1, 0.25, 0, 0.5, 1, 0.5, 0]);
				tempArray = tempArray.concat([0.25, 0, 0.25, 1, 0.5, 1, 0.25, 0, 0.5, 1, 0.5, 0]);
				tempArray = tempArray.concat([0.25, 0, 0.25, 1, 0.5, 1, 0.25, 0, 0.5, 1, 0.5, 0]);
				tempArray = tempArray.concat([0.25, 0, 0.25, 1, 0.5, 1, 0.25, 0, 0.5, 1, 0.5, 0]);
			} else if (explosion.counter === 2) {
				tempArray = tempArray.concat([0.5, 0, 0.5, 1, 0.75, 1, 0.5, 0, 0.75, 1, 0.75, 0]);
				tempArray = tempArray.concat([0.5, 0, 0.5, 1, 0.75, 1, 0.5, 0, 0.75, 1, 0.75, 0]);
				tempArray = tempArray.concat([0.5, 0, 0.5, 1, 0.75, 1, 0.5, 0, 0.75, 1, 0.75, 0]);
				tempArray = tempArray.concat([0.5, 0, 0.5, 1, 0.75, 1, 0.5, 0, 0.75, 1, 0.75, 0]);
			} else {
				tempArray = tempArray.concat([0.75, 0, 0.75, 1, 1, 1, 0.75, 0, 1, 1, 1, 0]);
				tempArray = tempArray.concat([0.75, 0, 0.75, 1, 1, 1, 0.75, 0, 1, 1, 1, 0]);
				tempArray = tempArray.concat([0.75, 0, 0.75, 1, 1, 1, 0.75, 0, 1, 1, 1, 0]);
				tempArray = tempArray.concat([0.75, 0, 0.75, 1, 1, 1, 0.75, 0, 1, 1, 1, 0]);
				explosion.active = false;
			}
			explosion.counter += 1;
			texcoordinates = new Float32Array( tempArray );
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, texcoordinates);
			// Set the matrix and draw.
			gl.drawArrays(gl.TRIANGLES, 0, count);
		}
		for (let explosion of explosionlist) {
			if (explosion.active === false) explosionlist.splice(explosionlist.indexOf(explosion), 1);
		}
		// draw ui
		DrawUI();
		
		requestAnimationFrame(drawScene);
	  }
  }
  // ---------End----------
}
// ---------------------------------Matrix control -------------------------------
var m4 = {
  orthographic: function(left, right, bottom, top, near, far) {
    return [
      2 / (right - left), 0, 0, 0,
      0, 2 / (top - bottom), 0, 0,
      0, 0, 2 / (near - far), 0,
 
      (left + right) / (left - right),
      (bottom + top) / (bottom - top),
      (near + far) / (near - far),
      1,
    ];
  },
  perspective: function(fieldOfViewInRadians, aspect, near, far) {
    var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
    var rangeInv = 1.0 / (near - far);

    return [
      f / aspect, 0, 0, 0,
      0, f, 0, 0,
      0, 0, (near + far) * rangeInv, -1,
      0, 0, near * far * rangeInv * 2, 0
    ];
  },

  projection: function(width, height, depth) {
    // Note: This matrix flips the Y axis so 0 is at the top.
    return [
       2 / width, 0, 0, 0,
       0, -2 / height, 0, 0,
       0, 0, 2 / depth, 0,
      -1, 1, 0, 1,
    ];
  },

  multiply: function(a, b) {
    var a00 = a[0 * 4 + 0];
    var a01 = a[0 * 4 + 1];
    var a02 = a[0 * 4 + 2];
    var a03 = a[0 * 4 + 3];
    var a10 = a[1 * 4 + 0];
    var a11 = a[1 * 4 + 1];
    var a12 = a[1 * 4 + 2];
    var a13 = a[1 * 4 + 3];
    var a20 = a[2 * 4 + 0];
    var a21 = a[2 * 4 + 1];
    var a22 = a[2 * 4 + 2];
    var a23 = a[2 * 4 + 3];
    var a30 = a[3 * 4 + 0];
    var a31 = a[3 * 4 + 1];
    var a32 = a[3 * 4 + 2];
    var a33 = a[3 * 4 + 3];
    var b00 = b[0 * 4 + 0];
    var b01 = b[0 * 4 + 1];
    var b02 = b[0 * 4 + 2];
    var b03 = b[0 * 4 + 3];
    var b10 = b[1 * 4 + 0];
    var b11 = b[1 * 4 + 1];
    var b12 = b[1 * 4 + 2];
    var b13 = b[1 * 4 + 3];
    var b20 = b[2 * 4 + 0];
    var b21 = b[2 * 4 + 1];
    var b22 = b[2 * 4 + 2];
    var b23 = b[2 * 4 + 3];
    var b30 = b[3 * 4 + 0];
    var b31 = b[3 * 4 + 1];
    var b32 = b[3 * 4 + 2];
    var b33 = b[3 * 4 + 3];
    return [
      b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
      b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
      b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
      b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
      b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
      b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
      b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
      b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
      b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
      b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
      b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
      b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
      b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
      b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
      b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
      b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
    ];
  },

  translation: function(tx, ty, tz) {
    return [
       1,  0,  0,  0,
       0,  1,  0,  0,
       0,  0,  1,  0,
       tx, ty, tz, 1,
    ];
  },

  xRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
      1, 0, 0, 0,
      0, c, s, 0,
      0, -s, c, 0,
      0, 0, 0, 1,
    ];
  },

  yRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
      c, 0, -s, 0,
      0, 1, 0, 0,
      s, 0, c, 0,
      0, 0, 0, 1,
    ];
  },

  zRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
       c, s, 0, 0,
      -s, c, 0, 0,
       0, 0, 1, 0,
       0, 0, 0, 1,
    ];
  },

  scaling: function(sx, sy, sz) {
    return [
      sx, 0,  0,  0,
      0, sy,  0,  0,
      0,  0, sz,  0,
      0,  0,  0,  1,
    ];
  },
  translate: function(m, tx, ty, tz) {
    return m4.multiply(m, m4.translation(tx, ty, tz));
  },

  xRotate: function(m, angleInRadians) {
    return m4.multiply(m, m4.xRotation(angleInRadians));
  },

  yRotate: function(m, angleInRadians) {
    return m4.multiply(m, m4.yRotation(angleInRadians));
  },

  zRotate: function(m, angleInRadians) {
    return m4.multiply(m, m4.zRotation(angleInRadians));
  },

  scale: function(m, sx, sy, sz) {
    return m4.multiply(m, m4.scaling(sx, sy, sz));
  },
  inverse: function(m) {
    var m00 = m[0 * 4 + 0];
    var m01 = m[0 * 4 + 1];
    var m02 = m[0 * 4 + 2];
    var m03 = m[0 * 4 + 3];
    var m10 = m[1 * 4 + 0];
    var m11 = m[1 * 4 + 1];
    var m12 = m[1 * 4 + 2];
    var m13 = m[1 * 4 + 3];
    var m20 = m[2 * 4 + 0];
    var m21 = m[2 * 4 + 1];
    var m22 = m[2 * 4 + 2];
    var m23 = m[2 * 4 + 3];
    var m30 = m[3 * 4 + 0];
    var m31 = m[3 * 4 + 1];
    var m32 = m[3 * 4 + 2];
    var m33 = m[3 * 4 + 3];
    var tmp_0  = m22 * m33;
    var tmp_1  = m32 * m23;
    var tmp_2  = m12 * m33;
    var tmp_3  = m32 * m13;
    var tmp_4  = m12 * m23;
    var tmp_5  = m22 * m13;
    var tmp_6  = m02 * m33;
    var tmp_7  = m32 * m03;
    var tmp_8  = m02 * m23;
    var tmp_9  = m22 * m03;
    var tmp_10 = m02 * m13;
    var tmp_11 = m12 * m03;
    var tmp_12 = m20 * m31;
    var tmp_13 = m30 * m21;
    var tmp_14 = m10 * m31;
    var tmp_15 = m30 * m11;
    var tmp_16 = m10 * m21;
    var tmp_17 = m20 * m11;
    var tmp_18 = m00 * m31;
    var tmp_19 = m30 * m01;
    var tmp_20 = m00 * m21;
    var tmp_21 = m20 * m01;
    var tmp_22 = m00 * m11;
    var tmp_23 = m10 * m01;

    var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
        (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
    var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
        (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
    var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
        (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
    var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
        (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);

    var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);

    return [
      d * t0,
      d * t1,
      d * t2,
      d * t3,
      d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
            (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
      d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
            (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
      d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
            (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
      d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
            (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)),
      d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
            (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
      d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
            (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
      d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
            (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
      d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
            (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)),
      d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
            (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
      d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
            (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
      d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
            (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
      d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
            (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))
    ];
  },
  vectorMultiply: function(v, m) {
    var dst = [];
    for (var i = 0; i < 4; ++i) {
      dst[i] = 0.0;
      for (var j = 0; j < 4; ++j) {
        dst[i] += v[j] * m[j * 4 + i];
      }
    }
    return dst;
  },
  getRight: function (m) {
	return [m[8], m[9], m[10], 0];
  },
  getForward: function(m) {
	return [m[0], m[1], m[2], 0];
  },
  getUp: function (m) {
	return [m[4], m[5], m[6], 0];
  },
  lookAt: function(cameraPosition, target, up) { // all of the parameters is vector
    var zAxis = this.normalize( this.subtractVectors(cameraPosition, target));
    var xAxis = this.normalize(this.cross(up, zAxis));
    var yAxis = this.normalize(this.cross(zAxis, xAxis));
 
    return [
       xAxis[0], xAxis[1], xAxis[2], 0,
       yAxis[0], yAxis[1], yAxis[2], 0,
       zAxis[0], zAxis[1], zAxis[2], 0,
       cameraPosition[0],
       cameraPosition[1],
       cameraPosition[2],
       1,
    ];
  },
  cross: function (a, b) {
	return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
  },
  subtractVectors: function (a, b) {
	return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
  },
  normalize: function (v) {
	  var length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
	  // make sure we don't divide by 0.
	  if (length > 0.00001) {
		return [v[0] / length, v[1] / length, v[2] / length];
	  } else {
		return [0, 0, 0];
	  }
  },
};

// Fill the buffer with the values of "W" and "F".
function setMapGeometry(gl, mapInfo) {
  let temparray = mapInfo.polygons.concat( new Array(6* MAX_RAY_NUM)); // for ray
  temparray = temparray.concat (new Array(3* 6 * 4 * MAX_PROJECTILE_NUMBER )); // for projectile
  temparray = temparray.concat (new Array(3*6*32)); // for player
  temparray = temparray.concat(new Array(3* 6 * MAX_PROJECTILE_NUMBER )); // for explosion
  let polygonArray = new Float32Array(temparray);
  gl.bufferData(
      gl.ARRAY_BUFFER,
      polygonArray,
      gl.STATIC_DRAW);
  return;
}

function setMapColors(gl, mapInfo) {
  for (let object of mapInfo.mapobjectlist){
	  if (object.constructor === Door) {
		  if (object.team === 1) {
				mapInfo.mapColors = mapInfo.mapColors.concat([ 
				146, 25, 14,
				146, 25, 14,
				146, 25, 14,
				146, 25, 14,
				146, 25, 14,
				146, 25, 14,
				//wall 2
				146, 25, 14,
				146, 25, 14,
				146, 25, 14,
				146, 25, 14,
				146, 25, 14,
				146, 25, 14
				]);
		  } else if (object.team === 2) {
			    mapInfo.mapColors = mapInfo.mapColors.concat([ 
				50, 14, 146,
				50, 14, 146,
				50, 14, 146,
				50, 14, 146,
				50, 14, 146,
				50, 14, 146,
				//wall 2
				50, 14, 146,
				50, 14, 146,
				50, 14, 146,
				50, 14, 146,
				50, 14, 146,
				50, 14, 146
				]);
		  } else {
			    mapInfo.mapColors = mapInfo.mapColors.concat([ 
				146, 125, 14,
				146, 125, 14,
				146, 125, 14,
				146, 125, 14,
				146, 125, 14,
				146, 125, 14,
				//wall 2
				146, 125, 14,
				146, 125, 14,
				146, 125, 14,
				146, 125, 14,
				146, 125, 14,
				146, 125, 14
				]);
		  }
	  } else if (object.constructor === GameObject) {
		if (object.flag === 0) {
			mapInfo.mapColors = mapInfo.mapColors.concat([ 
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			//wall 2
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			//wall 3
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			//wall 4
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14,
			50, 146, 14
			]);
		} else if (object.flag === 2) {
			mapInfo.mapColors = mapInfo.mapColors.concat([ 
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			//wall 2
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			//wall 3
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			//wall 4
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146,
			50, 14, 146
			]);
		} else if (object.flag === 1) {
			mapInfo.mapColors = mapInfo.mapColors.concat([ 
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			//wall 2
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			//wall 3
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			//wall 4
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14,
			146, 25, 14
			]);
		}
	  }
  }
  let temparray = mapInfo.mapColors.concat(new Array(2*3* MAX_RAY_NUM));
  temparray = temparray.concat (new Array(3* 6 * 4 * MAX_PROJECTILE_NUMBER )); // for projectile
  temparray = temparray.concat (new Array(3*6*32)); // for player
  temparray = temparray.concat(new Array(3* 6 * MAX_PROJECTILE_NUMBER )); // for explosion
  let mapColorArray = new Uint8Array(temparray);
  gl.bufferData(gl.ARRAY_BUFFER, mapColorArray, gl.STATIC_DRAW);
  return;
}

// ---------------------------------------HTML document managing function-------------------------------------
let ip = "";
let playername = "";
let ws;
let killloglist = [];

function killlog_Timeout() {
	if (killloglist.length > 0) {
		killloglist.splice(0, 1);
	}
	return;
}

function DoInitialJob () {
	createCanvasAndShader ();
	EngineMain();
}

function closeHandler() {
	alert(serverclosereason);
}

///////////////////////////////////////////////////////////////////////////////////////////////
// ---------------------------------Client-side message----------------------------------------
// --------------------------------------------------------------------------------------------
// -------------------------------type of receiving message------------------------------------
// 0: list of players (has heavy net load, do not use this frequently)
// 1: connection notice
// 2: disconnection notice
// 3: connection rejected(due to server player number limit or else)
// 4: reserved for chat
// 5: player's current status renewal
// 6: received fired ray
// 7: received fired projectilelist
// 8: initial player information
// 9: reserved for game objective(GameRule related)
// 10: kill log event
// 11: damaging event
// 12: explosion event
// 13: reserved for list of building
// 14: map version request
// 15: player name change event
// 16: reply to teammate number request
// 17: Healthkit touch event
// 18: sound play request
// 19: sound stop request
// 20: respawn event
// 21: scriptversion request
// 22: weapon change notice
// --------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------
// -------------------------------type of sending message--------------------------------------
// 0: request for all player's status
// 1: map version
// 2: player name set request
// 3: class change request
// 4: reserved for chat
// 5: player current position, looking direction info
// 6: fired ray info
// 7: fired projectile info
// 8: reserved for all building's current status
// 9: reserved for building repair
// 10: reserved for building destruction
// 11: heal event request
// 12: teammate number request
// 13: reload request
// 14: script version
// 15: Special skill request(include building construction, zoom in, etc.)
// 16: Weapon change request
// --------------------------------------------------------------------------------------------

let serverclosereason = "서버로부터 연결이 끊겼습니다";


function messageHandler(message) {
	let messageinfo = JSON.parse(message.data);
	switch (messageinfo.type) {
		case 0:
			playerlist = [];
			for (let playeritem of messageinfo.playerlist){
				playerlist.push(new Player(playeritem.pos, 12, playeritem.direction, playeritem.team, playeritem.health, playeritem.id, playeritem.isdead, playeritem.playername, playeritem.classnumber));
			}
			break;
		case 1:
			if (player.playerID != messageinfo.id) { // other player is connected
				playerlist.push(new Player(messageinfo.pos, 12, messageinfo.direction, messageinfo.team, messageinfo.health, messageinfo.id));
			} else { // ok, this is same with my player information, I have to set a reference of player variable to specific element of playerlist
				playerlist[messageinfo.id] = player;
			}
			if (messageinfo.team === 1) {
				redteamnum += 1;
			}
			else if (messageinfo.team  === 2) {
				blueteamnum += 1;
			}
			break;
		case 2:
			var index = messageinfo.id;
			let teamnumber = playerlist[index].team;
			playerlist.splice(index, 1);
			if (teamnumber === 1) {
				redteamnum -= 1;
			}
			else if (teamnumber === 2) {
				blueteamnum -= 1;
			}
			//arrange the new playerlist
			for (let i in playerlist) {
				playerlist[i].PlayerID = i;
			}
			break;
		case 3:
			serverclosereason = messageinfo.message;
			ws.close();
			break;
		case 5:
			if (messageinfo.id === player.playerID){
				player.pos = messageinfo.pos;
				player.direction = messageinfo.direction;
				player.team = messageinfo.team;
				player.healthpoint = messageinfo.health;
				player.canbedamaged = messageinfo.canbedamaged;
				player.classnumber = messageinfo.classnum;
				player.isdead = messageinfo.isdead;
			}
			playerlist[messageinfo.id].pos = messageinfo.pos;
			playerlist[messageinfo.id].direction = messageinfo.direction;
			playerlist[messageinfo.id].team = messageinfo.team;
			playerlist[messageinfo.id].healthpoint = messageinfo.health;
			playerlist[messageinfo.id].canbedamaged = messageinfo.canbedamaged;
			playerlist[messageinfo.id].classnumber = messageinfo.classnum;
			playerlist[messageinfo.id].isdead = messageinfo.isdead;
			break;
		case 6:
			playerlist[messageinfo.id].raylist = [];
			for (let rayinfo of messageinfo.raylist) {
				playerlist[messageinfo.id].raylist.push(new Ray(rayinfo.startpos, rayinfo.endpos));
			}
			for (let ray of playerlist[messageinfo.id].raylist){
				ray.DrawRay();
			}
			break;
		case 7:
			projectilelist = [];
			for (let projectile of messageinfo.projectilelist) {
				switch (projectile.type) {
					case 0:
						projectilelist.push(new Projectile_Rocket (projectile.pos, 8, projectile.direction, projectile.team, playerlist[projectile.shooterid], "http://libertyga.me/images/4/46/UF_Healthkit.png", projectile.type));
						break;
					default:
						break;
				}
			}
			break;
		case 8:
			player = new Player(messageinfo.pos, 12, messageinfo.direction, messageinfo.team, messageinfo.health, messageinfo.id, true, playername, 0);
			player.canbedamaged = messageinfo.canbedamaged;
			player.classnumber = messageinfo.classnum;
			player.isdead = messageinfo.isdead;
			ws.send(JSON.stringify({
				type: 2,
				id: messageinfo.id,
				newname: playername
			}));
			break;
		case 9:
			// reserved for gamerule control
			break;
		case 10:
			let killtext = playerlist[messageinfo.attackerID].playername + " killed " + playerlist[messageinfo.victimID].playername + " with ";
			switch (messageinfo.killtype) {
				case -1: // suicide
					killtext = playerlist[messageinfo.attackerID].playername + " suicided";
					break;
				case 0: // killed by pistol
					killtext = killtext + "pistol";
					break;
				case 1: // reserved for nail gun
					break;
				case 2: // reserved for short shotgun
					break;
				case 3: // reserved
					break;
				case 4: // killed by power shotgun
					killtext = killtext + "power shotgun";
					break;
				case 5: // killed by minigun
					killtext = killtext + "minigun";
					break;
				case 6: // killed by rocket launcher explosion
					killtext = killtext + "rocket launcher";
					break;
				default:
					killtext = playerlist[messageinfo.attackerID].playername + " killed " + playerlist[messageinfo.victimID].playername;
					console.log("unknown type kill event" + messageinfo.killtype);
					break;
			}
			if (killloglist.length >= 10) {
				killloglist.splice(0, 1);
			}
			killloglist.push(killtext);
			setTimeout(killlog_Timeout, 4000);
			break;
		case 11: // damaging event
			if (player.playerID === messageinfo.victimID){
				player.healthpoint -= messageinfo.damage;
				player.isdead = messageinfo.isdead;
				if (messageinfo.isdead) { // if player is dead
					player.healthpoint = 0;
					inputhandler.ReleasePlayer();
					player.ReloadWeapon = () => {
						return;
					};
					player.FireWeapon = () =>{
						return;
					};
					player.DoSpecialSkill = () => {
						return;
					};
					player.ReleaseFire = () => {
						return;
					};
					clearTimeout(player.deadtimer);
					player.deadtimer = setTimeout(deadStateHandler, 4000);
				}
			} else {
				playerlist[messageinfo.victimID].healthpoint -= messageinfo.damage;
				playerlist[messageinfo.victimID].isdead = messageinfo.isdead;
				if (messageinfo.isdead) {
					playerlist[messageinfo.victimID].healthpoint = 0;
				}
			}
			break;
		case 12: // reserved for explosion draw
			// explosiontype 0: damaging explosion, 1:healing explosion
			explosionlist.push(new Explosion(messageinfo.pos, messageinfo.radius, messageinfo.explosiontype));
			break;
		case 13: // reserved for the list of buildings
			break;
		case 14:
			ws.send(JSON.stringify({type:1, id:player.playerID, mapversion:mapinfo.mapversion}));
			break;
		case 15:
			playerlist[messageinfo.id].playername = messageinfo.newname;
			console.log("client "+ messageinfo.id +" changed name to "+ messageinfo.newname);
			break;
		case 16:
			redteamnum = messageinfo.redteamnum;
			blueteamnum = messageinfo.blueteamnum;
			break;
		case 17:
			if (messageinfo.id === player.playerID) {
				player.healthpoint = player.maxhealthpoint;
			}
			mapinfo.maphealthkitlist[messageinfo.healthkitid].isSpawned = messageinfo.isSpawned;
			break;
		case 18: // soundplay start
			break;
		case 19: // soundplay stop
			break;
		case 20: // respawn
			if (player.playerID === messageinfo.id) {
				player.pos = messageinfo.respawnpos;
				player.isdead = false;
			} else {
				playerlist[messageinfo.id].pos = messageinfo.respawnpos;
				playerlist[messageinfo.id].isdead = false;
			}
			break;
		case 21:
			ws.send(JSON.stringify({ type: 14, id: player.playerID, version: scriptversion}));
			break;
		case 22:
			if (player.playerID != messageinfo.id) {
				playerlist[messageinfo.id].currentweapon = messageinfo.currentweapon;
			}
			break;
		default:
			console.log("received message type "+ messageinfo.type);
			break;
	}
}

function deadStateHandler() {
	player.isdead = false;
	player.healthpoint = player.maxhealthpoint;
	inputhandler.GrabInput(player);
	player.ReloadWeapon = () => {
		if (player.currentweapon === 0) {
			player.secondweapon.ReloadWeapon();
		} else {
			player.firstweapon.ReloadWeapon();
		}
	};
	player.FireWeapon = () =>{
		if (player.currentweapon === 0) {
			player.secondweapon.FireWeapon();
		} else {
			player.firstweapon.FireWeapon();
		}
	};
	player.DoSpecialSkill = () => {
		if (player.currentweapon === 0) {
			player.secondweapon.DoSpecialSkill();
		} else {
			player.firstweapon.DoSpecialSkill();
		}
	};
	player.ReleaseFire = () => {
		if (player.currentweapon === 0) {
			player.secondweapon.ReleaseFire();
		} else {
			player.firstweapon.ReleaseFire();
		}
	};
	if (player.team === 1) {
		player.pos = [mapinfo.RedSpawn[0] + 4 , mapinfo.RedSpawn[1] + 4 ];
	} else {
		player.pos = [mapinfo.BlueSpawn[0] + 4 , mapinfo.BlueSpawn[1] + 4 ];
	}
}

function createIPInputBox () {
	let gamedisplay = document.getElementById("mw-content-text");
	gamedisplay.style.height = "1080px";
	let paragraph = document.createElement("p");
	paragraph.setAttribute("id", "infoparagraph");
	paragraph.innerHTML = "IP주소와 포트를 쓰고 그 오른쪽 칸에 닉네임를 입력해주세요."
	gamedisplay.appendChild(paragraph);
	let mainInputBox = document.createElement("input");
	mainInputBox.setAttribute("id", "input");
	mainInputBox.setAttribute("placeholder", "(ex: 127.0.0.1:8080)");
	mainInputBox.addEventListener("keydown", CheckInputBox);
	gamedisplay.appendChild(mainInputBox);
	let nameInputBox = document.createElement("input");
	nameInputBox.setAttribute("id", "playername");
	nameInputBox.setAttribute("placeholder", "Game Nickname");
	nameInputBox.addEventListener("keydown", CheckInputBox);
	gamedisplay.appendChild(nameInputBox);
	
	function CheckInputBox(e) {
		if (e.keyCode === 13) { // Pressing Enter key
			if (document.getElementById("input").value === "" || document.getElementById("playername").value === "" ) {
				alert("데이터가 없습니다! 빈칸을 내버려두지 마세요!")
				return;
			}
			ip = document.getElementById("input").value;
			playername = document.getElementById("playername").value;
			document.getElementById("input").value = "";
			document.getElementById("playername").value = "";
			document.getElementById("mw-content-text").removeChild(document.getElementById("input"));
			document.getElementById("mw-content-text").removeChild(document.getElementById("playername"));
			document.getElementById("mw-content-text").removeChild(document.getElementById("infoparagraph"));
			DoInitialJob();
		}
	}
}

function createCanvasAndShader () {
	let gamedisplay = document.getElementById("mw-content-text");
	gamedisplay.height = "1080px";
	let baseDiv = document.createElement("div");
	baseDiv.setAttribute("id", "baseDiv");
	baseDiv.style = "z-index:0;display:block;";
	baseDiv.style.position = "absolute";
	baseDiv.width = "1440px";
	baseDiv.height = "1080px";
	baseDiv.style.top = "0px";
	baseDiv.style.left = "0px";
	gamedisplay.appendChild(baseDiv);
	let baseDiv2 = document.createElement("div");
	baseDiv2.setAttribute("id", "baseDiv2");
	baseDiv2.style = "z-index:10;display:block;";
	baseDiv2.style.position = "absolute";
	baseDiv2.width = "1440px";
	baseDiv2.height = "1080px";
	baseDiv2.style.top = "0px";
	baseDiv2.style.left = "0px";
	gamedisplay.appendChild(baseDiv2);
	//create canvases
	let mainCanvas = document.createElement("canvas");
	mainCanvas.setAttribute("id", "MainCanvas");
	mainCanvas.style.border = "none";
	mainCanvas.style.align = "center";
	mainCanvas.setAttribute("width", "800");
	mainCanvas.setAttribute("height", "600");
	baseDiv.appendChild(mainCanvas);
	let uiCanvas = document.createElement("canvas");
	uiCanvas.setAttribute("id", "UICanvas");
	uiCanvas.style.border = "none";
	uiCanvas.style.align = "center";
	uiCanvas.setAttribute("width", "800");
	uiCanvas.setAttribute("height", "600");
	baseDiv2.appendChild(uiCanvas);
	// create shader script(GLSL)
	let vertexshader = document.createElement("script");
	vertexshader.setAttribute("id", "3d-vertex-shader");
	vertexshader.setAttribute("type", "notjs");
	vertexshader.innerText = "\
	attribute vec4 a_position; \
	attribute vec4 a_color; \
	uniform mat4 u_matrix; \
	varying vec4 v_color; \
	void main() { \
		gl_Position = u_matrix * a_position; \
		v_color = a_color; \
	}";
	gamedisplay.appendChild(vertexshader);
	let fragmentshader = document.createElement("script");
	fragmentshader.setAttribute("id", "3d-fragment-shader");
	fragmentshader.setAttribute("type", "notjs");
	fragmentshader.innerText = "\
	precision mediump float; \
	varying vec4 v_color; \
	\
	void main() { \
		gl_FragColor = v_color;\
	}";
	gamedisplay.appendChild(fragmentshader);
	let planevertexshader = document.createElement("script");
	planevertexshader.setAttribute("id", "2d-vertex-shader");
	planevertexshader.setAttribute("type", "notjs");
	planevertexshader.innerText = "\
	attribute vec4 a_position; \
	attribute vec2 a_texcoord; \
	uniform mat4 u_matrix; \
	varying vec2 v_texcoord; \
	void main() { \
	  gl_Position = u_matrix * a_position; \
	  v_texcoord = a_texcoord; \
	}";
	gamedisplay.appendChild(planevertexshader);
	let planefragmentshader = document.createElement("script");
	planefragmentshader.setAttribute("id", "2d-fragment-shader");
	planefragmentshader.setAttribute("type", "notjs");
	planefragmentshader.innerText = "\
	precision mediump float; \
	varying vec2 v_texcoord; \
	uniform sampler2D u_texture; \
	void main() { \
	   gl_FragColor = texture2D(u_texture, v_texcoord);\
	   gl_FragColor.rgb *= gl_FragColor.a;\
	}";
	gamedisplay.appendChild(planefragmentshader);
	return;
}

createIPInputBox ();

// ------------------------------------------------------------------------------------------------------------
// ----------------------------------------- put your own code here -------------------------------------------
// ------------------------------------------------------------------------------------------------------------

// ----------------------------------------------------------------------------------------------------
// --------------------------- UncycloFortress client-side Script -------------------------------------
// ----------------------------------------------------------------------------------------------------

let scriptversion = 0.1;
let inputhandler;

function Projectile_Rocket(pos, size, direction, team, shooter, imgsrc) {
	Projectile.call(this, pos, size, direction, team, shooter, imgsrc);
	this.vertexlist = GenerateProjectile(this.GetCenterPosition(), this.size, this.cameraMatrix);
}
Projectile_Rocket.prototype = Object.create(Projectile.prototype);
Projectile_Rocket.prototype.constructor = Projectile_Rocket;

function Weapon (type, owner) {
	this.type = type;
	this.owner = owner;
	this.timerhandler;
	this.loadedbullet = 1;
	this.firedelay = 1000;
	this.reloaddelay = 1000;
	this.isReloading = false;
	this.isFiring = false;
	this.MaxAmmo = this.loadedbullet;
	this.weaponname = "?";
	this.ReloadWeapon = () => {
		this.isFiring = false; 
		if (!this.isReloading && this.loadedbullet != this.MaxAmmo){
			clearTimeout(this.timerhandler);
			this.timerhandler = setTimeout(this.ReloadWeapon_sub, this.reloaddelay);
			this.isReloading = true;
		}
	};
	this.ReloadWeapon_sub = () => {
		// use fetch to fire event and get result
		// apply the result
			// if result failed(due to timing or else)
			// if result is success
			this.loadedbullet = this.MaxAmmo;
			this.isReloading = false;
			ws.send(JSON.stringify({
				type: 13,
				id: player.playerID,
				currentweapon: player.currentweapon
			}));
		return;
	}
	this.FireWeapon = () =>{
		if (this.type != 9 && this.loadedbullet === 0 && !this.isReloading) {
			this.ReloadWeapon();
		} else if (!this.isReloading && !this.isFiring) {
			// use fetch to fire event and get result
			// apply the result
				// if result failed(due to timing or else)
				// if result is success
			clearTimeout(this.timerhandler);
			this.timerhandler = setTimeout(this.FireWeapon_sub, this.firedelay);
			this.isFiring = true;
		}
	};
	this.FireWeapon_sub = () => {
		this.isFiring = false;
	};
	this.ReleaseFire = () => {
		return;
	};
	this.DoSpecialSkill = () => {
		// use fetch to fire event and get result
		// apply the result
			// if result failed(due to timing or else)
			// if result is success
	};
}
function Weapon_Pistol (type, owner) {
	Weapon.call(this, type, owner);
	this.loadedbullet = 6;
	this.firedelay = 300;
	this.reloaddelay = 1000;
	this.MaxAmmo = this.loadedbullet;
	this.weaponname = "Pistol";
	this.FireWeapon = () =>{
		if (this.type != 9 && this.loadedbullet === 0 && !this.isReloading) {
			this.ReloadWeapon();
		} else if (!this.isReloading && !this.isFiring) {
			// use fetch to fire event and get result
			// apply the result
				// if result failed(due to timing or else)
				// if result is success
				if (this.type != 9 && this.loadedbullet > 0)this.loadedbullet -= 1;
				this.owner.raylist = new Array(1);
				let randomNum = Math.random();
				this.owner.raylist[0] = new Ray ([this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/360+randomNum* Math.PI/180), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2 - Math.PI/360+randomNum* Math.PI/180), 0]);
				ws.send(JSON.stringify({
					type:6,
					id:player.playerID,
					raylist: [{startpos:[this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], endpos: [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/360+randomNum* Math.PI/180), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2 - Math.PI/360+randomNum* Math.PI/180), 0]}]
				}));
				this.owner.raylist[0].AddPenetratingObjects(mapinfo.mapobjectlist);
				this.owner.raylist[0].endpos = this.owner.raylist[0].GetClosestCollidedPosition();
				this.owner.raylist[0].DrawRay();
			clearTimeout(this.timerhandler);
			this.timerhandler = setTimeout(this.FireWeapon_sub, this.firedelay);
			this.isFiring = true;
		}
	};
	this.FireWeapon_sub = () => {
		this.isFiring = false;
	};
	this.ReleaseFire = () => {
		return;
	}
}
Weapon_Pistol.prototype = Object.create(Weapon.prototype);
Weapon_Pistol.prototype.constructor = Weapon_Pistol;

function Weapon_PowerShotgun (type, owner) {
	Weapon.call(this, type, owner);
	this.loadedbullet = 4;
	this.firedelay = 800;
	this.reloaddelay = 1000;
	this.MaxAmmo = this.loadedbullet;
	this.weaponname = "Power Shotgun";
	this.FireWeapon = () =>{
		if (this.type != 9 && this.loadedbullet === 0 && !this.isReloading) {
			this.ReloadWeapon();
		} else if (!this.isReloading && !this.isFiring) {
			// use fetch to fire event and get result
			// apply the result
				// if result failed(due to timing or else)
				// if result is success
				if (this.type != 9 && this.loadedbullet > 0)this.loadedbullet -= 1;
				this.owner.raylist = new Array(9);
				let raylist = [];
				for (let i = 0 ; i < 9; i++) { // 9 < MAX_RAY_PER_PERSON
					this.owner.raylist[i] = new Ray ([this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/12+i* Math.PI/48), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2- Math.PI/12+i* Math.PI/48), 0]);
					raylist.push({startpos:[this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], endpos: [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/12+i* Math.PI/48), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2- Math.PI/12+i* Math.PI/48), 0]});
				}
				ws.send(JSON.stringify({
					type:6,
					id:player.playerID,
					raylist: raylist
				}));
				for (let i = 0 ; i < 9 ; i++) {
					this.owner.raylist[i].AddPenetratingObjects(mapinfo.mapobjectlist);
					this.owner.raylist[i].endpos = this.owner.raylist[i].GetClosestCollidedPosition();
					this.owner.raylist[i].DrawRay();
				}
			clearTimeout(this.timerhandler);
			this.timerhandler = setTimeout(this.FireWeapon_sub, this.firedelay);
			this.isFiring = true;
		}
	};
	this.FireWeapon_sub = () => {
		this.isFiring = false;
	};
	this.ReleaseFire = () => {
		return;
	}
}
Weapon_PowerShotgun.prototype = Object.create(Weapon.prototype);
Weapon_PowerShotgun.prototype.constructor = Weapon_PowerShotgun;
function Weapon_Minigun (type, owner) {
	Weapon.call(this, type, owner);
	this.loadedbullet = 200;
	this.firedelay = 100;
	this.reloaddelay = 5000;
	this.MaxAmmo = this.loadedbullet;
	this.weaponname = "Minigun";
	this.FireWeapon = () =>{
		if (this.type != 9 && this.loadedbullet === 0 && !this.isReloading) {
			this.ReloadWeapon();
		} else if (!this.isReloading && !this.isFiring) {
			// use fetch to fire event and get result
			// apply the result
				// if result failed(due to timing or else)
				// if result is success
				if (this.type != 9 && this.loadedbullet > 0)this.loadedbullet -= 1;
				this.owner.raylist = new Array(2);
				let raylist = [];
				for (let i = 0 ; i < 2; i++) { // 2 < MAX_RAY_PER_PERSON
					let randomNum = Math.random();
					this.owner.raylist[i] = new Ray ([this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/24+randomNum* Math.PI/12), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2 - Math.PI/24+randomNum* Math.PI/12), 0]);
					raylist.push({startpos:[this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], endpos: [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/24+randomNum* Math.PI/12), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2 - Math.PI/24+randomNum* Math.PI/12), 0]});
				}
				ws.send(JSON.stringify({
					type:6,
					id:player.playerID,
					raylist: raylist
				}));
				for (let i = 0 ; i < 2; i++) { // 2 < MAX_RAY_PER_PERSON
					this.owner.raylist[i].AddPenetratingObjects(mapinfo.mapobjectlist);
					this.owner.raylist[i].endpos = this.owner.raylist[i].GetClosestCollidedPosition();
					this.owner.raylist[i].DrawRay();
				}
			clearTimeout(this.timerhandler);
			this.timerhandler = setTimeout(this.FireWeapon_sub, this.firedelay);
			this.isFiring = true;
		}
	};
	this.FireWeapon_sub = () => {
		if (this.loadedbullet > 0 ){
			// use fetch to fire event and get result
			// apply the result
				// if result failed(due to timing or else)
				// if result is success
				this.loadedbullet -= 1;
				this.owner.raylist = new Array(2);
				let raylist = [];
				for (let i = 0 ; i < 2; i++) { // 2 < MAX_RAY_PER_PERSON
					let randomNum = Math.random();
					this.owner.raylist[i] = new Ray ([this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/24+randomNum* Math.PI/12), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2 - Math.PI/24+randomNum* Math.PI/12), 0]);
					raylist.push({startpos:[this.owner.GetCenterPosition()[0], this.owner.GetCenterPosition()[1], -18], endpos: [this.owner.GetCenterPosition()[0]- 10000* Math.cos(this.owner.direction+Math.PI/2- Math.PI/24+randomNum* Math.PI/12), this.owner.GetCenterPosition()[1] - 10000*  Math.sin(this.owner.direction +Math.PI/2 - Math.PI/24+randomNum* Math.PI/12), 0]});
				}
				ws.send(JSON.stringify({
					type:6,
					id:player.playerID,
					raylist: raylist
				}));
				for (let i = 0 ; i < 2; i++) { // 2 < MAX_RAY_PER_PERSON
					this.owner.raylist[i].AddPenetratingObjects(mapinfo.mapobjectlist);
					this.owner.raylist[i].endpos = this.owner.raylist[i].GetClosestCollidedPosition();
					this.owner.raylist[i].DrawRay();
				}
			this.timerhandler = setTimeout(this.FireWeapon_sub, this.firedelay);
		}
	};
	this.ReleaseFire = () => {
		if (this.isFiring) {
			clearTimeout(this.timerhandler);
			this.isFiring = false;
		}
		return;
	}
}
Weapon_Minigun.prototype = Object.create(Weapon.prototype);
Weapon_Minigun.prototype.constructor = Weapon_Minigun;
function Weapon_RocketLauncher (type, owner) {
	Weapon.call(this, type, owner);
	this.loadedbullet = 6;
	this.firedelay = 800;
	this.reloaddelay = 1000;
	this.MaxAmmo = this.loadedbullet;
	this.weaponname = "Rocket Launcher";
	this.FireWeapon = () =>{
		if (this.type != 9 && this.loadedbullet === 0 && !this.isReloading) {
			this.ReloadWeapon();
		} else if (!this.isReloading && !this.isFiring) {
			// use fetch to fire event and get result
			// apply the result
				// if result failed(due to timing or else)
				// if result is success
				if (this.type != 9 && this.loadedbullet > 0)this.loadedbullet -= 1;
				let tempprojectile = new Projectile_Rocket([this.owner.GetCenterPosition()[0] - 8, this.owner.GetCenterPosition()[1] - 8], 1, this.owner.direction, 1, this.owner, "UF_Rocket_Red.png");
				tempprojectile.visible = true;
				if (projectilelist.length < MAX_PROJECTILE_NUMBER)projectilelist.push(tempprojectile);
				ws.send(JSON.stringify({
					type:7,
					id:player.playerID,
					pos: [this.owner.GetCenterPosition()[0] - 8, this.owner.GetCenterPosition()[1] - 8],
					direction:this.owner.direction,
					shooterid: this.owner.playerID,
					projectiletype: 0
				}));
			clearTimeout(this.timerhandler);
			this.timerhandler = setTimeout(this.FireWeapon_sub, this.firedelay);
			this.isFiring = true;
		}
	};
	this.FireWeapon_sub = () => {
		this.isFiring = false;
	};
	this.ReleaseFire = () => {
		return;
	}
}
Weapon_RocketLauncher.prototype = Object.create(Weapon.prototype);
Weapon_RocketLauncher.prototype.constructor = Weapon_RocketLauncher;

function DoSomethingAfterMapLoad () {
	// add class selection button
	let gamedisplay = document.getElementById("mw-content-text");

	let classbutton1 = document.createElement("button");
	classbutton1.setAttribute("name", "classbutton1");
	classbutton1.setAttribute("type", "button");
	classbutton1.setAttribute("value", "1");
	classbutton1.innerText = "Speedster";
	classbutton1.onclick = ChangeMyCharacter;
	gamedisplay.appendChild(classbutton1);
	let classbutton2 = document.createElement("button");
	classbutton2.setAttribute("name", "classbutton2");
	classbutton2.setAttribute("type", "button");
	classbutton2.setAttribute("value", "2");
	classbutton2.innerText = "Tank";
	classbutton2.onclick = ChangeMyCharacter;
	gamedisplay.appendChild(classbutton2);
	let classbutton3 = document.createElement("button");
	classbutton3.setAttribute("name", "classbutton3");
	classbutton3.setAttribute("type", "button");
	classbutton3.setAttribute("value", "3");
	classbutton3.innerText = "Tankbuster";
	classbutton3.onclick = ChangeMyCharacter;
	gamedisplay.appendChild(classbutton3);
	// spawn initial player
	player = new Player([mapinfo.RedSpawn[0], mapinfo.RedSpawn[1]], 8, [degToRad(0), degToRad(0), degToRad(0)], degToRad(0), 1, 0);
	player.secondweapon = new Weapon(0,player); // weapon 0
	player.firstweapon = new Weapon(4,player); // weapon 1
	player.currentweapon = 0;
	player.ReloadWeapon = () => {
	};
	player.FireWeapon = () =>{
	};
	player.DoSpecialSkill = () => {
	};
	player.ReleaseFire = () => {
	};
	gamerule = new GameRule(180, 180, 0);
	inputhandler = new Input(document.getElementById("UICanvas"));
	inputhandler.InitInput();
	return;
}

function DrawUI () {
	if (player != undefined && player.secondweapon != undefined && player.firstweapon != undefined){
		let ctx = document.getElementById("UICanvas").getContext("2d");
		ctx.canvas.width = 800; // reset the ui canvas
		ctx.beginPath();
		ctx.rect(0, 500, 800, 100);
		ctx.fillStyle = "rgba(125, 125, 5, 1)";
		ctx.fill();
		ctx.font = "18px Arial, sans-serif";
		ctx.fillStyle = "rgba(255, 255, 0, 1)";
		if (player.currentweapon === 0)ctx.fillText("Current Weapon: "+ player.secondweapon.weaponname,20,20);
		else ctx.fillText("Current Weapon: "+ player.firstweapon.weaponname,20,20);
		ctx.fillStyle = "rgba(255, 0, 0, 1)";
		ctx.fillText(String.format("{0}:{1}", Math.floor(gamerule.RedClock /60), gamerule.RedClock % 60),400-90,20 );
		ctx.fillStyle = "rgba(25, 125, 255, 1)";
		ctx.fillText(String.format("{0}:{1}", Math.floor(gamerule.BlueClock /60), gamerule.BlueClock % 60),400,20 );
		// reserved for kill log
		
		ctx.fillStyle = "rgba(255, 255, 0, 1)";
		ctx.fillText("Health: "+player.healthpoint, 20, 580);
		if (player.currentweapon === 0) {
			if (player.secondweapon.isReloading) {
				ctx.fillStyle = "rgba(255, 25, 0, 1)";
				ctx.fillText("Reloading", 700, 560);
			}
			ctx.fillText("Ammo: "+player.secondweapon.loadedbullet, 700, 580);
		} else {
			if (player.firstweapon.isReloading) {
				ctx.fillStyle = "rgba(255, 25, 0, 1)";
				ctx.fillText("Reloading", 700, 560);
			}
			ctx.fillText("Ammo: "+player.firstweapon.loadedbullet, 700, 580);
		}
		ctx.closePath();
		ctx.beginPath();
		ctx.arc(400, 300, 10, 0, 2*Math.PI, true);
		ctx.closePath();
		ctx.lineWidth = 2;
		ctx.strokeStyle = "#FFFF00";
		ctx.stroke();
		ctx.beginPath();
		ctx.font = "12px Arial, sans-serif";
		for (let killlog of killloglist){
			ctx.fillStyle = "rgba(0, 255, 0, 1)";
			ctx.fillText(killlog, 760 - 5*killlog.length, 40+8*killloglist.indexOf(killlog));
		}
		ctx.closePath();
		if (keystate[7]){
			//draw player list board
			ctx.beginPath();
			ctx.rect(100, 100, 600, 600);
			ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
			ctx.fill();
			ctx.font = "18px Arial, sans-serif";
			ctx.fillStyle = "rgba(0, 255, 0, 1)";
			ctx.fillText("Players", 400 - "Players".length / 2, 110);
			let blueteamplayer = 0;
			let redteamplayer = 0;
			for (let playeritem of playerlist) {
				if (playeritem.team === 1 ) {
					ctx.fillStyle = "rgba(255, 0, 0, 1)";
					ctx.fillText(playeritem.playername, 660 - 8*playeritem.playername.length, 140+9*redteamplayer);
					redteamplayer += 1;
				} else if (playeritem.team === 2) {
					ctx.fillStyle = "rgba(0, 0, 255, 1)";
					ctx.fillText(playeritem.playername, 140, 140+9*blueteamplayer);
					blueteamplayer += 1;
				}
			}
			ctx.closePath();
		}
	}
	return;
}

function GetMyNewWeapon(type) {
	let newweapon = new Weapon(type,player);
	switch (type) {
		case 0: // pistol
			newweapon = new Weapon_Pistol(type, player);
			break;
		case 1: // nail gun
			newweapon.loadedbullet = 30;
			newweapon.firedelay = 200;
			newweapon.reloaddelay = 1000;
			break;
		case 2: // short shotgun
			newweapon.loadedbullet = 8;
			newweapon.firedelay = 400;
			newweapon.reloaddelay = 2000;
			break;
		case 4: // power shotgun
			newweapon = new Weapon_PowerShotgun(type, player);
			break;
		case 5: // minigun
			newweapon = new Weapon_Minigun (type, player);
			break;
		case 6: // rocket shooter
			newweapon = new Weapon_RocketLauncher (type, player);
			break;
		case 7: // medibeam shooter
			newweapon.loadedbullet = -1;
			newweapon.firedelay = 200;
			newweapon.reloaddelay = 0;
			break;
		case 8: // watershooter
			newweapon.loadedbullet = -1;
			newweapon.firedelay = 200;
			newweapon.reloaddelay = 0;
			break;
		case 9: // wrench
			newweapon.loadedbullet = 200;
			newweapon.firedelay = 800;
			newweapon.reloaddelay = 1000;
			break;
		case 10: // pipe shooter
			newweapon.loadedbullet = 8;
			newweapon.firedelay = 400;
			newweapon.reloaddelay = 2000;
			break;
		case 11: // sniper rifle
			newweapon.loadedbullet = 1;
			newweapon.firedelay = 400;
			newweapon.reloaddelay = 2000;
			break;
		case 12: // knife
			newweapon.loadedbullet = -1;
			newweapon.firedelay = 800;
			newweapon.reloaddelay = 1000;
			break;
		default:
			newweapon.loadedbullet = 1;
			newweapon.firedelay = 400;
			newweapon.reloaddelay = 1000;
			break;
	}
	newweapon.MaxAmmo = newweapon.loadedbullet;
	return newweapon;
}

function ChangeMyCharacter(event) {
	inputhandler.ReleasePlayer();
	switch(event.target.value) {
		case "1":
			if (player.team === 1) {
				player = new Player(player.pos, 12, degToRad(0), player.team, 100, player.playerID, true, player.playername, 1);
			} else {
				player = new Player(player.pos, 12, degToRad(180), player.team, 100, player.playerID, true, player.playername, 1);
			}
			player.secondweapon = GetMyNewWeapon(0); // weapon 0
			player.firstweapon = GetMyNewWeapon(4); // weapon 1
			player.movespeed = 2;
			break;
		case "2":
			if (player.team === 1) {
				player = new Player(player.pos, 12, degToRad(0), player.team, 300, player.playerID, true, player.playername, 2);
			} else {
				player = new Player(player.pos, 12, degToRad(180), player.team, 300, player.playerID, true, player.playername, 2);
			}
			player.secondweapon = GetMyNewWeapon(0); // weapon 0
			player.firstweapon = GetMyNewWeapon(5); // weapon 1
			player.movespeed = 0.5;
			break;
		case "3":
			if (player.team === 1) {
				player = new Player(player.pos, 12, degToRad(0), player.team, 200, player.playerID, true, player.playername, 3);
			} else {
				player = new Player(player.pos, 12, degToRad(180), player.team, 200, player.playerID, true, player.playername, 3);
			}
			player.secondweapon = GetMyNewWeapon(0); // weapon 0
			player.firstweapon = GetMyNewWeapon(6); // weapon 1
			player.movespeed = 1;
			break;
		default:
			//only possible in initial entrance
			break;
	}
	if (player.team === 1) {
	player.SetCenterPosition([mapinfo.RedSpawn[0] + mapobjectsizeconstant/2, mapinfo.RedSpawn[1] + mapobjectsizeconstant/2]);
	} else {
		player.SetCenterPosition([mapinfo.BlueSpawn[0] + mapobjectsizeconstant/2, mapinfo.BlueSpawn[1] + mapobjectsizeconstant/2]);
	}
	player.currentweapon = 0;
	ws.send(JSON.stringify({
		type:3,
		id:player.playerID,
		classnum:player.classnumber,
		team: player.team
	}));
	clearTimeout(player.deadtimer);
	player.deadtimer = setTimeout(deadStateHandler, 4000);
	return;
}