using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; 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.SaveLoad; using ProjectZ.InGame.Things; using System; namespace ProjectZ.InGame.GameObjects.Bosses { class BossFinalBoss : GameObject { public readonly CSprite Sprite; private readonly BodyDrawShadowComponent _bodyShadow; private readonly DamageFieldComponent _damageField; private readonly HittableComponent _hittableComponent; private readonly BodyComponent _body; private readonly AiComponent _aiComponent; private readonly Animator _animator; private readonly Animator _animatorBody; private readonly Animator _animatorWeapon; private readonly Animator _animatorEye; private readonly DrawComponent _drawComponent; private readonly AiDamageState _aiDamageState; private readonly CBox _hittableBoxMan; private CBox _hittableBox; private DictAtlasEntry _spriteBody; private BossFinalBossFireball _objFireball; private Rectangle _roomRectangle; private Vector2 _bodyPosition; private Vector2 _bodyStartPosition; private Vector2 _bodyTargetPosition; private Vector2 _manTargetPosition; private Vector2[] _fireballOffset = new Vector2[] { new Vector2(-15, -8), new Vector2(0, -24), new Vector2(15, -8), new Vector2(0, -4) }; private Vector2[] _bodyParts = new Vector2[6]; private string _saveKey; // state slime private const int SlimeDamageTime = 2200; private const int RotateTime = 2500; private int _slimeLives = 3; private bool _slimeForm; // state man private bool _manInit = true; private int _manLives = 4; // state ganon private int _ganonLives = 12; private Vector2 _ganonTargetPosition; private const int _ganonDeathTime = 2200; private bool _ganonForm; private float _batCounter; private int _batIndex; private int _batIndexStart; // state moldorm private int _moldormLives = 16; private const int TailMult = 8; private BossFinalBossTail[] _moldormTails = new BossFinalBossTail[4]; private AiTriggerSwitch _moldormSpeedUp; private Vector2[] _moldormPositions = new Vector2[6 * TailMult]; private Vector2[] _moldormPositionsNew = new Vector2[6 * TailMult]; private float[] _partDist = new float[4]; private float _moldormRadiant; private float _moldormChangeCounter; private float _moldormSpeed; private float _directionChangeMultiplier; private int _moldormDirection = 1; private bool _moldormHit; private const float MoldormSpeedNormal = 1.0f; private const float MoldormSpeedFast = 1.75f; private int _direction; private int _sideIndex; private int _moveDist = 40; private float _moveSpeed = 0.25f; private float _moldormSoundCounter; private int _moveCounter; // state face private float _faceParticleCounter; // state final // objects used to deal damage private readonly BossFinalBossFinalTail[] _finalParts = new BossFinalBossFinalTail[8]; private readonly string[] _spriteFinalParts = new string[] { "final_part0", "final_part1", "final_part1", "final_part2" }; private float[] _finalPartDistance = new float[] { 18, 10, 10, 10 }; private Vector2 _targetPosition; private float _finalPartCounter; private float _finalPart0 = -MathF.PI / 2; private float _finalPart1 = -MathF.PI / 2; private float _finalPartSpeed0 = 1 / 2500.0f * MathF.PI * 2; private float _finalPartSpeed1 = 1 / 2500.0f * MathF.PI * 2; private int _finalStateLives = 16; private int _finalStateDeathCounter = 2500; private float _finalEyeCounter; private int _finalEyeState; private bool _finalState; private bool _hideBody; private bool _hideHead; private bool _drawGanonWeapon; private bool _drawMoldormTail; private bool _pushRepel; public BossFinalBoss() : base("nightmare_head") { } public BossFinalBoss(Map.Map map, int posX, int posY, string saveKey) : base(map) { Tags = Values.GameObjectTag.Enemy; EntityPosition = new CPosition(posX + 8, posY + 8, 0); EntityPosition.AddPositionListener(typeof(BossFinalBoss), OnUpdatePosition); EntitySize = new Rectangle(-16, -24, 32, 40); _saveKey = saveKey; if (!string.IsNullOrEmpty(_saveKey) && Game1.GameManager.SaveManager.GetString(_saveKey) == "1") { IsDead = true; return; } _bodyStartPosition = EntityPosition.Position; _bodyTargetPosition = _bodyStartPosition - new Vector2(0, _moveDist); _animator = AnimatorSaveLoad.LoadAnimator("nightmares/nightmare"); _animator.Play("head"); _animatorBody = AnimatorSaveLoad.LoadAnimator("nightmares/nightmare"); _animatorBody.Play("idle"); _animatorWeapon = AnimatorSaveLoad.LoadAnimator("nightmares/nightmare ganon weapon"); _animatorEye = AnimatorSaveLoad.LoadAnimator("nightmares/nightmare final"); _spriteBody = Resources.GetSprite("nightmare_body"); _bodyPosition = EntityPosition.Position; Sprite = new CSprite(EntityPosition); var animationComponent = new AnimationComponent(_animator, Sprite, Vector2.Zero); for (var i = 0; i < _finalParts.Length; i++) { _finalParts[i] = new BossFinalBossFinalTail(map, this, _spriteFinalParts[i % 4], EntityPosition.Position); map.Objects.SpawnObject(_finalParts[i]); } _roomRectangle = map.GetField(posX, posY); _body = new BodyComponent(EntityPosition, -7, -7, 14, 14, 8) { MoveCollision = OnCollision, AbsorbPercentage = 1f, Gravity = -0.085f, DragAir = 1.0f, Drag = 0.8f, FieldRectangle = map.GetField(posX, posY), CollisionTypes = Values.CollisionTypes.Normal }; _aiComponent = new AiComponent(); // floor var stateIdle = new AiState(UpdateIdle) { Init = InitIdle }; var stateMoveBody = new AiState(UpdateMoveBody) { Init = InitMoveBody }; var stateMoveHead = new AiState(UpdateMoveHead); var stateWobble = new AiState(UpdateWobble) { Init = InitWobble }; var stateDespawn = new AiState(UpdateDespawn) { Init = InitStartDespawn }; // slime var stateSlimeSpawn = new AiState(UpdateSlimeSpawn) { Init = InitSlimeSpawn }; var stateSlimeJump = new AiState(UpdateSlimeJump) { Init = InitSlimeJump }; var stateSlimeWait = new AiState() { Init = InitSlimeWait }; stateSlimeWait.Trigger.Add(new AiTriggerCountdown(800, null, () => _aiComponent.ChangeState("slimeJump"))); var stateSlimeDespawn = new AiState(UpdateSlimeDespawn) { Init = InitSlimeDespawn }; var stateSlimeHidden = new AiState() { Init = InitSlimeHidden }; stateSlimeHidden.Trigger.Add(new AiTriggerCountdown(2200, null, EndSlimeHidden)); var stateSlimeDamaged = new AiState() { Init = InitSlimeDamaged }; stateSlimeDamaged.Trigger.Add(new AiTriggerCountdown(SlimeDamageTime, TickSlimeDamaged, EndSlimeDamge)); var stateSlimeHideExplode = new AiState() { Init = InitSlimeHideExplode }; stateSlimeHideExplode.Trigger.Add(new AiTriggerCountdown(2200, null, EndSlimeHidden)); var stateSlimeExplode = new AiState() { Init = InitSlimeExplode }; stateSlimeExplode.Trigger.Add(new AiTriggerCountdown(2800, null, SlimeEnd)); // man var stateManPreAttack = new AiState(UpdateManPreAttack); stateManPreAttack.Trigger.Add(new AiTriggerCountdown(1100, null, () => _aiComponent.ChangeState("manAttack"))); var stateManAttack = new AiState(UpdateManAttack) { Init = InitManAttack }; var stateManPostAttack = new AiState(UpdateManPostAttack) { Init = InitPostAttack }; stateManPostAttack.Trigger.Add(new AiTriggerCountdown(1300, null, () => _aiComponent.ChangeState("manDespawn"))); var stateManDespawn = new AiState(UpdateManDespawn) { Init = InitManDespawn }; var stateManMove = new AiState(UpdateManMove) { Init = InitManMove }; var stateManMoveWait = new AiState(); stateManMoveWait.Trigger.Add(new AiTriggerCountdown(650, null, () => _aiComponent.ChangeState("manSpawn"))); var stateManSpawn = new AiState(UpdateManSpawn) { Init = InitManSpawn }; var stateManRotate = new AiState() { Init = InitManRotate }; stateManRotate.Trigger.Add(new AiTriggerCountdown(RotateTime, TickRotate, EndRotate)); // explode var stateExplode = new AiState() { Init = InitExplode }; stateExplode.Trigger.Add(new AiTriggerCountdown(950, null, () => _aiComponent.ChangeState("explodeDespawn"))); var stateExplodeDespawn = new AiState(UpdateDepawn) { Init = InitDespawn }; var stateMove = new AiState(UpdateMove) { Init = InitMove }; var stateMoveWait = new AiState(); stateMoveWait.Trigger.Add(new AiTriggerCountdown(650, null, EndMoveWait)); // moldorm var stateMoldormSpawn = new AiState(UpdateMoldormSpawn) { Init = InitMoldormSpawn }; var stateMoldorm = new AiState(UpdateMoldorm) { Init = InitMoldorm }; stateMoldorm.Trigger.Add(_moldormSpeedUp = new AiTriggerSwitch(3000)); var stateMoldormDying = new AiState(UpdateMoldormDying) { Init = InitMoldormDying }; stateMoldormDying.Trigger.Add(new AiTriggerCountdown(1250, null, MoldormExplode)); var stateMoldormDespawn = new AiState(UpdateMoldormDespawn); // ganon var stateGanonSpawn = new AiState(UpdateGanonSpawn) { Init = InitGanonSpawn }; var stateGanonPreSpawnWeapon = new AiState(UpdateGanonPreSpawnWeapon); stateGanonPreSpawnWeapon.Trigger.Add(new AiTriggerCountdown(850, null, () => _aiComponent.ChangeState("ganonSpawnWeapon"))); var stateGanonSpawnWeapon = new AiState(UpdateGanonSpawnWeapon) { Init = InitGanonSpawnWeapon }; var stateGanon = new AiState(UpdateGanon) { Init = InitGanon }; stateGanon.Trigger.Add(new AiTriggerCountdown(1000, null, () => _aiComponent.ChangeState("ganonBats"))); var stateGanonBats = new AiState(UpdateGanonBats) { Init = InitGanonBats }; var stateGanonThrow = new AiState(UpdateGanonThrow) { Init = InitGanonThrow }; stateGanonThrow.Trigger.Add(new AiTriggerCountdown(800, null, ThrowWeapon)); var stateGanonPostThrow = new AiState() { Init = InitGanonPostThrow }; stateGanonPostThrow.Trigger.Add(new AiTriggerCountdown(1100, null, () => _aiComponent.ChangeState("ganonMove"))); var stateGanonMove = new AiState(UpdateGanonMove) { Init = InitGanonMove }; var stateGanonCatchWeapon = new AiState(UpdateGanonCatchWeapon) { Init = InitGanonCatchWeapon }; var stateGanonWait = new AiState(UpdateGanonWait); stateGanonWait.Trigger.Add(new AiTriggerCountdown(1000, null, () => _aiComponent.ChangeState("ganonBats"))); var stateGanonDeath = new AiState() { Init = InitGanonDeath }; stateGanonDeath.Trigger.Add(new AiTriggerCountdown(_ganonDeathTime, TickGanonDamage, () => _aiComponent.ChangeState("ganonExplode"))); var stateGanonExplode = new AiState() { Init = InitGanonExplode }; stateGanonExplode.Trigger.Add(new AiTriggerCountdown(_ganonDeathTime, null, () => _aiComponent.ChangeState("face"))); // face var stateFace = new AiState(UpdateFace) { Init = InitFace }; var stateFaceExplode = new AiState() { Init = InitFaceExplose }; stateFaceExplode.Trigger.Add(new AiTriggerCountdown(2200, null, () => _aiComponent.ChangeState("faceMove"))); var stateFaceMove = new AiState(UpdateFaceMove) { Init = InitFaceMove }; var stateFaceHidden = new AiState() { Init = InitFaceHidden }; stateFaceHidden.Trigger.Add(new AiTriggerCountdown(1000, null, () => _aiComponent.ChangeState("faceDespawn"))); var stateFaceDespawn = new AiState(UpdateFaceDespawn) { Init = InitFaceDespawn }; // final var stateFinalSpawn = new AiState(UpdateFianalSpawn) { Init = InitFinalSpawn }; var stateFinal = new AiState(UpdateFinal) { Init = InitFinal }; var stateFinalBlink = new AiState() { Init = InitFinalBlink }; stateFinalBlink.Trigger.Add(new AiTriggerCountdown(_finalStateDeathCounter, TickFinalDespawn, () => _aiComponent.ChangeState("finalDeath"))); var stateFinalDeath = new AiState(UpdateFinalDeath) { Init = InitFinalDeath }; var stateTest = new AiState(); stateTest.Trigger.Add(new AiTriggerCountdown(1500, null, TestProjectileSpawn) { ResetAfterEnd = true }); // spawning _aiComponent.States.Add("idle", stateIdle); _aiComponent.States.Add("moveBody", stateMoveBody); _aiComponent.States.Add("moveHead", stateMoveHead); _aiComponent.States.Add("wobble", stateWobble); _aiComponent.States.Add("despawn", stateDespawn); // slime _aiComponent.States.Add("slimeSpawn", stateSlimeSpawn); _aiComponent.States.Add("slimeJump", stateSlimeJump); _aiComponent.States.Add("slimeWait", stateSlimeWait); _aiComponent.States.Add("slimeDespawn", stateSlimeDespawn); _aiComponent.States.Add("slimeHidden", stateSlimeHidden); _aiComponent.States.Add("slimeDamaged", stateSlimeDamaged); _aiComponent.States.Add("slimeHideExplode", stateSlimeHideExplode); _aiComponent.States.Add("slimeExplode", stateSlimeExplode); // man _aiComponent.States.Add("manPreAttack", stateManPreAttack); _aiComponent.States.Add("manAttack", stateManAttack); _aiComponent.States.Add("manPostAttack", stateManPostAttack); _aiComponent.States.Add("manDespawn", stateManDespawn); _aiComponent.States.Add("manMove", stateManMove); _aiComponent.States.Add("manMoveWait", stateManMoveWait); _aiComponent.States.Add("manSpawn", stateManSpawn); _aiComponent.States.Add("manRotate", stateManRotate); // moldorm _aiComponent.States.Add("moldormSpawn", stateMoldormSpawn); _aiComponent.States.Add("moldorm", stateMoldorm); _aiComponent.States.Add("moldormDying", stateMoldormDying); _aiComponent.States.Add("moldormDespawn", stateMoldormDespawn); // ganon _aiComponent.States.Add("ganonSpawn", stateGanonSpawn); _aiComponent.States.Add("ganonPreSpawnWeapon", stateGanonPreSpawnWeapon); _aiComponent.States.Add("ganonSpawnWeapon", stateGanonSpawnWeapon); _aiComponent.States.Add("ganon", stateGanon); _aiComponent.States.Add("ganonBats", stateGanonBats); _aiComponent.States.Add("ganonThrow", stateGanonThrow); _aiComponent.States.Add("ganonPostThrow", stateGanonPostThrow); _aiComponent.States.Add("ganonMove", stateGanonMove); _aiComponent.States.Add("ganonCatchWeapon", stateGanonCatchWeapon); _aiComponent.States.Add("ganonWait", stateGanonWait); _aiComponent.States.Add("ganonDeath", stateGanonDeath); _aiComponent.States.Add("ganonExplode", stateGanonExplode); // face _aiComponent.States.Add("face", stateFace); _aiComponent.States.Add("faceExplode", stateFaceExplode); _aiComponent.States.Add("faceMove", stateFaceMove); _aiComponent.States.Add("faceHidden", stateFaceHidden); _aiComponent.States.Add("faceDespawn", stateFaceDespawn); // final _aiComponent.States.Add("finalSpawn", stateFinalSpawn); _aiComponent.States.Add("final", stateFinal); _aiComponent.States.Add("finalBlink", stateFinalBlink); _aiComponent.States.Add("finalDeath", stateFinalDeath); _aiComponent.States.Add("explode", stateExplode); _aiComponent.States.Add("explodeDespawn", stateExplodeDespawn); _aiComponent.States.Add("move", stateMove); _aiComponent.States.Add("moveWait", stateMoveWait); _aiDamageState = new AiDamageState(this, _body, _aiComponent, Sprite, 8, false); _aiDamageState.OnDeath = OnZeroLives; _aiDamageState.BossHitSound = true; var damageCollider = new CBox(EntityPosition, -6, -6, 0, 12, 12, 8); _hittableBox = new CBox(EntityPosition, -7, -7, 0, 14, 14, 8); _hittableBoxMan = new CBox(EntityPosition, -6, -12, 0, 12, 18, 8); AddComponent(AiComponent.Index, _aiComponent); AddComponent(BodyComponent.Index, _body); AddComponent(BaseAnimationComponent.Index, animationComponent); AddComponent(PushableComponent.Index, new PushableComponent(_body.BodyBox, OnPush)); AddComponent(HittableComponent.Index, _hittableComponent = new HittableComponent(_hittableBox, OnHit)); AddComponent(DamageFieldComponent.Index, _damageField = new DamageFieldComponent(damageCollider, HitType.Enemy, 2) { IsActive = false }); AddComponent(UpdateComponent.Index, new UpdateComponent(Update)); AddComponent(DrawComponent.Index, _drawComponent = new DrawComponent(Draw, Values.LayerPlayer, EntityPosition)); AddComponent(DrawShadowComponent.Index, _bodyShadow = new BodyDrawShadowComponent(_body, Sprite) { ShadowWidth = 14, ShadowHeight = 5 }); _aiComponent.ChangeState("idle"); //DebugMan(); } #region Debug private void DebugSlimeEnd() { _slimeLives = 1; _aiComponent.ChangeState("slimeHidden"); } private void DebugMan() { _manLives = 2; _aiComponent.ChangeState("slimeExplode"); } private void DebugMoldrom() { _moveCounter = 1; _moldormLives = 1; _aiComponent.ChangeState("moldormSpawn"); } private void DebugGanon() { _ganonLives = 1; _aiComponent.ChangeState("ganonSpawn"); } private void DebugFace() { _finalStateLives = 4; _aiComponent.ChangeState("face"); } #endregion #region state final private void InitFinalDeath() { Sprite.SpriteShader = null; Game1.GameManager.PlaySoundEffect("D370-16-10"); Game1.GameManager.PlaySoundEffect("D378-60-3D"); } private void UpdateFinalDeath() { // spawn the parts _finalPartCounter -= Game1.DeltaTime; // despawn? if (_finalPartCounter < -300) FinalExplose(); for (var i = 0; i < 4; i++) { if (i * 300 >= _finalPartCounter) { _finalParts[i].SetActive(false); _finalParts[i + 4].SetActive(false); } } } private void FinalExplose() { if (!string.IsNullOrEmpty(_saveKey)) Game1.GameManager.SaveManager.SetString(_saveKey, "1"); Map.Objects.DeleteObjects.Add(this); ExplodeAnimation(); } private void InitFinalBlink() { Game1.GameManager.SetMusic(93, 2); Game1.GameManager.StopSoundEffect("D360-61-3D"); Game1.GameManager.PlaySoundEffect("D370-16-10"); // hack to not allow anymore attacks _body.VelocityTarget = Vector2.Zero; Game1.GameManager.StartDialog("nightmareFinal0"); // deactivate the damge fields _damageField.IsActive = false; for (var i = 0; i < 8; i++) _finalParts[i].DeactivateDamageField(); } private void TickFinalDespawn(double counter) { var time = _finalStateDeathCounter - counter; Sprite.SpriteShader = time % (AiDamageState.BlinkTime * 2) < AiDamageState.BlinkTime ? Resources.DamageSpriteShader0 : null; } private void InitFinalSpawn() { Game1.GameManager.PlaySoundEffect("D370-35-23"); Game1.GameManager.SetMusic(79, 2); _targetPosition = EntityPosition.Position; _animator.Play("final_spawn"); _damageField.IsActive = true; _damageField.CollisionBox = new CBox(EntityPosition, -8, -12, 16, 16, 8); _hittableComponent.HittableBox = new CBox(EntityPosition, -6, -8, 12, 14, 8); } private void UpdateFianalSpawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("final"); } private Vector2 RandomRoomPositionFinal() { var posY = 0; // make it more likeley to move around at the top if (Game1.RandomNumber.Next(1, 10) <= 6) posY = Game1.RandomNumber.Next(8, 6 * 8); else if (Game1.RandomNumber.Next(1, 10) <= 6) posY = Game1.RandomNumber.Next(8, 9 * 8); int posX; if (16 < posY || posY < 64 - 16) posX = Game1.RandomNumber.Next(16, 6 * 16); else posX = Game1.RandomNumber.Next(0, 8 * 16); return new Vector2(_roomRectangle.X + 24 + posX, _roomRectangle.Y + 24 + posY); } private void InitFinal() { _finalState = true; _animator.Play("final"); } private void UpdateFinal() { _animatorEye.Update(); Game1.GameManager.PlaySoundEffect("D360-61-3D", false); // move to the target position var distance = _targetPosition - EntityPosition.Position; if (distance.Length() > 4) { distance.Normalize(); _body.VelocityTarget = AnimationHelper.MoveToTarget(_body.VelocityTarget, distance * 0.5f, 0.0125f * Game1.TimeMultiplier); } else { // generate new target position _targetPosition = RandomRoomPositionFinal(); } if (!_animatorEye.IsPlaying) _finalEyeCounter -= Game1.DeltaTime; if (_finalEyeCounter <= 0) { // open the eye if (_finalEyeState == 0) { _finalEyeState = 1; _finalEyeCounter += 1250 + Game1.RandomNumber.Next(750); _animatorEye.Play("eye_open"); } // close the eye else if (_finalEyeState == 1) { _finalEyeState = 0; _finalEyeCounter += 2500 + Game1.RandomNumber.Next(2500); _animatorEye.Play("eye_close"); } } // spawn the parts if (_finalPartCounter < 4 * 300) _finalPartCounter += Game1.DeltaTime; if (_finalPartCounter > 300) { _finalPart0 += Game1.DeltaTime * _finalPartSpeed0; _finalPart1 += Game1.DeltaTime * _finalPartSpeed1; var directionPart0 = new Vector2(MathF.Cos(_finalPart0), MathF.Sin(_finalPart0)); SetFinalPartsPosition(directionPart0, 0); var directionPart1 = new Vector2(-MathF.Cos(_finalPart1), MathF.Sin(_finalPart1)); SetFinalPartsPosition(directionPart1, 1); } } private void SetFinalPartsPosition(Vector2 direction, int index) { var position = new Vector2(EntityPosition.X, EntityPosition.Y - 5); for (var i = 0; i < 4; i++) { if ((i + 1) * 300 > _finalPartCounter) break; _finalParts[i + index * 4].SetActive(true); position += direction * _finalPartDistance[i]; _finalParts[i + index * 4].EntityPosition.Set(position); } } #endregion #region state face private void InitFaceDespawn() { _damageField.IsActive = false; _animator.Play("slime_despawn"); } private void UpdateFaceDespawn() { // TODO: delay if (!_animator.IsPlaying) _aiComponent.ChangeState("finalSpawn"); } private void InitFaceHidden() { _animator.Play("face_hidden"); } private void InitFaceExplose() { Game1.GameManager.SetMusic(-1, 2); Game1.GameManager.PlaySoundEffect("D370-16-10"); _body.VelocityTarget = Vector2.Zero; ExplodeAnimation(); } private void InitFaceMove() { Game1.GameManager.PlaySoundEffect("D360-53-35"); } private void UpdateFaceMove() { var targetPosition = new Vector2(_body.FieldRectangle.X + 80, _body.FieldRectangle.Y + 40); var direction = targetPosition - EntityPosition.Position; var moveSpeed = 1.25f; if (direction.Length() > moveSpeed * Game1.TimeMultiplier) { direction.Normalize(); _body.VelocityTarget = AnimationHelper.MoveToTarget(_body.VelocityTarget, direction * moveSpeed, 0.075f * Game1.TimeMultiplier); } else { _body.VelocityTarget = Vector2.Zero; _aiComponent.ChangeState("faceHidden"); } } private void InitFace() { _damageField.IsActive = true; _damageField.CollisionBox = new CBox(EntityPosition, -6, -6, 0, 12, 12, 8); _hittableComponent.HittableBox = new CBox(EntityPosition, -6, -6, 0, 12, 12, 8); _drawComponent.Layer = Values.LayerPlayer; } private void UpdateFace() { // spawn paticles _faceParticleCounter -= Game1.DeltaTime; if (_faceParticleCounter < 0) { _faceParticleCounter += 125; var objParticle = new ObjAnimator(Map, (int)EntityPosition.X, (int)EntityPosition.Y, Values.LayerBottom, "Nightmares/nightmare particle", "face_particle", true); Map.Objects.SpawnObject(objParticle); } // move towards the player var playerDirection = MapManager.ObjLink.EntityPosition.Position - new Vector2(EntityPosition.Position.X, EntityPosition.Position.Y + 4); if (playerDirection != Vector2.Zero) { playerDirection.Normalize(); _body.VelocityTarget = AnimationHelper.MoveToTarget(_body.VelocityTarget, playerDirection * 1.5f, Game1.TimeMultiplier * 0.035f); } } #endregion #region state ganon public void CatchWeapon() { _body.VelocityTarget = Vector2.Zero; Game1.GameManager.PlaySoundEffect("D378-25-19"); if (_ganonLives > 0) _aiComponent.ChangeState("ganonCatchWeapon"); } private void InitGanonDeath() { _ganonForm = false; _animator.Pause(); _animatorWeapon.Pause(); Game1.GameManager.PlaySoundEffect("D370-16-10"); } private void InitGanonExplode() { _drawGanonWeapon = false; EntityPosition.Set(new Vector2(EntityPosition.X, EntityPosition.Y - 4)); Game1.GameManager.PlaySoundEffect("D378-55-37"); ExplodeAnimation(); } private void TickGanonDamage(double counter) { var time = _ganonDeathTime - counter; Sprite.SpriteShader = time % (AiDamageState.BlinkTime * 2) < AiDamageState.BlinkTime ? Resources.DamageSpriteShader0 : null; } private void UpdateGanonWait() { var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; var dir = playerDirection.X < 0 ? -1 : 1; _animator.Play("ganon_" + dir); _animatorWeapon.Play("ganon_" + dir); } private void InitGanonCatchWeapon() { _drawGanonWeapon = true; var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; var dir = playerDirection.X < 0 ? -1 : 1; _animator.Play("ganon_weapon_spawn_" + dir); _animatorWeapon.Play("ganon_weapon_spawn_" + dir); } private void UpdateGanonCatchWeapon() { if (!_animatorWeapon.IsPlaying) _aiComponent.ChangeState("ganonWait"); } private void InitGanonMove() { _ganonTargetPosition = RandomRoomPosition(); } private void UpdateGanonMove() { var moveDirection = _ganonTargetPosition - EntityPosition.Position; if (moveDirection.Length() > 8) { if (moveDirection != Vector2.Zero) moveDirection.Normalize(); _body.VelocityTarget = AnimationHelper.MoveToTarget(_body.VelocityTarget, moveDirection * 1.5f, 0.075f * Game1.TimeMultiplier); _direction = moveDirection.X < 0 ? -1 : 1; _animator.Play("ganon_" + _direction); } else { _body.VelocityTarget = Vector2.Zero; } } private void InitGanonPostThrow() { _drawGanonWeapon = false; _animator.Play("ganon_throw_" + _direction); } private void InitGanonThrow() { _drawGanonWeapon = true; } private void UpdateGanonThrow() { UpdateDirection(); _animator.Play("ganon_swing_up_" + _direction); _animatorWeapon.Play("ganon_swing_up_" + _direction); } private void ThrowWeapon() { _aiComponent.ChangeState("ganonPostThrow"); var position = EntityPosition.Position - new Vector2(-_direction * 12, 22); var objWeapon = new BossFinalBossWeapon(Map, this, (int)position.X, (int)position.Y, _direction); Map.Objects.SpawnObject(objWeapon); } private void UpdateDirection() { var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; _direction = playerDirection.X < 0 ? -1 : 1; } private void InitGanonBats() { _batIndex = 0; _batCounter = 550 - 350; UpdateDirection(); _batIndexStart = _direction < 0 ? 3 : 0; _animator.Play("ganon_swing_" + _direction); _animatorWeapon.Play("ganon_swing_" + _direction); } private void UpdateGanonBats() { _batCounter += Game1.DeltaTime; if (_batCounter > 550) { _batIndex++; _batCounter -= 550; if (_batIndex > 8) { _aiComponent.ChangeState("ganonThrow"); return; } if (_batIndex >= 7) return; var radians = (_batIndexStart + _batIndex - 1) / 3f * MathF.PI; var position = EntityPosition.Position - new Vector2(_direction * 9, 26) + new Vector2(MathF.Cos(radians), _direction * -MathF.Sin(radians)) * 24; var objBat = new BossFinalBossBat(Map, (int)position.X, (int)position.Y); Map.Objects.SpawnObject(objBat); } } private void InitGanonSpawn() { _ganonForm = true; _pushRepel = true; _damageField.IsActive = true; _damageField.CollisionBox = new CBox(EntityPosition, -12, 7 - 24, 24, 24, 8); _hittableComponent.HittableBox = new CBox(EntityPosition, -12, 7 - 24, 24, 24, 8); var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; var dir = playerDirection.X < 0 ? -1 : 1; Game1.GameManager.PlaySoundEffect("D370-35-23"); _animator.Play("ganon_spawn_" + dir); } private void UpdateGanonSpawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("ganonPreSpawnWeapon"); } private void UpdateGanonPreSpawnWeapon() { var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; var dir = playerDirection.X < 0 ? -1 : 1; _animator.Play("ganon_weapon_" + dir); } private void InitGanonSpawnWeapon() { _drawGanonWeapon = true; var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; var dir = playerDirection.X < 0 ? -1 : 1; Game1.GameManager.PlaySoundEffect("D360-57-39"); _animator.Play("ganon_weapon_spawn_" + dir); _animatorWeapon.Play("ganon_weapon_spawn_" + dir); } private void UpdateGanonSpawnWeapon() { if (!_animatorWeapon.IsPlaying) _aiComponent.ChangeState("ganon"); } private void InitGanon() { } private void UpdateGanon() { var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; var dir = playerDirection.X < 0 ? -1 : 1; _animator.Play("ganon_" + dir); _animatorWeapon.Play("ganon_" + dir); } #endregion #region state moldorm private void InitMoldormSpawn() { _animator.Play("meldorm_spawn"); } private void UpdateMoldormSpawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("moldorm"); } private void InitMoldorm() { _pushRepel = true; _body.CollisionTypes = Values.CollisionTypes.Normal; _drawMoldormTail = true; _damageField.IsActive = true; for (var i = 0; i < _moldormPositions.Length; i++) _moldormPositions[i] = EntityPosition.Position; for (var i = 0; i < _moldormTails.Length; i++) { string animationId; if (i == _moldormTails.Length - 1) animationId = "moldorm_tail"; else if (i == _moldormTails.Length - 2) animationId = "moldorm_body_2"; else animationId = "moldorm_body"; _moldormTails[i] = new BossFinalBossTail(Map, this, animationId, i == _moldormTails.Length - 1); Map.Objects.SpawnObject(_moldormTails[i]); } } private void UpdateMoldormTail() { // blinking tail _moldormTails[3].Sprite.SpriteShader = Game1.TotalGameTime % (AiDamageState.BlinkTime * 2) < AiDamageState.BlinkTime ? Resources.DamageSpriteShader0 : null; } private void UpdateMoldorm() { UpdateMoldormTail(); // change the direction? _moldormChangeCounter -= Game1.DeltaTime; if (_moldormChangeCounter < 0) { _moldormChangeCounter += Game1.RandomNumber.Next(500, 2000); _moldormDirection = -_moldormDirection; } _moldormRadiant += Game1.TimeMultiplier * 0.065f * _moldormDirection; _moldormSpeed = _moldormSpeedUp.State ? MoldormSpeedNormal : MoldormSpeedFast; if (_moldormHit) { _moldormSpeed = 0; UpdateMoldormTail(EntityPosition); } else { _moldormSoundCounter -= Game1.DeltaTime * _moldormSpeed; if (_moldormSoundCounter < 0) { _moldormSoundCounter += 250; Game1.GameManager.PlaySoundEffect("D360-56-38"); } } _body.VelocityTarget = new Vector2(MathF.Sin(_moldormRadiant), MathF.Cos(_moldormRadiant)) * _moldormSpeed; _directionChangeMultiplier = AnimationHelper.MoveToTarget(_directionChangeMultiplier, 1, 0.1f * Game1.TimeMultiplier); } private void InitMoldormDying() { _body.VelocityTarget = Vector2.Zero; } private void UpdateMoldormDying() { UpdateMoldormTail(); _moldormRadiant += Game1.TimeMultiplier * 0.065f * _moldormDirection; UpdateMoldormTail(EntityPosition); } private void MoldormExplode() { _aiComponent.ChangeState("moldormDespawn"); _animator.Play("slime_despawn"); _drawMoldormTail = false; _pushRepel = false; _damageField.IsActive = false; _bodyShadow.IsActive = false; _drawComponent.Layer = Values.LayerBottom; Game1.GameManager.PlaySoundEffect("D378-55-37"); ExplosionParticle(); } private void UpdateMoldormDespawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("move"); } private void OnUpdatePosition(CPosition position) { if (_aiComponent.CurrentStateId == "moldorm") UpdateMoldormTail(position); } private void UpdateMoldormTail(CPosition position) { // set the rotation to be 0 < rotation < Pi * 2 to allow for correct calculations while (_moldormRadiant < MathF.PI * 2) _moldormRadiant += MathF.PI * 2; _moldormRadiant %= MathF.PI * 2; var radiantIndex = (int)(((_moldormRadiant + MathF.PI / 8) / (2 * MathF.PI)) * 8) % 8; _animator.Play("moldorm_head_" + (8 - radiantIndex) % 8); _moldormPositions[0] = position.Position; if (!_aiDamageState.IsInDamageState()) { if (_moldormHit) { _moldormHit = false; _moldormSpeedUp.Reset(); } _partDist = new float[] { 12, 12, 12, 10 }; } else { for (int i = 0; i < _partDist.Length; i++) { if (_partDist[i] > 0) { _partDist[i] -= Game1.TimeMultiplier * 1.75f; if (_partDist[i] < 0) _partDist[i] = 0; break; } } } var partPos = 0f; var partIndex = 0; var targetDist = _partDist[0] / TailMult; for (int i = 1; i < _moldormPositions.Length; i++) { // this loop is only used to make sure to not have and endless loop incase of a problem with the code below while (partIndex + 1 < _moldormPositions.Length) { var dist = (_moldormPositions[partIndex + 1] - _moldormPositions[partIndex]).Length(); if (dist - partPos >= targetDist) { var percentage = dist > 0 ? (dist - partPos - targetDist) / dist : 1; var newPosition = Vector2.Lerp(_moldormPositions[partIndex + 1], _moldormPositions[partIndex], percentage); partPos += targetDist; if (i / TailMult < _partDist.Length) targetDist = _partDist[i / TailMult] / TailMult; _moldormPositionsNew[i] = newPosition; break; } else { partIndex++; targetDist -= (dist - partPos); partPos = 0; } } // the tail is not expaneded if (partIndex + 1 >= _moldormPositions.Length) _moldormPositionsNew[i] = _moldormPositions[_moldormPositions.Length - 1]; } for (int i = 1; i < _moldormPositions.Length; i++) _moldormPositions[i] = _moldormPositionsNew[i]; // set the newly calculated positions for (var i = 0; i < _moldormTails.Length; i++) _moldormTails[i].EntityPosition.Set(_moldormPositions[(i + 1) * TailMult]); } #endregion #region state init private void InitIdle() { Game1.GameManager.StartDialogPath("final_boss_intro"); } private void UpdateIdle() { if (!Game1.GameManager.DialogIsRunning() && !Game1.GameManager.InGameOverlay.TextboxOverlay.IsOpen) { _aiComponent.ChangeState("moveBody"); } } private void InitMoveBody() { Game1.GameManager.PlaySoundEffect("D360-53-35"); } private void UpdateBodyPartPosition(float state) { var direction = _bodyPosition - EntityPosition.Position; // this is supposed to make it look better by moving the first element more than the following elements // TODO: should be replaced by constant speed up moving up? var stateRad = MathF.PI / 2 - state * MathF.PI / 2; var max = MathF.Sin(stateRad); var min = MathF.Sin(stateRad - MathF.PI / 2); for (var i = 0; i < _bodyParts.Length; i++) { var percentage = stateRad - (float)(i + 1) / (_bodyParts.Length + 1) * MathF.PI / 2; var sinState = (MathF.Sin(percentage) - min) / (max - min); _bodyParts[i] = EntityPosition.Position + direction * (1 - sinState); } } private void UpdateMoveBody() { _moveSpeed = AnimationHelper.MoveToTarget(_moveSpeed, 1.75f, 0.05f * Game1.TimeMultiplier); _bodyPosition = AnimationHelper.MoveToTarget(_bodyPosition, _bodyTargetPosition, _moveSpeed * Game1.TimeMultiplier); var bodyState = Math.Clamp(0.5f - (_bodyPosition.Y - _bodyTargetPosition.Y) / (_moveDist * 0.5f), 0, 0.5f); UpdateBodyPartPosition(bodyState); if (_bodyPosition == _bodyTargetPosition) { _moveSpeed = 0.25f; _animatorBody.Play("spawn"); _aiComponent.ChangeState("moveHead"); } } private void UpdateMoveHead() { _moveSpeed = AnimationHelper.MoveToTarget(_moveSpeed, 1.75f, 0.05f * Game1.TimeMultiplier); var newPosition = AnimationHelper.MoveToTarget(EntityPosition.Position, _bodyTargetPosition, _moveSpeed * Game1.TimeMultiplier); EntityPosition.X = newPosition.X; EntityPosition.Y = newPosition.Y; var bodyState = Math.Clamp(2.5f - (EntityPosition.Y - _bodyTargetPosition.Y) / (_moveDist * 0.5f), 0.5f, 1.0f); UpdateBodyPartPosition(bodyState); if (EntityPosition.Position == _bodyTargetPosition) _aiComponent.ChangeState("wobble"); } private void InitWobble() { _animatorBody.Play("wobble"); } private void UpdateWobble() { if (!_animatorBody.IsPlaying) _aiComponent.ChangeState("despawn"); } private void InitStartDespawn() { _hideBody = true; _animator.Play("despawn"); Game1.GameManager.PlaySoundEffect("D360-04-04"); } private void UpdateDespawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("slimeSpawn"); } #endregion #region state explode private void InitExplode() { _pushRepel = false; Game1.GameManager.PlaySoundEffect("D378-55-37"); ExplodeAnimation(); } private void ExplodeAnimation() { _animator.Play("head"); _damageField.IsActive = false; _bodyShadow.IsActive = false; _drawComponent.Layer = Values.LayerBottom; ExplosionParticle(); } private void ExplosionParticle() { for (int i = 0; i < 8; i++) { var radiant = i / 4f * MathF.PI; var velocity = new Vector2(MathF.Sin(radiant), MathF.Cos(radiant)) * 2.5f; var objParticle0 = new BossFinalBossParticle(Map, new Vector2(EntityPosition.X, EntityPosition.Y - EntityPosition.Z), velocity); Map.Objects.SpawnObject(objParticle0); } } private void InitDespawn() { _animator.Play("slime_despawn"); } private void UpdateDepawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("move"); } private void InitMove() { Game1.GameManager.PlaySoundEffect("D360-53-35"); _body.CollisionTypes = Values.CollisionTypes.None; } private void UpdateMove() { var direction = new Vector2(_roomRectangle.Center.X, _roomRectangle.Y + 43) - EntityPosition.Position; if (direction.Length() < 2) { _body.CollisionTypes = Values.CollisionTypes.Normal; _aiComponent.ChangeState("moveWait"); } else { direction.Normalize(); var targetVelocity = AnimationHelper.MoveToTarget(new Vector2(_body.Velocity.X, _body.Velocity.Y), direction, 0.15f * Game1.TimeMultiplier); _body.Velocity.X = targetVelocity.X; _body.Velocity.Y = targetVelocity.Y; } } private void EndMoveWait() { if (_moveCounter == 0) _aiComponent.ChangeState("moldormSpawn"); else if (_moveCounter == 1) _aiComponent.ChangeState("ganonSpawn"); _moveCounter++; } #endregion #region state slime private void InitSlimeSpawn() { _slimeForm = true; _hideHead = false; _pushRepel = true; _bodyShadow.IsActive = true; _damageField.IsActive = true; _hittableComponent.HittableBox = new CBox(EntityPosition, -8, -8, 0, 16, 16, 8, true); _hittableComponent.IsActive = true; _drawComponent.Layer = Values.LayerPlayer; _animator.Play("slime_spawn"); } private void UpdateSlimeSpawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("slimeJump"); } private void InitSlimeJump() { _animator.Play("slime_jump"); var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; if (playerDirection != Vector2.Zero) playerDirection.Normalize(); _body.IsGrounded = false; _body.Velocity.X = playerDirection.X * 0.5f; _body.Velocity.Y = playerDirection.Y * 0.5f; _body.Velocity.Z = 1.75f; } private void UpdateSlimeJump() { if (_body.IsGrounded) { Game1.GameManager.PlaySoundEffect("D360-32-20"); if (Game1.RandomNumber.Next(0, 2) == 0) _aiComponent.ChangeState("slimeWait"); else _aiComponent.ChangeState("slimeDespawn"); } else if (_body.Velocity.Y < -0.5f) { _animator.Play("slime_land"); } } private void InitSlimeWait() { _animator.Play("slime"); } private void InitSlimeDespawn() { _bodyShadow.IsActive = false; _damageField.IsActive = false; _hittableComponent.IsActive = false; _pushRepel = false; _animator.Play("slime_despawn"); EntityPosition.Offset(new Vector2(0, -2)); } private void UpdateSlimeDespawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("slimeHidden"); } private void InitSlimeHidden() { _hideHead = true; } private void EndSlimeHidden() { // random new position EntityPosition.Set(RandomRoomPosition()); _aiComponent.ChangeState("slimeSpawn"); } private void InitSlimeDamaged() { _animator.Play("slime_damaged"); Game1.GameManager.PlaySoundEffect("D360-55-37"); } private void TickSlimeDamaged(double counter) { var time = SlimeDamageTime - counter; if (time < AiDamageState.CooldownTime && time % (AiDamageState.BlinkTime * 2) < AiDamageState.BlinkTime) Sprite.SpriteShader = Resources.DamageSpriteShader0; else Sprite.SpriteShader = null; } private void EndSlimeDamge() { _slimeForm = false; if (_slimeLives <= 0) _aiComponent.ChangeState("slimeExplode"); else _aiComponent.ChangeState("slimeDespawn"); } private void InitSlimeHideExplode() { _pushRepel = false; _hideHead = true; _bodyShadow.IsActive = false; _damageField.IsActive = false; _hittableComponent.IsActive = false; InitSlimeExplode(); } private void InitSlimeExplode() { EntityPosition.Offset(new Vector2(0, -2)); Game1.GameManager.PlaySoundEffect("D370-33-21"); _body.VelocityTarget = Vector2.Zero; ExplodeAnimation(); } #endregion #region state man // @TODO: // type 2 spawn rate private void SlimeEnd() { _aiComponent.ChangeState("manMove"); _manTargetPosition = new Vector2(_roomRectangle.Center.X, _roomRectangle.Y + 43); } private void InitManSpawn() { _animator.Play("man_spawn"); } private void UpdateManSpawn() { if (_animator.IsPlaying) return; _pushRepel = true; _damageField.IsActive = true; Game1.GameManager.PlaySoundEffect("D370-35-23"); _aiComponent.ChangeState("manPreAttack"); } private void TestProjectileSpawn() { _animator.Play("man_" + _direction); _objFireball = new BossFinalBossFireball(this, EntityPosition.Position + _fireballOffset[_direction]); Map.Objects.SpawnObject(_objFireball); _objFireball.Fire(); } private void UpdateManPreAttack() { var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; _direction = AnimationHelper.GetDirection(playerDirection); if (_manInit) { _animator.Play("man_" + _direction); _animator.Pause(); } else { _animator.Play("man_attack_" + _direction); } } private void InitManAttack() { _manInit = false; _animator.Play("man_" + _direction); Game1.GameManager.PlaySoundEffect("D370-34-22"); _objFireball = new BossFinalBossFireball(this, EntityPosition.Position + _fireballOffset[_direction]); Map.Objects.SpawnObject(_objFireball); } private void UpdateManAttack() { var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position; var newDirection = AnimationHelper.GetDirection(playerDirection); if (newDirection != _direction) { _direction = newDirection; _animator.Play("man_" + _direction); _objFireball.EntityPosition.Set(EntityPosition.Position + _fireballOffset[_direction]); } if (_objFireball.IsReady) { _aiComponent.ChangeState("manPostAttack"); _objFireball.Fire(); } } private void InitPostAttack() { _animator.Play("man_attack_" + _direction); } private void UpdateManPostAttack() { } private void InitManDespawn() { _pushRepel = false; _damageField.IsActive = false; _animator.Play("man_despawn"); } private void UpdateManDespawn() { if (!_animator.IsPlaying) _aiComponent.ChangeState("manMove"); } private void InitManMove() { Game1.GameManager.PlaySoundEffect("D360-53-35"); _body.CollisionTypes = Values.CollisionTypes.None; _manTargetPosition = RandomRoomPositionSide(); } private void UpdateManMove() { var direction = _manTargetPosition - EntityPosition.Position; if (direction.Length() < 2) { _body.CollisionTypes = Values.CollisionTypes.Normal; _aiComponent.ChangeState("manMoveWait"); } else { direction.Normalize(); var targetVelocity = AnimationHelper.MoveToTarget(new Vector2(_body.Velocity.X, _body.Velocity.Y), direction, 0.15f * Game1.TimeMultiplier); _body.Velocity.X = targetVelocity.X; _body.Velocity.Y = targetVelocity.Y; } } private void InitManRotate() { Game1.GameManager.PlaySoundEffect("D360-54-36"); _animator.Play("man_rotate"); // first frame = up // make sure to not jump to a different direction _animator.SetFrame((_direction + 3) % 4); } private void TickRotate(double time) { // speed up the rotation speed var rotateSpeed = Math.Clamp((RotateTime - (float)time) / (RotateTime * 0.65f), 0, 1); _animator.SpeedMultiplier = rotateSpeed * 3 + 1; } private void EndRotate() { _pushRepel = false; EntityPosition.Set(new Vector2(EntityPosition.X, EntityPosition.Y - 4)); _animator.SpeedMultiplier = 1; _aiComponent.ChangeState("explode"); } #endregion public bool HitBoss(GameObject origin, Vector2 direction, CBox damageBox) { // getting hit by the fireball? if (!_aiDamageState.IsInDamageState() && _aiComponent.CurrentStateId == "manPostAttack" && damageBox.Box.Intersects(_hittableBoxMan.Box)) { _aiDamageState.OnHit(origin, direction, HitType.Boss, 1, false); _manLives--; if (_manLives <= 0) _aiComponent.ChangeState("manRotate"); return true; } return false; } private void OnZeroLives(bool pieceOfPower) { } private Vector2 RandomRoomPositionSide() { // always change the side _sideIndex = (_sideIndex + Game1.RandomNumber.Next(1, 4)) % 4; if (_sideIndex == 0) return new Vector2(_roomRectangle.X + 16 + 16, _roomRectangle.Y + 32 + 16 + Game1.RandomNumber.Next(0, 2 * 16)); else if (_sideIndex == 2) return new Vector2(_roomRectangle.X + 128, _roomRectangle.Y + 32 + 16 + Game1.RandomNumber.Next(0, 2 * 16)); else if (_sideIndex == 1) return new Vector2(_roomRectangle.X + 32 + 16 + Game1.RandomNumber.Next(0, 4 * 16), _roomRectangle.Y + 32); else return new Vector2(_roomRectangle.X + 32 + 16 + Game1.RandomNumber.Next(0, 4 * 16), _roomRectangle.Y + 100); } private Vector2 RandomRoomPosition() { var posY = Game1.RandomNumber.Next(0, 5 * 16); int posX; if (16 < posY || posY < 64 - 16) posX = Game1.RandomNumber.Next(16, 6 * 16); else posX = Game1.RandomNumber.Next(0, 8 * 16); return new Vector2(_roomRectangle.X + 24 + posX, _roomRectangle.Y + 24 + posY); } private void Update() { _animatorBody.Update(); if (_drawGanonWeapon) _animatorWeapon.Update(); } private void Draw(SpriteBatch spriteBatch) { // draw the tail if (_drawMoldormTail) for (var i = _moldormTails.Length - 1; i >= 0; i--) { if (i != _moldormTails.Length - 1) _moldormTails[i].Sprite.SpriteShader = Sprite.SpriteShader; _moldormTails[i].Sprite.Draw(spriteBatch); } // draw the body parts if (!_hideBody) for (var i = 0; i < _bodyParts.Length; i++) DrawHelper.DrawNormalized(spriteBatch, _spriteBody, _bodyParts[i], Color.White); if (!_hideBody) _animatorBody.Draw(spriteBatch, _bodyPosition, Color.White); if (Sprite.SpriteShader != null) { spriteBatch.End(); ObjectManager.SpriteBatchBegin(spriteBatch, Sprite.SpriteShader); } // draw the weapon of ganon if (_drawGanonWeapon) _animatorWeapon.Draw(spriteBatch, EntityPosition.Position, Color.White); if (!_hideHead) Sprite.Draw(spriteBatch); if (Sprite.SpriteShader != null) { spriteBatch.End(); ObjectManager.SpriteBatchBegin(spriteBatch, Sprite.SpriteShader); } if (_finalState && (_finalEyeState == 1 || (_finalEyeState == 0 && _animatorEye.IsPlaying))) _animatorEye.Draw(spriteBatch, new Vector2(EntityPosition.X, EntityPosition.Y + 1), Color.White); if (Sprite.SpriteShader != null) { spriteBatch.End(); ObjectManager.SpriteBatchBegin(spriteBatch, null); } } public Values.HitCollision OnHit(GameObject gameObject, Vector2 direction, HitType damageType, int damage, bool pieceOfPower) { // slime attacked by powder if (_slimeForm && (damageType & HitType.MagicPowder) != 0) { _body.Velocity.X = direction.X; _body.Velocity.Y = direction.Y; if (_body.Velocity.Z > 0) _body.Velocity.Z = 0; _aiComponent.ChangeState("slimeDamaged"); _slimeLives--; return Values.HitCollision.Enemy; } if ((damageType & (HitType.Sword | HitType.SwordHold)) != 0 && (_aiComponent.CurrentStateId == "slimeJump" || _aiComponent.CurrentStateId == "slimeWait")) { _aiComponent.ChangeState("slimeHideExplode"); return Values.HitCollision.Enemy; } if (_aiComponent.CurrentStateId == "moldorm") { return Values.HitCollision.RepellingParticle; } // ganon damage form if (_ganonForm && !_aiDamageState.IsInDamageState() && (damageType & HitType.PegasusBootsSword) != 0) { _ganonLives -= damage; if (_ganonLives <= 0) _aiComponent.ChangeState("ganonDeath"); Game1.GameManager.PlaySoundEffect("D370-07-07"); _aiDamageState.SetDamageState(); return Values.HitCollision.Repelling; } if (_ganonForm && (damageType & HitType.Sword) != 0) { return Values.HitCollision.Repelling; } if (_aiComponent.CurrentStateId == "face" && !_aiDamageState.IsInDamageState()) { if ((damageType & (HitType.Hookshot | HitType.MagicRod | HitType.Bomb | HitType.Boomerang | HitType.Bow)) != 0) { _aiDamageState.SetDamageState(); _aiComponent.ChangeState("faceExplode"); return Values.HitCollision.Enemy; } else { _aiDamageState.SetDamageState(false); _body.Velocity.X += direction.X; _body.Velocity.Y += direction.Y; Game1.GameManager.PlaySoundEffect("D360-09-09"); Game1.GameManager.PlaySoundEffect("D370-17-11"); return Values.HitCollision.Repelling | Values.HitCollision.Repelling1; } } if (_aiComponent.CurrentStateId == "final") { // bow hit from below with an open eye if (!_aiDamageState.IsInDamageState() && ( (_finalEyeState == 0 && _animatorEye.CurrentFrameIndex < 1) || (_finalEyeState == 1 && _animatorEye.CurrentFrameIndex >= 1)) && (damageType & HitType.Bow) != 0 && MathF.Abs(direction.Y) > MathF.Abs(direction.X) && direction.Y < 0) { _aiDamageState.SetDamageState(); _finalStateLives--; if (_finalStateLives <= 0) _aiComponent.ChangeState("finalBlink"); Game1.GameManager.PlaySoundEffect("D370-07-07"); // randomly change the speed of the two parts _finalPartSpeed0 = (1 / 2500.0f * MathF.PI * 2) * (1 + (Game1.RandomNumber.Next(0, 101) - 50) / 500f); _finalPartSpeed1 = (1 / 2500.0f * MathF.PI * 2) * (1 + (Game1.RandomNumber.Next(0, 101) - 50) / 500f); return Values.HitCollision.Enemy; } else { return Values.HitCollision.RepellingParticle; } } return Values.HitCollision.None; } public Values.HitCollision HitTail(GameObject gameObject, Vector2 direction, HitType damageType, int damage, bool pieceOfPower) { if (!_aiDamageState.IsInDamageState() && _aiComponent.CurrentStateId == "moldorm" && ((damageType & HitType.Sword) != 0)) { _moldormLives -= damage; if (_moldormLives <= 0) _aiComponent.ChangeState("moldormDying"); Game1.GameManager.PlaySoundEffect("D370-07-07"); Game1.GameManager.StopSoundEffect("D360-56-38"); _moldormHit = true; _aiDamageState.SetDamageState(true); return Values.HitCollision.Enemy; } return Values.HitCollision.None; } private void OnCollision(Values.BodyCollision collision) { if (_aiComponent.CurrentStateId == "moldorm") { if (Game1.RandomNumber.Next(0, 2) == 0) _moldormDirection = -_moldormDirection; if ((collision & Values.BodyCollision.Horizontal) != 0) _moldormRadiant = (float)Math.Atan2(-_body.VelocityTarget.X * _directionChangeMultiplier, _body.VelocityTarget.Y); else if ((collision & Values.BodyCollision.Vertical) != 0) _moldormRadiant = (float)Math.Atan2(_body.VelocityTarget.X, -_body.VelocityTarget.Y * _directionChangeMultiplier); _directionChangeMultiplier *= 0.75f; } if (_aiComponent.CurrentStateId == "face") { if ((collision & Values.BodyCollision.Horizontal) != 0) { _body.Velocity.X = -_body.VelocityTarget.X * 0.125f; _body.VelocityTarget.X = 0; } else if ((collision & Values.BodyCollision.Vertical) != 0) { _body.Velocity.Y = -_body.VelocityTarget.Y * 0.125f; _body.VelocityTarget.Y = 0; } } } private bool OnPush(Vector2 direction, PushableComponent.PushType type) { if (!_pushRepel) return false; return true; } } }