using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using ProjectZ.Base; using ProjectZ.InGame.GameObjects.Base; using ProjectZ.InGame.GameObjects.Base.CObjects; using ProjectZ.InGame.GameObjects.Base.Components; using ProjectZ.InGame.GameObjects.Base.Components.AI; using ProjectZ.InGame.GameObjects.Things; using ProjectZ.InGame.Map; using ProjectZ.InGame.Things; namespace ProjectZ.InGame.GameObjects.Bosses { class BossSlimeEelSpawn : GameObject { private enum SpawnState { Init, Shake, FloorBreak, FloorGone, Wall0, ShakeEnd, Wall1, Wall2, Wall3, } private SpawnState _spawnState; private float _spawnCounter; private readonly BossSlimeEel _slimeEel; private readonly BossSlimeEelTail[] _tailParts = new BossSlimeEelTail[5]; private readonly AiComponent _aiComponent; private readonly CSprite[] _sprite = new CSprite[20]; private RectangleF _fieldRectangle; private Vector2 _centerPosition; private float _rotation; private readonly float _rotationSpeed; private int _rotationDirection = 1; private float _shakeSoundCounter; private float _tailState; private int _tailIndex; private bool _tailIsMoving = true; private bool _tailComeOut = true; private bool _isVisible = false; public BossSlimeEelSpawn() : base("slime eel") { } public BossSlimeEelSpawn(Map.Map map, int posX, int posY, string saveKey) : base(map) { Tags = Values.GameObjectTag.Enemy; _centerPosition = new Vector2(posX + 8, posY + 8); if (!string.IsNullOrWhiteSpace(saveKey) && Game1.GameManager.SaveManager.GetString(saveKey) == "1") { // respawn the heart if the player died after he killed the boss without collecting the heart SpawnHeart(); SpawnHoles(); IsDead = true; return; } _sprite[0] = new CSprite("eel_floor_broken", new CPosition(posX - 8, posY - 8, 0), Vector2.Zero); _sprite[1] = new CSprite("eel_floor_broken", new CPosition(posX + 8, posY - 8, 0), Vector2.Zero); _sprite[2] = new CSprite("eel_floor_broken", new CPosition(posX - 8, posY + 8, 0), Vector2.Zero); _sprite[3] = new CSprite("eel_floor_broken", new CPosition(posX + 8, posY + 8, 0), Vector2.Zero); _sprite[4] = new CSprite("eel_floor", new CPosition(posX - 8, posY - 8, 0), Vector2.Zero); _sprite[5] = new CSprite("eel_floor", new CPosition(posX + 8, posY - 8, 0), Vector2.Zero); _sprite[6] = new CSprite("eel_floor", new CPosition(posX - 8, posY + 8, 0), Vector2.Zero); _sprite[7] = new CSprite("eel_floor", new CPosition(posX + 8, posY + 8, 0), Vector2.Zero); _sprite[8] = new CSprite("eel_wall_open", new CPosition(posX - 40, posY - 56, 0), Vector2.Zero); _sprite[9] = new CSprite("eel_wall_open", new CPosition(posX + 24, posY - 56, 0), Vector2.Zero); _sprite[10] = new CSprite("eel_wall_open", new CPosition(posX - 40, posY + 56, 0), Vector2.Zero) { SpriteEffect = SpriteEffects.FlipVertically }; _sprite[11] = new CSprite("eel_wall_open", new CPosition(posX + 24, posY + 56, 0), Vector2.Zero) { SpriteEffect = SpriteEffects.FlipVertically }; _sprite[12] = new CSprite("eel_wall", new CPosition(posX - 40, posY - 72, 0), Vector2.Zero) { SpriteEffect = SpriteEffects.FlipVertically }; _sprite[13] = new CSprite("eel_wall", new CPosition(posX + 24, posY - 72, 0), Vector2.Zero) { SpriteEffect = SpriteEffects.FlipVertically }; _sprite[14] = new CSprite("eel_wall", new CPosition(posX - 40, posY + 72, 0), Vector2.Zero); _sprite[15] = new CSprite("eel_wall", new CPosition(posX + 24, posY + 72, 0), Vector2.Zero); _sprite[16] = new CSprite("eel_wall", new CPosition(posX - 40, posY - 56, 0), Vector2.Zero); _sprite[17] = new CSprite("eel_wall", new CPosition(posX + 24, posY - 56, 0), Vector2.Zero); _sprite[18] = new CSprite("eel_wall", new CPosition(posX - 40, posY + 56, 0), Vector2.Zero) { SpriteEffect = SpriteEffects.FlipVertically }; _sprite[19] = new CSprite("eel_wall", new CPosition(posX + 24, posY + 56, 0), Vector2.Zero) { SpriteEffect = SpriteEffects.FlipVertically }; // moved down so that we draw over the door EntityPosition = new CPosition(posX + 8, posY + 64, 0); EntitySize = new Rectangle(-80, -120, 160, 128); _fieldRectangle = Map.GetField(posX, posY, 16); // ~4 sec for turn _rotationSpeed = (MathF.PI * 2) / 60 / 4; for (var i = 0; i < _tailParts.Length; i++) { var spriteIndex = i < 4 ? 1 : 2; if (i == 0) spriteIndex = 0; _tailParts[i] = new BossSlimeEelTail(map, _centerPosition, spriteIndex, null); map.Objects.SpawnObject(_tailParts[i]); } _slimeEel = new BossSlimeEel(map, _centerPosition, this, saveKey); map.Objects.SpawnObject(_slimeEel); var stateHidden = new AiState(UpdateHidden); var stateSpawn = new AiState(UpdateSpawn); var stateIdle = new AiState(UpdateIdle); var stateDespawn = new AiState(UpdateDespawn); _aiComponent = new AiComponent(); _aiComponent.States.Add("hidden", stateHidden); _aiComponent.States.Add("spawn", stateSpawn); _aiComponent.States.Add("idle", stateIdle); _aiComponent.States.Add("despawn", stateDespawn); _aiComponent.ChangeState("hidden"); AddComponent(AiComponent.Index, _aiComponent); AddComponent(DrawComponent.Index, new DrawComponent(Draw, Values.LayerBottom, EntityPosition)); } public void SetTailState(float tailState) { _tailComeOut = false; _tailState = tailState; } public void ToDespawn() { _aiComponent.ChangeState("despawn"); } public void ChangeRotation() { _rotationDirection = -_rotationDirection; } public void SetTailIsMoving(bool isMoving) { _tailComeOut = true; _tailIsMoving = isMoving; } private void UpdateHidden() { // player entered the room? if (_fieldRectangle.Contains(MapManager.ObjLink.BodyRectangle)) { Game1.GameManager.StartDialogPath("slime_eel"); _aiComponent.ChangeState("spawn"); } } private void UpdateSpawn() { _spawnCounter += Game1.DeltaTime; var spawnTime = 900; var endTime = 5500 + spawnTime * 4; if(_spawnState == SpawnState.Shake || _spawnState == SpawnState.FloorBreak) { // @TODO: sound effect; gets louder over time _shakeSoundCounter -= Game1.DeltaTime; if (_shakeSoundCounter < 0) { _shakeSoundCounter += 150; Game1.GameManager.PlaySoundEffect("D378-61-3E", true); } } if (_spawnState == SpawnState.Init && _spawnCounter > 1000) { _spawnState = SpawnState.Shake; Game1.GameManager.ShakeScreen(endTime, 1, 2, 6.5f, 4.5f); } else if (_spawnState == SpawnState.Shake && _spawnCounter > 2000) { _spawnState = SpawnState.FloorBreak; for (var i = 0; i < 4; i++) _sprite[4 + i].IsVisible = false; } else if (_spawnState == SpawnState.FloorBreak && _spawnCounter > 3000) { _spawnState = SpawnState.FloorGone; for (var i = 0; i < 4; i++) _sprite[i].IsVisible = false; _isVisible = true; SpawnStonesCenter(); SpawnHoles(); } else if (_spawnState == SpawnState.FloorGone && _spawnCounter > 5500) { _spawnState = SpawnState.Wall0; _sprite[16].IsVisible = false; _slimeEel.SpawnAttack(0); SpawnStonesWall((int)_centerPosition.X - 32, (int)_centerPosition.Y - 56, 1); } else if (_spawnState == SpawnState.Wall0 && _spawnCounter > 6000) { _spawnState = SpawnState.ShakeEnd; } else if (_spawnState == SpawnState.ShakeEnd && _spawnCounter > 5500 + spawnTime) { _spawnState = SpawnState.Wall1; _sprite[17].IsVisible = false; _slimeEel.SpawnAttack(1); SpawnStonesWall((int)_centerPosition.X + 32, (int)_centerPosition.Y - 56, 1); } else if (_spawnState == SpawnState.Wall1 && _spawnCounter > 5500 + spawnTime * 2) { _spawnState = SpawnState.Wall2; _sprite[18].IsVisible = false; _slimeEel.SpawnAttack(2); SpawnStonesWall((int)_centerPosition.X - 32, (int)_centerPosition.Y + 48, -1); } else if (_spawnState == SpawnState.Wall2 && _spawnCounter > 5500 + spawnTime * 3) { _spawnState = SpawnState.Wall3; _sprite[19].IsVisible = false; _slimeEel.SpawnAttack(3); SpawnStonesWall((int)_centerPosition.X + 32, (int)_centerPosition.Y + 48, -1); } else if (_spawnState == SpawnState.Wall3 && _spawnCounter > endTime) { _aiComponent.ChangeState("idle"); _slimeEel.ToSpawned(); } if (_isVisible) { // move the tail out of the hole _tailState += Game1.TimeMultiplier * 0.025f; if (_tailState > 1) _tailState = 1; // move the tail left/right _rotation = -MathF.Sin((_spawnCounter - 3000) / (endTime - 3000) * MathF.PI * 7) * 0.35f; } UpdateTail(); } private void SpawnStonesCenter() { SpawnStoneLine((int)_centerPosition.X - 10, (int)_centerPosition.Y - 12, -1); SpawnStoneLine((int)_centerPosition.X - 4, (int)_centerPosition.Y - 12, -0.25f); SpawnStoneLine((int)_centerPosition.X + 4, (int)_centerPosition.Y - 12, 0.25f); SpawnStoneLine((int)_centerPosition.X + 10, (int)_centerPosition.Y - 12, 1f); } private void SpawnStoneLine(int posX, int posY, float dir) { var randomOffset0 = Game1.RandomNumber.Next(80, 120) / 100f; var randomOffset1 = Game1.RandomNumber.Next(80, 120) / 100f; var randomOffset2 = Game1.RandomNumber.Next(80, 120) / 100f; var stone0 = new ObjSmallStone(Map, posX, posY, 0, new Vector3(dir * 0.35f, -0.25f, 1.25f) * randomOffset0, true); var stone1 = new ObjSmallStone(Map, posX, posY + 6, 0, new Vector3(dir * 0.35f, 0.0f, 1.25f) * randomOffset1, true); var stone2 = new ObjSmallStone(Map, posX, posY + 12, 0, new Vector3(dir * 0.35f, 0.25f, 1.25f) * randomOffset2, true); Map.Objects.SpawnObject(stone0); Map.Objects.SpawnObject(stone1); Map.Objects.SpawnObject(stone2); } private void SpawnStonesWall(int posX, int posY, int dir) { Game1.GameManager.PlaySoundEffect("D378-12-0C"); var randomOffset0 = Game1.RandomNumber.Next(90, 110) / 100f; var randomOffset1 = Game1.RandomNumber.Next(90, 110) / 100f; var randomOffset2 = Game1.RandomNumber.Next(90, 110) / 100f; var randomOffset3 = Game1.RandomNumber.Next(90, 110) / 100f; var stone0 = new ObjSmallStone(Map, posX - 5, posY + 2 * dir, 0, new Vector3(-0.15f, 0.95f * dir, 0.85f) * randomOffset0, true); var stone1 = new ObjSmallStone(Map, posX - 7, posY, 0, new Vector3(-0.45f, 0.75f * dir, 0.85f) * randomOffset1, true); var stone2 = new ObjSmallStone(Map, posX + 5, posY + 2 * dir, 0, new Vector3(0.15f, 0.95f * dir, 0.85f) * randomOffset2, true); var stone3 = new ObjSmallStone(Map, posX + 7, posY, 0, new Vector3(0.45f, 0.75f * dir, 0.85f) * randomOffset3, true); Map.Objects.SpawnObject(stone0); Map.Objects.SpawnObject(stone1); Map.Objects.SpawnObject(stone2); Map.Objects.SpawnObject(stone3); } private void SpawnHoles() { for (var i = 0; i < 4; i++) { var hole = new ObjHole(Map, (int)_centerPosition.X - 16 + (i % 2) * 16, (int)_centerPosition.Y - 16 + (i / 2) * 16, 16, 16, Rectangle.Empty, 0, 0, 0); Map.Objects.SpawnObject(hole); } } private void UpdateDespawn() { _tailState -= Game1.TimeMultiplier * 0.025f; if (_tailState <= 0) { _tailState = 0; _isVisible = false; } UpdateTail(); } private void UpdateIdle() { if (_tailIsMoving) _rotation += Game1.TimeMultiplier * _rotationSpeed * _rotationDirection; if (_tailComeOut) { _tailState += Game1.TimeMultiplier * 0.025f; if (_tailState > 1) _tailState = 1; } UpdateTail(); } private void UpdateTail() { var tailLength = _tailState * 47; var parts = (int)(tailLength / 10) + 1; if (!_isVisible) parts = 0; _tailIndex = 5 - parts; for (var i = 0; i < 5 - parts; i++) _tailParts[i].SetActive(false); var lastPosition = Vector2.Zero; for (var i = 0; i < parts; i++) { var index = 5 - parts + i; _tailParts[index].SetActive(true); // rotate the tip farther in the rotation direction var dist = ((tailLength - (parts - 1 - i) * 10) / 47f); var mult = dist * dist; var rotation = _rotation - MathF.Sin(MathF.PI + _rotation * 2) * 0.75f * mult; var newPosition = new Vector2(MathF.Sin(rotation), -MathF.Cos(rotation)) * (tailLength - (parts - 1 - i) * 10); if (i > 0) { // make sure that the distance between the parts is 10 var direction = newPosition - lastPosition; direction.Normalize(); newPosition = lastPosition + direction * 10; } lastPosition = newPosition; _tailParts[index].EntityPosition.Set(_centerPosition + newPosition); } } private void Draw(SpriteBatch spriteBatch) { for (var i = 0; i < _sprite.Length; i++) _sprite[i].Draw(spriteBatch); // we cant let the object have its own draw method because we could not set the draw order if (_isVisible) for (var i = _tailIndex; i < _tailParts.Length; i++) _tailParts[i].Sprite.Draw(spriteBatch); } private void SpawnHeart() { // spawn big heart Map.Objects.SpawnObject(new ObjItem(Map, (int)_centerPosition.X - 8, (int)_centerPosition.Y - 32, "j", "d5_nHeart", "heartMeterFull", null)); } } }