mirror of
https://github.com/Phantop/LADXHD.git
synced 2024-11-01 04:14:22 +00:00
442 lines
17 KiB
C#
442 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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.Dungeon;
|
|
using ProjectZ.InGame.GameObjects.Things;
|
|
using ProjectZ.InGame.Map;
|
|
using ProjectZ.InGame.SaveLoad;
|
|
using ProjectZ.InGame.Things;
|
|
|
|
namespace ProjectZ.InGame.GameObjects.MidBoss
|
|
{
|
|
class MDodongoSnake : GameObject
|
|
{
|
|
private readonly List<GameObject> _collidingObjects = new List<GameObject>();
|
|
|
|
private readonly BodyComponent _body;
|
|
private readonly BodyDrawComponent _bodyDrawComponent;
|
|
private readonly AiComponent _aiComponent;
|
|
private readonly AiDamageState _damageState;
|
|
private readonly CSprite _sprite;
|
|
private readonly AiTriggerRandomTime _directionTrigger;
|
|
private readonly CBox _eatBox;
|
|
|
|
private readonly DictAtlasEntry _spriteHead;
|
|
private readonly DictAtlasEntry _spriteBody0;
|
|
private readonly DictAtlasEntry _spriteBody1;
|
|
private readonly DictAtlasEntry _spriteBody2;
|
|
|
|
private readonly string _saveKey;
|
|
private readonly int _color;
|
|
|
|
private Vector2 _bodyPosition;
|
|
private Vector2 _bodyExplosionPosition;
|
|
private Vector2 _turningPosition;
|
|
private Vector2 _bodyOffset;
|
|
private Vector2 _lastHeadPosition;
|
|
|
|
private int _direction;
|
|
private float _movementSpeed = 0.375f;
|
|
|
|
private float _explosionCounter;
|
|
|
|
private const float TailDistance = 12;
|
|
private float _bodyDistance;
|
|
private bool _wallCollision = true;
|
|
private bool _stopDraggin = true;
|
|
private bool _playedSwollowSound;
|
|
private bool _playerInRoom;
|
|
|
|
private int _lives = 3;
|
|
|
|
// @TODO: it looks like the body gets left behind when we move out of the screen
|
|
public MDodongoSnake() : base("snake blue") { }
|
|
|
|
public MDodongoSnake(Map.Map map, int posX, int posY, string saveKey, int color, bool resetKey) : base(map)
|
|
{
|
|
Tags = Values.GameObjectTag.Enemy;
|
|
|
|
EntityPosition = new CPosition(posX + 8, posY + 16, 0);
|
|
EntitySize = new Rectangle(-22, -8 - 22, 44, 44);
|
|
|
|
_bodyPosition = EntityPosition.Position;
|
|
_lastHeadPosition = EntityPosition.Position;
|
|
|
|
_saveKey = saveKey;
|
|
_color = color;
|
|
|
|
var strColor = _color == 0 ? "blue" : "green";
|
|
|
|
// was the boss already defeated?
|
|
if (!string.IsNullOrEmpty(_saveKey) && Game1.GameManager.SaveManager.GetString(_saveKey) == "1")
|
|
{
|
|
if (resetKey)
|
|
{
|
|
Game1.GameManager.SaveManager.SetString(_saveKey, "0");
|
|
}
|
|
else
|
|
{
|
|
IsDead = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
_spriteHead = Resources.GetSprite("snake " + strColor);
|
|
_spriteBody0 = Resources.GetSprite("snake body " + strColor);
|
|
_spriteBody1 = Resources.GetSprite("snake body");
|
|
_spriteBody2 = Resources.GetSprite("snake big " + strColor);
|
|
|
|
_eatBox = new CBox(EntityPosition, -1, -8, 2, 4, 8);
|
|
|
|
_sprite = new CSprite("snake " + strColor, EntityPosition, new Vector2(-8, -16));
|
|
|
|
_body = new BodyComponent(EntityPosition, -7, -13, 14, 12, 8)
|
|
{
|
|
MoveCollision = OnCollision,
|
|
Drag = 0.65f,
|
|
DragAir = 0.95f,
|
|
Gravity = -0.15f,
|
|
FieldRectangle = map.GetField(posX, posY),
|
|
AvoidTypes = Values.CollisionTypes.Hole | Values.CollisionTypes.NPCWall
|
|
};
|
|
|
|
var stateMoving = new AiState(UpdateMoving);
|
|
stateMoving.Trigger.Add(_directionTrigger = new AiTriggerRandomTime(ChangeDirection, 1000, 1500));
|
|
var stateExplosion = new AiState(UpdateExplosion);
|
|
|
|
_aiComponent = new AiComponent();
|
|
_aiComponent.States.Add("moving", stateMoving);
|
|
_aiComponent.States.Add("explosion", stateExplosion);
|
|
|
|
_damageState = new AiDamageState(this, _body, _aiComponent, _sprite, 8, false)
|
|
{
|
|
OnDeath = OnDeath
|
|
};
|
|
|
|
_bodyDrawComponent = new BodyDrawComponent(_body, _sprite, Values.LayerPlayer);
|
|
|
|
var damageCollider = new CBox(EntityPosition, -7, -11, 0, 14, 11, 8, true);
|
|
AddComponent(DamageFieldComponent.Index, new DamageFieldComponent(damageCollider, HitType.Enemy, 4));
|
|
|
|
var hittableBox = new CBox(EntityPosition, -7, -15, 0, 14, 14, 8, true);
|
|
AddComponent(PushableComponent.Index, new PushableComponent(_body.BodyBox, OnPush));
|
|
AddComponent(HittableComponent.Index, new HittableComponent(hittableBox, OnHit));
|
|
AddComponent(BodyComponent.Index, _body);
|
|
AddComponent(AiComponent.Index, _aiComponent);
|
|
AddComponent(DrawComponent.Index, new DrawComponent(Draw, Values.LayerPlayer, EntityPosition));
|
|
AddComponent(DrawShadowComponent.Index, new BodyDrawShadowComponent(_body, _sprite) { ShadowWidth = 16, ShadowHeight = 6 });
|
|
|
|
ChangeDirection();
|
|
_aiComponent.ChangeState("moving");
|
|
}
|
|
|
|
private bool OnPush(Vector2 direction, PushableComponent.PushType pushType)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
private Values.HitCollision OnHit(GameObject gameObject, Vector2 direction, HitType damageType, int damage, bool pieceOfPower)
|
|
{
|
|
return Values.HitCollision.RepellingParticle;
|
|
}
|
|
|
|
private void OnCollision(Values.BodyCollision collision)
|
|
{
|
|
_wallCollision = true;
|
|
_directionTrigger.CurrentTime = Math.Min(_directionTrigger.CurrentTime, 250);
|
|
}
|
|
|
|
private void ChangeDirection()
|
|
{
|
|
_direction = Game1.RandomNumber.Next(0, 4);
|
|
|
|
_body.VelocityTarget = AnimationHelper.DirectionOffset[_direction] * _movementSpeed;
|
|
|
|
_turningPosition = EntityPosition.Position;
|
|
|
|
if (_wallCollision)
|
|
{
|
|
_stopDraggin = true;
|
|
_wallCollision = false;
|
|
}
|
|
}
|
|
|
|
private void ToExploding()
|
|
{
|
|
_playedSwollowSound = false;
|
|
_aiComponent.ChangeState("explosion");
|
|
_bodyExplosionPosition = _bodyPosition;
|
|
_damageState.SetDamageState();
|
|
}
|
|
|
|
private void UpdateExplosion()
|
|
{
|
|
_body.VelocityTarget = Vector2.Zero;
|
|
_explosionCounter += Game1.DeltaTime;
|
|
|
|
// swollow sound effect
|
|
if (!_playedSwollowSound && _explosionCounter > 55 / 0.06)
|
|
{
|
|
_playedSwollowSound = true;
|
|
Game1.GameManager.PlaySoundEffect("D360-42-2A");
|
|
}
|
|
|
|
if (_explosionCounter > 94 / 0.06 && _explosionCounter - Game1.DeltaTime < 94 / 0.06)
|
|
{
|
|
Game1.GameManager.PlaySoundEffect("D378-12-0C");
|
|
|
|
var particlePosition = EntityPosition.Position + AnimationHelper.DirectionOffset[_direction] * 13;
|
|
Map.Objects.SpawnObject(new ObjAnimator(Map,
|
|
(int)particlePosition.X, (int)particlePosition.Y, -8, -16, Values.LayerPlayer, "Particles/spawn", "run", true));
|
|
}
|
|
|
|
if (_explosionCounter > 76 / 0.06 && _explosionCounter - Game1.DeltaTime < 76 / 0.06)
|
|
{
|
|
_lives--;
|
|
|
|
// enemy is dead?
|
|
if (_lives <= 0)
|
|
{
|
|
OnDeath();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (_explosionCounter > 112 / 0.06)
|
|
{
|
|
_explosionCounter = 0;
|
|
ChangeDirection();
|
|
_aiComponent.ChangeState("moving");
|
|
_bodyPosition = _bodyExplosionPosition;
|
|
}
|
|
}
|
|
|
|
private void OnDeath()
|
|
{
|
|
if (!string.IsNullOrEmpty(_saveKey))
|
|
Game1.GameManager.SaveManager.SetString(_saveKey, "1");
|
|
|
|
// stop the boss music
|
|
Game1.GameManager.SetMusic(-1, 2);
|
|
|
|
Game1.GameManager.PlaySoundEffect("D378-26-1A");
|
|
|
|
// spawn fairy
|
|
Game1.GameManager.PlaySoundEffect("D360-27-1B");
|
|
Map.Objects.SpawnObject(new ObjDungeonFairy(Map, (int)_bodyExplosionPosition.X, (int)_bodyExplosionPosition.Y + 8, 0));
|
|
|
|
// shake the screen
|
|
Game1.GameManager.ShakeScreen(225, 4, 1, 5, 2.5f);
|
|
|
|
// spawn explosion effect
|
|
Map.Objects.SpawnObject(new ObjAnimator(Map,
|
|
(int)_bodyExplosionPosition.X, (int)_bodyExplosionPosition.Y - 8, Values.LayerPlayer, "Particles/explosionBomb", "run2", true));
|
|
|
|
Map.Objects.DeleteObjects.Add(this);
|
|
}
|
|
|
|
private void UpdateMoving()
|
|
{
|
|
// start/stop music when the player enters/leaves the room
|
|
if (_body.FieldRectangle.Contains(MapManager.ObjLink.BodyRectangle))
|
|
{
|
|
_playerInRoom = true;
|
|
if (Game1.GameManager.GetCurrentMusic() != 79)
|
|
Game1.GameManager.SetMusic(79, 2);
|
|
}
|
|
else if (_playerInRoom)
|
|
{
|
|
_playerInRoom = false;
|
|
Game1.GameManager.SetMusic(-1, 2);
|
|
}
|
|
|
|
EatBombs();
|
|
|
|
var offset = 0.5f;
|
|
var speed = 55;
|
|
_sprite.DrawOffset.X = -8 + ((_direction == 0 || _direction == 2) ? MathF.Sin((float)(Game1.TotalGameTime / speed)) * offset : 0);
|
|
_sprite.DrawOffset.Y = -16 + ((_direction == 1 || _direction == 3) ? MathF.Sin((float)(Game1.TotalGameTime / speed)) * offset : 0);
|
|
|
|
_bodyOffset.X = (_direction == 0 || _direction == 2) ? MathF.Sin((float)(Game1.TotalGameTime / speed) + MathF.PI * 0.9f) * offset : 0;
|
|
_bodyOffset.Y = (_direction == 1 || _direction == 3) ? MathF.Sin((float)(Game1.TotalGameTime / speed) + MathF.PI * 0.9f) * offset : 0;
|
|
|
|
// updated body distance
|
|
var distance = (_lastHeadPosition - EntityPosition.Position).Length();
|
|
_bodyDistance += distance;
|
|
|
|
if (distance < 0.001f)
|
|
{
|
|
_sprite.DrawOffset.X = -8;
|
|
_sprite.DrawOffset.Y = -16;
|
|
}
|
|
|
|
if (_bodyDistance > TailDistance)
|
|
{
|
|
_bodyDistance = TailDistance;
|
|
_stopDraggin = false;
|
|
}
|
|
|
|
if (!_stopDraggin || _wallCollision)
|
|
{
|
|
_bodyDistance -= _movementSpeed * Game1.TimeMultiplier;
|
|
if (_bodyDistance < 0)
|
|
_bodyDistance = 0;
|
|
}
|
|
|
|
// drag the body behind the head
|
|
if (_turningPosition != Vector2.Zero)
|
|
{
|
|
// update position
|
|
var directionTurningPoint = _turningPosition - EntityPosition.Position;
|
|
var turningPointDistance = directionTurningPoint.Length();
|
|
if (turningPointDistance > _bodyDistance)
|
|
{
|
|
directionTurningPoint.Normalize();
|
|
_bodyPosition = EntityPosition.Position + directionTurningPoint * _bodyDistance;
|
|
_turningPosition = Vector2.Zero;
|
|
}
|
|
else
|
|
{
|
|
// update position
|
|
var direction = _bodyPosition - _turningPosition;
|
|
if (direction != Vector2.Zero)
|
|
{
|
|
direction.Normalize();
|
|
_bodyPosition = _turningPosition + direction * (_bodyDistance - turningPointDistance);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// update position
|
|
var direction = _bodyPosition - EntityPosition.Position;
|
|
|
|
if (direction != Vector2.Zero)
|
|
{
|
|
direction.Normalize();
|
|
_bodyPosition = EntityPosition.Position + direction * _bodyDistance;
|
|
}
|
|
}
|
|
|
|
_lastHeadPosition = EntityPosition.Position;
|
|
}
|
|
|
|
private void EatBombs()
|
|
{
|
|
_collidingObjects.Clear();
|
|
Map.Objects.GetComponentList(_collidingObjects,
|
|
(int)EntityPosition.Position.X - 8, (int)EntityPosition.Position.Y - 16, 16, 16, BodyComponent.Mask);
|
|
|
|
foreach (var collidingObject in _collidingObjects)
|
|
{
|
|
var body = (BodyComponent)collidingObject.Components[BodyComponent.Index];
|
|
|
|
if (collidingObject.GetType() == typeof(ObjBomb) && _eatBox.Box.Intersects(body.BodyBox.Box))
|
|
{
|
|
var bomb = (ObjBomb)collidingObject;
|
|
if (bomb.Body.IsActive)
|
|
{
|
|
bomb.IsActive = false;
|
|
bomb.Map.Objects.DeleteObjects.Add(bomb);
|
|
|
|
ToExploding();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Draw(SpriteBatch spriteBatch)
|
|
{
|
|
_sprite.SourceRectangle.X = _spriteHead.ScaledRectangle.X;
|
|
_sprite.SourceRectangle.Y = _spriteHead.ScaledRectangle.Y;
|
|
|
|
if (_direction == 1)
|
|
_sprite.SourceRectangle.X += 18;
|
|
else if (_direction == 3)
|
|
_sprite.SourceRectangle.X += 36;
|
|
|
|
_sprite.SpriteEffect = _direction == 2 ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
|
|
|
var bodyDrawPosition = _bodyPosition + new Vector2(-8, -16) + _bodyOffset;
|
|
var bodyRectangle = _spriteBody0.ScaledRectangle;
|
|
|
|
// explosion going on?
|
|
if (_explosionCounter > 0)
|
|
{
|
|
_sprite.DrawOffset = new Vector2(-8, -16);
|
|
|
|
// change the color to green
|
|
if (_explosionCounter < 102 / 0.06)
|
|
{
|
|
var dir = _color == 0 ? 1 : -1;
|
|
_sprite.SourceRectangle.Y += 18 * dir;
|
|
bodyRectangle.Y += 18 * dir;
|
|
}
|
|
|
|
var targetPosition = EntityPosition.Position - new Vector2(
|
|
AnimationHelper.DirectionOffset[_direction].X * 13,
|
|
AnimationHelper.DirectionOffset[_direction].Y * 12);
|
|
var distance = (_bodyExplosionPosition - targetPosition).Length();
|
|
|
|
if (distance > 0)
|
|
{
|
|
var amount = Math.Min(1, (1 * Game1.TimeMultiplier) / distance);
|
|
_bodyExplosionPosition = Vector2.Lerp(_bodyExplosionPosition, targetPosition, amount);
|
|
}
|
|
|
|
if (_explosionCounter < 60 / 0.06)
|
|
{
|
|
|
|
}
|
|
else if (_explosionCounter < 66 / 0.06)
|
|
{
|
|
bodyRectangle = _spriteBody1.ScaledRectangle;
|
|
_sprite.DrawOffset += AnimationHelper.DirectionOffset[_direction] * 2;
|
|
}
|
|
else if (_explosionCounter < 86 / 0.06)
|
|
{
|
|
bodyRectangle = _spriteBody2.ScaledRectangle;
|
|
_sprite.DrawOffset += AnimationHelper.DirectionOffset[_direction] * 4;
|
|
}
|
|
else if (_explosionCounter < 92 / 0.06)
|
|
{
|
|
bodyRectangle = _spriteBody1.ScaledRectangle;
|
|
_sprite.DrawOffset += AnimationHelper.DirectionOffset[_direction] * 2;
|
|
}
|
|
else if (_explosionCounter < 98 / 0.06)
|
|
{
|
|
|
|
}
|
|
|
|
bodyDrawPosition = _bodyExplosionPosition + new Vector2(-bodyRectangle.Width / 2, -8 - bodyRectangle.Height / 2);
|
|
}
|
|
|
|
var drawBodyFirst = bodyDrawPosition.Y + bodyRectangle.Height <= EntityPosition.Y || (_explosionCounter > 0 && _direction != 1);
|
|
|
|
// draw the body
|
|
if (drawBodyFirst)
|
|
spriteBatch.Draw(_spriteHead.Texture, bodyDrawPosition, bodyRectangle, Color.White);
|
|
|
|
// draw the head
|
|
_bodyDrawComponent.Draw(spriteBatch);
|
|
|
|
// draw the body
|
|
if (!drawBodyFirst)
|
|
spriteBatch.Draw(_spriteHead.Texture, bodyDrawPosition, bodyRectangle, Color.White);
|
|
|
|
}
|
|
|
|
private void OnDeath(bool pieceOfPower)
|
|
{
|
|
_aiComponent.ChangeState("death");
|
|
|
|
Game1.GameManager.PlaySoundEffect("D370-16-10");
|
|
}
|
|
}
|
|
}
|