List<Projectile> barrels; //keep this so we can delete barrels when they're offscreen

        public GorillaAI(Player player, PlayState ps) {
			this.ps = ps;
            rng = new Random(System.DateTime.Now.Millisecond);
            SpriteSheet gorillaSprite = LoadLevel.parseSpriteFile("gorilla_sprite.dat");
            barrelProp = LoadLevel.parseProjectileFile("barrel_projectile.dat", ps);
            Vector3 location = new Vector3(4370,162.5f,51);
            Vector3 scale = new Vector3(50.0f, 50.0f, 50.0f);
            Vector3 pbox = new Vector3(6, 6, 6);
            Vector3 cbox = new Vector3(6, 6, 6);
            boss = new GorillaBossobject(player, location, pbox, cbox, scale, true, true, 6, 2, 1, gorillaSprite);
            ps.objList.Add(boss);
            ps.physList.Add(boss);
            ps.collisionList.Add(boss);
            ps.renderList.Add(boss);
            ps.combatList.Add(boss);
			barrels = new List<Projectile>();

            delayTime = 3;
            delayTimer = 0;
            barrelDelayed = true;
        }
		public Projectile(Vector3 location, Vector3 direction, bool playerSpawned, ProjectileProperties p, Player player, int type=-1) {
			_location = location;
			this.direction = direction;
			_scale = new Vector3(p.scale);
			_pbox = new Vector3(p.pbox);
			_cbox = new Vector3(p.cbox);
			_existsIn2d = p.existsIn2d;
			_existsIn3d = p.existsIn3d;
			_damage = p.damage;
			_speed = p.speed;
			_sprite = p.sprite;
			_deathAnim = p.deathAnim;
			doesGravity = p.gravity;
            this.player = player;
			this.duration = p.duration;
			liveTime = 0.0;

			if(type == -1) {
				if(p.gravity) {
					_type = (int)CombatType.grenade;
					_hascbox = false; //Has normal pbox collisions until detonation
				} else {
					_type = (int)CombatType.projectile;
					_hascbox = true;
				}
			} else {
				_type = type;
				_hascbox = true;
			}

			_alive = true;
			_health = 1; // health 1 = active, health 0 = despawning, waiting for cleanup in PlayState
			_mesh = null;
			_texture = null;
			_frameNum = 0;
			this.playerspawned = playerSpawned;
			velocity = direction * speed;
			accel = new Vector3(0, 0, 0);
			_animDirection = 1;
		}
		internal ZooBoss(Player player, PlayState ps, Vector3 location, int maxheight, ProjectileProperties invisProj, Obstacle crate, Obstacle ground, Obstacle rope, SpriteSheet bossSprite)
            : base(player, ps, location, maxheight, invisProj, crate, ground, rope) {

            //animation
            _sprite = bossSprite;
            _cycleNum = 0;
            _frameNum = 0;
            _animDirection = 1;

			//combat stuff
			_cbox = new Vector3(6, 6, 6);
            _health = 5;
            _damage = 1;
            _speed = 1;
            _alive = true;
            _hascbox = true;
            _type = 3; //Type 3 means this is the boss

			//Offset boss location
			centerLoc = new Vector3(_location);
			_location.X += 8.0f;
			_location.Z -= 0.01f;
			in3d = false;
			bb = Billboarding.Lock2d;

            invincible = false;
        }
		internal Crate(Player player, PlayState ps, Vector3 location, int maxheight, ProjectileProperties invisProj, Obstacle crate, Obstacle ground, Obstacle rope)
			: base(player, ps, location, maxheight, invisProj, crate, ground, rope) {
		}
		internal FallingBox(Player player, PlayState ps, Vector3 location, int maxheight, ProjectileProperties invisProj, Obstacle crate, Obstacle ground, Obstacle rope)
			: base() {
            //physics stuff
            velocity = new Vector3(0, 0, 0);
            accel = new Vector3(0, 0, 0);
            doesGravity = false;
            _scale = new Vector3(30.0f, 33.84f, 30.0f);
            _pbox = new Vector3(15.0f, 16.92f, 0.0f);
            _existsIn3d = true;
            _existsIn2d = true;
            maxHeight = maxheight;
            falling = false;
            rising = false;
            prefalling = false;
            idle = true;

            //animation
			_sprite = null;
            _cycleNum = 0;
            _frameNum = 0;
            _animDirection = 1;

			//timers
			downtime = 3;
			pretime = 1.5;
			shadowtimer = 0;

            //setup the sub-objects of the boss
			projectile = invisProj;
            
            mybox = crate;
            ps.objList.Add(mybox);
            ps.physList.Add(mybox);
            ps.renderList.Add(mybox);

			myGround = ground;
			ps.objList.Add(myGround);
			ps.physList.Add(myGround);
			ps.renderList.Add(myGround);

            myRope = rope;
            ps.objList.Add(myRope);
            ps.renderList.Add(myRope);

            //initialize everything's location based on the seed location
            mybox.canSquish = true;
            mybox.location = location + new Vector3(0, mybox.pbox.Y, 0);
            _location = location + new Vector3(0, (mybox.pbox.Y * 2) + pbox.Y, 0); 
            myRope.location = location + new Vector3(0, (mybox.pbox.Y * 2) + pbox.Y + ropeOffset, 0);//rope sprite
			myGround.location = new Vector3(myGround.location.X, 100.0f, myGround.location.Z);
            minHeight = 125;
            preHeight = 210;
		}
        public Enemy(Player player, Vector3 location, Vector3 scale, Vector3 pbox, Vector3 cbox, bool existsIn2d, bool existsIn3d, int health, int damage, float speed,
						int AItype, SpriteSheet sprite, Effect death, ProjectileProperties proj = null)
        {
			_location = location;
			_scale = scale;
            _pbox = pbox;
            _cbox = cbox;
			_existsIn3d = existsIn3d;
			_existsIn2d = existsIn2d;
            _health = health;
            _damage = damage;
            _speed = speed;
            _alive = true;
            this.AItype = AItype;
            _hascbox = true;
            _type = 1; // type one means this is an enemy
            frozen = false;
            freezetimer = 0;
            maxFreezeTime = 0.7;
            attackspeed = 1;
            attacktimer = 0;
			this.player = player;
			_deathAnim = death;

			_mesh = null;
			_texture = null;
			_sprite = sprite;
			projectile = proj;
            if (projectile != null)
                projectile.damage = damage;
			_frameNum = 0;
			_is3dGeo = false;
			_animDirection = 1;

            velocity = new Vector3(0, 0, 0);
            accel = new Vector3(0, 0, 0);
            doesGravity = true;

            CurrentAI = new Stack<Airoutine>();
            InitilizeAI();
		}
        public Player(SpriteSheet sprite, SpriteSheet arms, List<ProjectileProperties> projectiles, PlayState ps) : base(Int32.MaxValue) //player always has largest ID for rendering purposes
        {
            p_state = new PlayerState("TEST player");
            //p_state.setSpeed(130);
			this.playstate = ps;
            _speed = runSpeed;
			_location = new Vector3(25, 12.5f, 50);
            _scale = new Vector3(25f, 25f, 25f);
            _pbox = new Vector3(5f, 11.0f, 5f);
            _cbox = new Vector3(4f, 10.0f, 4f);
            spinSize = 12; //?? experiment with this, TODO: change it to match the sprites size
			velocity = new Vector3(0, 0, 0);
			accel = new Vector3(0, 0, 0);
			kbspeed = new Vector3(70, 100, 70);
			jumpspeed = 250.0f;
			_cycleNum = 0;
			_frameNum = 0;
			_sprite = sprite;
            _hascbox = true;
            _type = 0; //type 0 is the player and only the player
			_existsIn2d = true;
			_existsIn3d = true;
			onGround = true;

			//arms
			this.arms = new Decoration(_location + new Vector3(0.005f, 0.0f, 0.005f), _scale, true, true, Billboarding.Yes, arms);
			ps.renderList.Add(this.arms);

            //combat things
            _damage = 1;
            spinDamage = 2;
            maxHealth = _health = 7;
			stamina = 5.0;
			maxStamina = 5.0;

			spaceDown = false;
			eDown = false;
            pDown = false;
			isMobile = true;

            Invincible = false;
            HasControl = true;
            Invincibletimer = 0.0;
            NoControlTimer = 0.0;
			fallTimer = 0.0;
			viewSwitchJumpTimer = 0.0;
			projectileTimer = 0.0;
			spinTimer = 0.0;
			lookDownTimer = -1.0;
			squishTimer = -1.0;
			lastPosOnGround = new Vector3(_location);
			_animDirection = 1;
			this.projectiles = projectiles;
			curProjectile = projectiles[0];
            curProjectile.damage = _damage;
			markerList = new List<Decoration>();

			Assembly assembly = Assembly.GetExecutingAssembly();
			jumpSound = new AudioFile(assembly.GetManifestResourceStream("U5Designs.Resources.Sound.jump_sound.ogg"));
			bananaSound = new AudioFile(assembly.GetManifestResourceStream("U5Designs.Resources.Sound.banana2.ogg"));
			HurtSound = new AudioFile(assembly.GetManifestResourceStream("U5Designs.Resources.Sound.hurt.ogg"));
            HitSound = new AudioFile(assembly.GetManifestResourceStream("U5Designs.Resources.Sound.hit.ogg"));
            SpinSound = new AudioFile(assembly.GetManifestResourceStream("U5Designs.Resources.Sound.spin.ogg"));
		}
        /// <summary>
        /// Provides all the mouse input for the player.  Will currently check for a left click and shoot a projectile in the direction
        /// </summary>
        /// <param name="playstate">a pointer to play state, for accessing the obj lists in playstate</param>
        private void handleMouseInput()
        {
            if (playstate.eng.ThisMouse.LeftPressed()) {

				if(stamina >= curProjectile.staminaCost && projectileTimer <= 0.0) {
					spawnProjectile(calcProjDir());

					stamina -= curProjectile.staminaCost;
					projectileTimer = 0.25;

					if(_cycleNum == (int)(enable3d ? PlayerAnim.walk3d : PlayerAnim.walk2d)) {
						arms.cycleNumber = (int)(enable3d ? PlayerAnim.throwRun3d : PlayerAnim.throwRun2d);
					} else { //standing
						arms.cycleNumber = (int)(enable3d ? PlayerAnim.throwStand3d : PlayerAnim.throwStand2d);
					}
					arms.frameNumber = 0;
					prevArmFrameNum = -1.0;

					bananaSound.Play();
				}
				//TODO: If the player was out of stamina, flash the stamina bar to let them know what happened

				//Note: do this whether we actually launched a grenade or not, because if we didn't, it's
				//      probably because of stamina, and the player may not have time to switch back or to
				//      wait for stamina to refill.
				if(curProjectile.gravity) {
					//Automatically switch back to normal projectile
					curProjectile = projectiles[0];
					isMobile = true;
					markerList.Clear();

					projectileTimer = 0.25;
				}
            }

            if (isMobile && playstate.eng.ThisMouse.RightPressed() && spinTimer <= 0.0 && stamina > 0.0) {
                spinning = true;
				spinTimer = 0.5;
                _speed = spinSpeed;
				arms.cycleNumber = cycleNumber = (int)(enable3d ? PlayerAnim.spin3d : PlayerAnim.spin2d); //explicitly set arms to clear possible throw animation
                oldMouseState = playstate.eng.ThisMouse.RightPressed();
            }
        }
		/// <summary>
		/// Handles all keyboard input from the player
		/// </summary>
		/// <param name="keyboard">Contains the current keypresses</param>
		private void handleKeyboardInput(KeyboardDevice keyboard) {
			if(isMobile) {
				if(enable3d) {
					Vector2 newVel = new Vector2(0);
					if(keyboard[Key.W]) { newVel.X++; }
					if(keyboard[Key.S]) { newVel.X--; }
					if(keyboard[Key.A]) { newVel.Y--; }
					if(keyboard[Key.D]) { newVel.Y++; }

					newVel.NormalizeFast();
					if(viewSwitchJumpTimer > 0.0 && (newVel.X != 0 || newVel.Y != 0)) {
						viewSwitchJumpTimer = Math.Min(viewSwitchJumpTimer, 0.025);
					}

					velocity.X = newVel.X * speed;
					velocity.Z = newVel.Y * speed;

					//update current animation and flip scale if necessary
					//arms.animDirection = _animDirection = (velocity.X >= 0 ? 1 : 1);
					if(!spinning) {
						cycleNumber = (int)(velocity.X == 0 ? PlayerAnim.stand3d : PlayerAnim.walk3d);
						if(arms.cycleNumber != _cycleNum) {
							arms.cycleNumber = (int)(velocity.X == 0 ? PlayerAnim.throwStand3d : PlayerAnim.throwRun3d);
						}
					}

					if(_scale.X < 0) {
						_scale.X = -_scale.X;
						arms.scaleX = -arms.scaleX;
					}
				} else {
					if(keyboard[Key.A] == keyboard[Key.D]) {
						velocity.X = 0f;
					} else if(keyboard[Key.D]) {
						velocity.X = _speed;
					} else { //a
						velocity.X = -_speed;
					}

					//update current animation and flip scale if necessary
					//arms.animDirection = _animDirection = 1;
					if(!spinning) {
						cycleNumber = (int)(velocity.X == 0 ? PlayerAnim.stand2d : PlayerAnim.walk2d);
						if((velocity.X < 0 && _scale.X > 0) || (velocity.X > 0 && _scale.X < 0)) {
							_scale.X = -_scale.X;
							arms.scaleX = -arms.scaleX;
						}
						if(arms.cycleNumber != _cycleNum) {
							arms.cycleNumber = (int)(velocity.X == 0 ? PlayerAnim.throwStand2d : PlayerAnim.throwRun2d);
						}
					}
				}

#if true
// 				//Cloud
// 				//TODO: Implement animation etc, possibly change which key triggers this
// 				if(keyboard[Key.C]) {
// 					velocity.Y = _speed;
// 					fallTimer = 0.0;
// 				}
//                 //TMP PHYSICS TEST BUTTON suicide button
//                 if (keyboard[Key.X]) {
//                     _health = 0;
//                 }
//                 if (keyboard[Key.P] && !pDown) {
//                     pDown = true;
//                     Console.WriteLine("Player position: (" + location.X + ", " + location.Y + ", " + location.Z + ")");
//                 }
//                 else if (!keyboard[Key.P])
//                     pDown = false;
// 
//                 if (keyboard[Key.BackSlash])
//                     location = new Vector3(3779, 138, 43);

                if (keyboard[Key.Semicolon]) {
                    playstate.bossAI.killBoss(playstate);
                    playstate.bossMode = false;
                    //transition to next level
                    playstate.waitingToSwitchLevels = true;
                }
#endif

				//Jump
				if(keyboard[Key.Space] && !spaceDown) {
					if(onGround) {
						accelerate(Vector3.UnitY * jumpspeed);
						onGround = false;
						viewSwitchJumpTimer = 0.0;
						//jumpSound.Play();
					}
					spaceDown = true;
				} else if(!keyboard[Key.Space]) {
					spaceDown = false;
				}
			} else { //not mobile - grenade selected
				velocity.X = 0.0f;
				velocity.Z = 0.0f;
			}

			//Look down
			if(!enable3d && keyboard[Key.S] && !keyboard[Key.A] && !keyboard[Key.D]) {
				if(lookDownTimer == -1.0) {
					lookDownTimer = 0.0;
				}
			} else if(lookDownTimer != -1.0) {
				cam.moveToYPos(_location.Y);
				lookDownTimer = -1.0;
				isMobile = true;
			}

			//Toggle Grenade
			if(keyboard[Key.E] && !eDown) {
				if(curProjectile.gravity) { //Turn grenades off
					curProjectile = projectiles[0];
					isMobile = true;
					markerList.Clear();
				} else { //Turn grenades on
					if(onGround) {
						curProjectile = projectiles[1];
						velocity.X = 0.0f;
						velocity.Z = 0.0f;
						isMobile = false;
						cycleNumber = (int)(enable3d ? PlayerAnim.stand3d : PlayerAnim.stand2d);
						if(arms.cycleNumber != _cycleNum) {
							arms.cycleNumber = (int)(enable3d ? PlayerAnim.throwStand3d : PlayerAnim.throwStand2d);
						}
					}
				}
				eDown = true;
			} else if(!keyboard[Key.E]) {
				eDown = false;
			}
			
			//Secret skip to boss
			if(keyboard[Key.Period] && keyboard[Key.Comma]) {
				//floor at 125 + 12.5 player pbox
				location = new Vector3(playstate.bossSpawn) + Vector3.UnitY * (_pbox.Y + 0.1f);

				//The following should maybe be moved to a function in Camera
				playstate.camera.eye.Y = _location.Y + (enable3d ? 24.0f : 31.25f);
				playstate.camera.lookat.Y = _location.Y + (enable3d ? 20.5f : 31.25f);
				cam.moveToYPos(_location.Y);

				//playstate.enterBossMode();
			}

		}