mirror of
https://github.com/Phantop/LADXHD.git
synced 2024-11-01 04:14:22 +00:00
983 lines
44 KiB
C#
983 lines
44 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using ProjectZ.Base;
|
|
using ProjectZ.InGame.GameObjects;
|
|
using ProjectZ.InGame.GameObjects.Base;
|
|
using ProjectZ.InGame.GameObjects.Base.Components;
|
|
using ProjectZ.InGame.GameObjects.Base.Pools;
|
|
using ProjectZ.InGame.GameObjects.Base.Systems;
|
|
using ProjectZ.InGame.GameObjects.Things;
|
|
using ProjectZ.InGame.SaveLoad;
|
|
using ProjectZ.InGame.Things;
|
|
|
|
namespace ProjectZ.InGame.Map
|
|
{
|
|
public class ObjectManager
|
|
{
|
|
// TODO_End check if everything is cleaned while loading a new map
|
|
public Map Owner;
|
|
|
|
public List<GameObjectItem> ObjectList = new List<GameObjectItem>();
|
|
private List<GameObject> SpawnObjects = new List<GameObject>();
|
|
public List<GameObject> DeleteObjects = new List<GameObject>();
|
|
|
|
public Texture2D ShadowTexture;
|
|
public static Effect CurrentEffect;
|
|
|
|
private List<GameObject> _poolSpawnedObjects = new List<GameObject>();
|
|
|
|
private ComponentPool _gameObjectPool;
|
|
private ComponentDrawPoolNew _drawPool;
|
|
|
|
private SystemBody _systemBody = new SystemBody();
|
|
private SystemAi _systemAi = new SystemAi();
|
|
private SystemAnimation _systemAnimator = new SystemAnimation();
|
|
|
|
private List<KeyChangeListenerComponent.KeyChangeTemplate> _keyChangeListeners =
|
|
new List<KeyChangeListenerComponent.KeyChangeTemplate>();
|
|
|
|
// lists are used to not generate new ones every time
|
|
// one list object would probably be enough?
|
|
private readonly List<GameObject> _updateGameObject = new List<GameObject>();
|
|
private readonly List<GameObject> _damageFieldObjects = new List<GameObject>();
|
|
private readonly List<GameObject> _drawShadowObjects = new List<GameObject>();
|
|
private readonly List<GameObject> _depthObjectList = new List<GameObject>();
|
|
private readonly List<GameObject> _objectTypeList = new List<GameObject>();
|
|
private readonly List<GameObject> _objectTagAllList = new List<GameObject>();
|
|
private readonly List<GameObject> _collisionObjectList = new List<GameObject>();
|
|
|
|
private readonly List<GameObject> _collidingObjectList = new List<GameObject>();
|
|
private readonly List<GameObject> _lightObjectList = new List<GameObject>();
|
|
private readonly List<GameObject> _carriableObjectList = new List<GameObject>();
|
|
private readonly List<GameObject> _hittableObjectList = new List<GameObject>();
|
|
private readonly List<GameObject> _pushableObjectList = new List<GameObject>();
|
|
private readonly List<GameObject> _interactableObjectList = new List<GameObject>();
|
|
|
|
private readonly List<GameObject> db_hittableList = new List<GameObject>();
|
|
private readonly List<GameObject> db_damageList = new List<GameObject>();
|
|
private readonly List<GameObject> db_bodyList = new List<GameObject>();
|
|
private readonly List<GameObject> db_gameObjectList = new List<GameObject>();
|
|
|
|
private bool _keyChanged;
|
|
private bool _finishedLoading;
|
|
|
|
public ObjectManager(Map owner)
|
|
{
|
|
Owner = owner;
|
|
}
|
|
|
|
public void LoadObjects()
|
|
{
|
|
// TODO_End tweak the size for best performance for the finished game
|
|
// the size of the pools can be tweaked for faster update times
|
|
_gameObjectPool = new ComponentPool(Owner, Owner.MapWidth, Owner.MapHeight, 32, 32);
|
|
_drawPool = new ComponentDrawPoolNew(Owner.MapWidth, Owner.MapHeight, 32, 32);
|
|
|
|
_systemAnimator.Pool = _gameObjectPool;
|
|
_systemAi.Pool = _gameObjectPool;
|
|
_systemBody.Pool = _gameObjectPool;
|
|
|
|
ClearPools();
|
|
|
|
foreach (var gameObj in ObjectList)
|
|
{
|
|
var gameObject = GetGameObject(Owner, gameObj.Index, gameObj.Parameter);
|
|
AddObjectToMap(gameObject);
|
|
}
|
|
|
|
// done after calling the constructors before the init methode
|
|
// stonespawner adds sprites that can be accessed in the init methode
|
|
// we make sure to not call the init methodes to not call it twice
|
|
AddSpawnedObjects(true);
|
|
|
|
// okay so one problem are the dodongo snakes:
|
|
// a chest should get spawned after killing two of them
|
|
// but after reloading the chest should not be there
|
|
// so the snakes will reset a key on reload
|
|
// now the key condition setter sets the key
|
|
// and the position dialog spawns the chest
|
|
// so in the init method the key needs to be set correctly
|
|
// for this to work the key condition setter will need to be updated after the snakes are created
|
|
// for this we need to call the key listeners before calling the init method
|
|
UpdateKeyListeners();
|
|
|
|
foreach (var gameObject in _poolSpawnedObjects)
|
|
gameObject.Init();
|
|
|
|
// ensures key setters, key condition setter, etc are all set correctly after loading the objects
|
|
UpdateKeyListeners();
|
|
|
|
// done before and after init because ObjEnemyTrigger Init expects objects spawned in the constructor
|
|
AddSpawnedObjects();
|
|
|
|
_finishedLoading = true;
|
|
}
|
|
|
|
public void Update(bool frozen)
|
|
{
|
|
// mode used for opened dialog
|
|
if (frozen)
|
|
{
|
|
// notify all key listener
|
|
UpdateKeyListeners();
|
|
|
|
UpdateDeleteObjects();
|
|
|
|
AddSpawnedObjects();
|
|
|
|
return;
|
|
}
|
|
|
|
if (Game1.GameManager.FreezeWorldAroundPlayer)
|
|
{
|
|
Game1.GameManager.FreezeWorldAroundPlayer = false;
|
|
|
|
// only update the player
|
|
var updateComponent = (UpdateComponent)MapManager.ObjLink.Components[UpdateComponent.Index];
|
|
updateComponent?.UpdateFunction();
|
|
|
|
return;
|
|
}
|
|
|
|
Game1.StopWatchTracker.Start("update gameobjects");
|
|
|
|
_systemAnimator.Update(false);
|
|
|
|
UpdateGameObjects();
|
|
|
|
_systemAi.Update();
|
|
|
|
// notify all key listener
|
|
UpdateKeyListeners();
|
|
|
|
_systemBody.Update(0, 1);
|
|
|
|
UpdatePlayerCollision();
|
|
|
|
UpdateDamageFields();
|
|
|
|
UpdateDeleteObjects();
|
|
|
|
AddSpawnedObjects();
|
|
}
|
|
|
|
public void UpdateAnimations()
|
|
{
|
|
_systemAnimator.Update(true);
|
|
}
|
|
|
|
private void AddSpawnedObjects(bool suppressInit = false)
|
|
{
|
|
// add newly spawned objects
|
|
if (SpawnObjects.Count > 0)
|
|
{
|
|
// ObjChest is adding to the SpawnObjects list in it's init method
|
|
for (var index = 0; index < SpawnObjects.Count; index++)
|
|
{
|
|
if (!suppressInit)
|
|
SpawnObjects[index].Init();
|
|
AddObjectToMap(SpawnObjects[index]);
|
|
}
|
|
|
|
SpawnObjects.Clear();
|
|
}
|
|
}
|
|
|
|
private void UpdateGameObjects()
|
|
{
|
|
_updateGameObject.Clear();
|
|
|
|
// only update the objects that are in a tile that is visible
|
|
var updateFieldSize = new Vector2(Game1.RenderWidth, Game1.RenderHeight);
|
|
_gameObjectPool.GetComponentList(_updateGameObject,
|
|
(int)((MapManager.Camera.X - updateFieldSize.X / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - updateFieldSize.Y / 2) / MapManager.Camera.Scale),
|
|
(int)(updateFieldSize.X / MapManager.Camera.Scale),
|
|
(int)(updateFieldSize.Y / MapManager.Camera.Scale), UpdateComponent.Mask);
|
|
|
|
foreach (var gameObject in _updateGameObject)
|
|
{
|
|
if (gameObject.IsActive)
|
|
(gameObject.Components[UpdateComponent.Index] as UpdateComponent)?.UpdateFunction();
|
|
}
|
|
}
|
|
|
|
private void UpdateKeyListeners()
|
|
{
|
|
// notify all key listeners
|
|
// repeat the process so key changes that depend on different key changes get processed in a single frame
|
|
// max is used to avoid an infinite loop in case of a bug
|
|
var max = 5;
|
|
while (_keyChanged && max > 0)
|
|
{
|
|
max--;
|
|
_keyChanged = false;
|
|
|
|
// notify key listeners if a key value was changed
|
|
foreach (var listener in _keyChangeListeners)
|
|
listener();
|
|
}
|
|
}
|
|
|
|
private void UpdatePlayerCollision()
|
|
{
|
|
// player collides with stuff
|
|
var player = MapManager.ObjLink;
|
|
|
|
_collidingObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_collidingObjectList,
|
|
(int)player.BodyRectangle.X, (int)player.BodyRectangle.Y,
|
|
(int)player.BodyRectangle.Width, (int)player.BodyRectangle.Height, ObjectCollisionComponent.Mask);
|
|
|
|
foreach (var gameObject in _collidingObjectList)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var component = gameObject.Components[ObjectCollisionComponent.Index] as ObjectCollisionComponent;
|
|
if (component.TriggerOnCollision && component.CollisionRectangle.Rectangle.Intersects(player.BodyRectangle) ||
|
|
!component.TriggerOnCollision && component.CollisionRectangle.Rectangle.Contains(player.BodyRectangle))
|
|
component.OnCollision(player);
|
|
}
|
|
}
|
|
|
|
private void UpdateDamageFields()
|
|
{
|
|
var player = MapManager.ObjLink;
|
|
var playerDamageBox = player.DamageCollider.Box;
|
|
|
|
// get the objects that could potentially inflic damage
|
|
_damageFieldObjects.Clear();
|
|
_gameObjectPool.GetComponentList(_damageFieldObjects,
|
|
(int)playerDamageBox.X, (int)playerDamageBox.Y,
|
|
(int)playerDamageBox.Width, (int)playerDamageBox.Height, DamageFieldComponent.Mask);
|
|
|
|
foreach (var gameObject in _damageFieldObjects)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var damageField = (gameObject.Components[DamageFieldComponent.Index] as DamageFieldComponent);
|
|
if (damageField.IsActive && damageField.CollisionBox.Box.Intersects(playerDamageBox))
|
|
damageField.OnDamage?.Invoke();
|
|
}
|
|
}
|
|
|
|
private void UpdateDeleteObjects()
|
|
{
|
|
Game1.StopWatchTracker.Start("delete gameObjects");
|
|
|
|
if (DeleteObjects.Count > 0)
|
|
{
|
|
foreach (var deletable in DeleteObjects)
|
|
RemoveObject(deletable);
|
|
|
|
DeleteObjects.Clear();
|
|
}
|
|
|
|
Game1.StopWatchTracker.Stop();
|
|
}
|
|
|
|
public static void SpriteBatchBegin(SpriteBatch spriteBatch, SpriteShader spriteShader)
|
|
{
|
|
SetSpriteShader(spriteShader);
|
|
|
|
CurrentEffect = spriteShader?.Effect;
|
|
spriteBatch.Begin(SpriteSortMode.Deferred, null,
|
|
MapManager.Camera.Scale >= 1 ? SamplerState.PointWrap : SamplerState.AnisotropicWrap,
|
|
null, null, CurrentEffect, MapManager.Camera.TransformMatrix);
|
|
}
|
|
|
|
public static void SetSpriteShader(SpriteShader spriteShader)
|
|
{
|
|
// update the parameters of the shader
|
|
if (spriteShader != null)
|
|
foreach (var parameter in spriteShader.FloatParameter)
|
|
spriteShader.Effect.Parameters[parameter.Key].SetValue(parameter.Value);
|
|
}
|
|
|
|
public static void SpriteBatchBeginAnisotropic(SpriteBatch spriteBatch, Effect effect)
|
|
{
|
|
CurrentEffect = effect;
|
|
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.AnisotropicClamp,
|
|
null, null, effect, MapManager.Camera.TransformMatrix);
|
|
}
|
|
|
|
public void DrawBottom(SpriteBatch spriteBatch)
|
|
{
|
|
if (!_finishedLoading)
|
|
return;
|
|
|
|
Game1.StopWatchTracker.Start("2 draw sorted objects");
|
|
|
|
SpriteBatchBegin(spriteBatch, null);
|
|
|
|
_drawPool.DrawPool(spriteBatch,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), 0, 1);
|
|
|
|
spriteBatch.End();
|
|
}
|
|
|
|
public void DrawMiddle(SpriteBatch spriteBatch)
|
|
{
|
|
if (!_finishedLoading)
|
|
return;
|
|
|
|
Game1.StopWatchTracker.Start("2 draw sorted objects shadow");
|
|
|
|
SpriteBatchBegin(spriteBatch, null);
|
|
|
|
_drawPool.DrawPool(spriteBatch,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), 1, 2);
|
|
spriteBatch.End();
|
|
|
|
// draw the hole map
|
|
Owner.HoleMap.Draw(spriteBatch);
|
|
|
|
Game1.StopWatchTracker.Start("3 draw the shadows");
|
|
if (GameSettings.EnableShadows && Owner.UseShadows && !Game1.GameManager.UseShockEffect && ShadowTexture != null)
|
|
{
|
|
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.AnisotropicClamp);//, null, null, null, Game1.GameManager.GetMatrix);
|
|
spriteBatch.Draw(ShadowTexture, new Rectangle(0, 0, Game1.GameManager.CurrentRenderWidth, Game1.GameManager.CurrentRenderHeight), Color.Black * 0.55f);
|
|
spriteBatch.End();
|
|
}
|
|
}
|
|
|
|
public void Draw(SpriteBatch spriteBatch)
|
|
{
|
|
if (!_finishedLoading)
|
|
return;
|
|
|
|
Game1.StopWatchTracker.Start("4 draw sorted objects");
|
|
spriteBatch.Begin(SpriteSortMode.Deferred, null,
|
|
MapManager.Camera.Scale >= 1 ? SamplerState.PointWrap : SamplerState.AnisotropicWrap,
|
|
null, null, null, MapManager.Camera.TransformMatrix);
|
|
_drawPool.DrawPool(spriteBatch,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), 2, 4);
|
|
spriteBatch.End();
|
|
|
|
// draw the body colliders
|
|
if (Game1.DebugMode)
|
|
{
|
|
Game1.StopWatchTracker.Start("5 debug draw");
|
|
|
|
spriteBatch.Begin(SpriteSortMode.Deferred, null,
|
|
MapManager.Camera.Scale >= 1 ? SamplerState.PointWrap : SamplerState.AnisotropicWrap,
|
|
null, null, null, MapManager.Camera.TransformMatrix);
|
|
|
|
// draw entity size rectangle
|
|
if (Game1.DebugBoxMode == 0)
|
|
{
|
|
db_gameObjectList.Clear();
|
|
_gameObjectPool.GetObjectList(db_gameObjectList,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale));
|
|
foreach (var gameObject in db_gameObjectList)
|
|
{
|
|
if (gameObject.EntityPosition != null && !(gameObject is ObjLamp))
|
|
{
|
|
var rectangle = new RectangleF(
|
|
gameObject.EntityPosition.X + gameObject.EntitySize.X,
|
|
gameObject.EntityPosition.Y + gameObject.EntitySize.Y,
|
|
gameObject.EntitySize.Width, gameObject.EntitySize.Height);
|
|
|
|
DrawRectangle(spriteBatch, rectangle, Color.LightBlue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// draw the bodies
|
|
if (Game1.DebugBoxMode == 0 || Game1.DebugBoxMode == 1)
|
|
{
|
|
db_bodyList.Clear();
|
|
_gameObjectPool.GetComponentList(db_bodyList,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), BodyComponent.Mask);
|
|
foreach (var drawTile in db_bodyList)
|
|
{
|
|
var body = drawTile.Components[BodyComponent.Index] as BodyComponent;
|
|
DrawRectangle(spriteBatch, body.BodyBox.Box.Rectangle(), Color.Red);
|
|
}
|
|
}
|
|
|
|
// draw the damage fields
|
|
if (Game1.DebugBoxMode == 0 || Game1.DebugBoxMode == 2)
|
|
{
|
|
db_damageList.Clear();
|
|
_gameObjectPool.GetComponentList(db_damageList,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), DamageFieldComponent.Mask);
|
|
foreach (var drawTile in db_damageList)
|
|
{
|
|
var damageComponent = drawTile.Components[DamageFieldComponent.Index] as DamageFieldComponent;
|
|
DrawRectangle(spriteBatch, damageComponent.CollisionBox.Box.Rectangle(), Color.Green);
|
|
}
|
|
}
|
|
|
|
// draw the hittable fields
|
|
if (Game1.DebugBoxMode == 0 || Game1.DebugBoxMode == 3)
|
|
{
|
|
db_damageList.Clear();
|
|
_gameObjectPool.GetComponentList(db_damageList,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), HittableComponent.Mask);
|
|
foreach (var drawTile in db_damageList)
|
|
{
|
|
var hittableComponent = drawTile.Components[HittableComponent.Index] as HittableComponent;
|
|
DrawRectangle(spriteBatch, hittableComponent.HittableBox.Box.Rectangle(), Color.Yellow);
|
|
}
|
|
}
|
|
|
|
// draw the push fields
|
|
if (Game1.DebugBoxMode == 0 || Game1.DebugBoxMode == 4)
|
|
{
|
|
db_damageList.Clear();
|
|
_gameObjectPool.GetComponentList(db_damageList,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), PushableComponent.Mask);
|
|
foreach (var drawTile in db_damageList)
|
|
{
|
|
var pushableComponent = drawTile.Components[PushableComponent.Index] as PushableComponent;
|
|
DrawRectangle(spriteBatch, pushableComponent.PushableBox.Box.Rectangle(), Color.Orange);
|
|
}
|
|
}
|
|
|
|
// draw the interact rectangle
|
|
if (Game1.DebugBoxMode == 0 || Game1.DebugBoxMode == 5)
|
|
{
|
|
db_damageList.Clear();
|
|
_gameObjectPool.GetComponentList(db_damageList,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), InteractComponent.Mask);
|
|
foreach (var drawTile in db_damageList)
|
|
{
|
|
var pushableComponent = drawTile.Components[InteractComponent.Index] as InteractComponent;
|
|
DrawRectangle(spriteBatch, pushableComponent.BoxInteractabel.Box.Rectangle(), Color.Aqua);
|
|
}
|
|
}
|
|
|
|
spriteBatch.End();
|
|
}
|
|
|
|
Game1.StopWatchTracker.Stop();
|
|
|
|
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointWrap, null, null, null, MapManager.Camera.TransformMatrix);
|
|
}
|
|
|
|
private void DrawRectangle(SpriteBatch spriteBatch, RectangleF rectangle, Color color)
|
|
{
|
|
spriteBatch.Draw(Resources.SprWhite,
|
|
new Vector2(rectangle.X, rectangle.Y),
|
|
new Rectangle(0, 0, (int)rectangle.Width, (int)rectangle.Height), color * 0.25f);
|
|
|
|
var thickness = 1 / (float)Game1.ScreenScale;
|
|
spriteBatch.Draw(Resources.SprWhite,
|
|
new Vector2(rectangle.X, rectangle.Y),
|
|
new Rectangle(0, 0, 1, (int)(rectangle.Height * Game1.ScreenScale)),
|
|
color * 0.75f, 0, Vector2.Zero, thickness, SpriteEffects.None, 0);
|
|
spriteBatch.Draw(Resources.SprWhite,
|
|
new Vector2(rectangle.X + rectangle.Width - thickness, rectangle.Y),
|
|
new Rectangle(0, 0, 1, (int)(rectangle.Height * Game1.ScreenScale)),
|
|
color * 0.75f, 0, Vector2.Zero, thickness, SpriteEffects.None, 0);
|
|
spriteBatch.Draw(Resources.SprWhite,
|
|
new Vector2(rectangle.X, rectangle.Y),
|
|
new Rectangle(0, 0, (int)(rectangle.Width * Game1.ScreenScale), 1),
|
|
color * 0.75f, 0, Vector2.Zero, thickness, SpriteEffects.None, 0);
|
|
spriteBatch.Draw(Resources.SprWhite,
|
|
new Vector2(rectangle.X, rectangle.Y + rectangle.Height - thickness),
|
|
new Rectangle(0, 0, (int)(rectangle.Width * Game1.ScreenScale), 1),
|
|
color * 0.75f, 0, Vector2.Zero, thickness, SpriteEffects.None, 0);
|
|
}
|
|
|
|
public void DrawShadow(SpriteBatch spriteBatch)
|
|
{
|
|
DrawHelper.StartShadowDrawing();
|
|
|
|
// draw the shadows
|
|
_drawShadowObjects.Clear();
|
|
_gameObjectPool.GetComponentList(_drawShadowObjects,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), DrawShadowComponent.Mask);
|
|
|
|
foreach (var gameObject in _drawShadowObjects)
|
|
if (gameObject.IsActive)
|
|
(gameObject.Components[DrawShadowComponent.Index] as DrawShadowComponent)?.Draw(spriteBatch);
|
|
|
|
DrawHelper.EndShadowDrawing();
|
|
}
|
|
|
|
public void DrawLight(SpriteBatch spriteBatch)
|
|
{
|
|
// draw the shadows
|
|
_lightObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_lightObjectList,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), LightDrawComponent.Mask);
|
|
|
|
_lightObjectList.Sort((obj0, obj1) =>
|
|
{
|
|
var light0 = (LightDrawComponent)obj0.Components[LightDrawComponent.Index];
|
|
var light1 = (LightDrawComponent)obj1.Components[LightDrawComponent.Index];
|
|
|
|
if (light0 != null && light1 != null)
|
|
{
|
|
if (light0.Layer == light1.Layer &&
|
|
light0.Owner.EntityPosition != null && light1.Owner.EntityPosition != null)
|
|
return (int)light0.Owner.EntityPosition.Y - (int)light1.Owner.EntityPosition.Y;
|
|
|
|
return light0.Layer - light1.Layer;
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
for (var i = 0; i < _lightObjectList.Count; i++)
|
|
{
|
|
if (_lightObjectList[i].IsActive)
|
|
(_lightObjectList[i].Components[LightDrawComponent.Index] as LightDrawComponent)?.Draw(spriteBatch);
|
|
}
|
|
}
|
|
|
|
public void DrawBlur(SpriteBatch spriteBatch)
|
|
{
|
|
// draw the shadows
|
|
_drawShadowObjects.Clear();
|
|
_gameObjectPool.GetComponentList(_drawShadowObjects,
|
|
(int)((MapManager.Camera.X - Game1.RenderWidth / 2) / MapManager.Camera.Scale),
|
|
(int)((MapManager.Camera.Y - Game1.RenderHeight / 2) / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderWidth / MapManager.Camera.Scale),
|
|
(int)(Game1.RenderHeight / MapManager.Camera.Scale), BlurDrawComponent.Mask);
|
|
|
|
foreach (var gameObject in _drawShadowObjects)
|
|
if (gameObject.IsActive)
|
|
(gameObject.Components[BlurDrawComponent.Index] as BlurDrawComponent)?.Draw(spriteBatch);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
ObjectList.Clear();
|
|
}
|
|
|
|
private void ClearPools()
|
|
{
|
|
_keyChangeListeners.Clear();
|
|
_poolSpawnedObjects.Clear();
|
|
}
|
|
|
|
private void AddObjectToMap(GameObject gameObject)
|
|
{
|
|
if (gameObject == null || gameObject.IsDead)
|
|
return;
|
|
|
|
_poolSpawnedObjects.Add(gameObject);
|
|
|
|
// order is important because the draw pool does not update the last position value
|
|
// add the object to the drawable pool
|
|
if ((gameObject.ComponentsMask & DrawComponent.Mask) == DrawComponent.Mask)
|
|
_drawPool.AddEntity(gameObject);
|
|
|
|
// add the object to the pool
|
|
_gameObjectPool.AddEntity(gameObject);
|
|
|
|
// add key listeners
|
|
if ((gameObject.ComponentsMask & KeyChangeListenerComponent.Mask) == KeyChangeListenerComponent.Mask)
|
|
{
|
|
var listener = (gameObject.Components[KeyChangeListenerComponent.Index] as KeyChangeListenerComponent).KeyChangeFunction;
|
|
_keyChangeListeners.Add(listener);
|
|
}
|
|
}
|
|
|
|
public void RemoveObject(GameObject gameObject)
|
|
{
|
|
gameObject.Map = null;
|
|
|
|
_poolSpawnedObjects.Remove(gameObject);
|
|
|
|
// remove the object from the drawable pool
|
|
if ((gameObject.ComponentsMask & DrawComponent.Mask) == DrawComponent.Mask)
|
|
_drawPool.RemoveEntity(gameObject);
|
|
|
|
// remove the object from the pool
|
|
_gameObjectPool.RemoveEntity(gameObject);
|
|
|
|
// remove key listeners
|
|
if ((gameObject.ComponentsMask & KeyChangeListenerComponent.Mask) == KeyChangeListenerComponent.Mask)
|
|
{
|
|
var listener = (gameObject.Components[KeyChangeListenerComponent.Index] as KeyChangeListenerComponent).KeyChangeFunction;
|
|
_keyChangeListeners.Remove(listener);
|
|
}
|
|
}
|
|
|
|
public void ReloadObjects()
|
|
{
|
|
LoadObjects();
|
|
}
|
|
|
|
public void TriggerKeyChange()
|
|
{
|
|
_keyChanged = true;
|
|
}
|
|
|
|
public bool SpawnObject(GameObject newObject)
|
|
{
|
|
if (newObject == null || newObject.IsDead)
|
|
return false;
|
|
|
|
SpawnObjects.Add(newObject);
|
|
return true;
|
|
}
|
|
|
|
public bool SpawnObject(string objectId, object[] objectParameter)
|
|
{
|
|
if (objectId == null || !GameObjectTemplates.ObjectTemplates.ContainsKey(objectId))
|
|
return false;
|
|
|
|
return SpawnObject(GetGameObject(Owner, objectId, objectParameter));
|
|
}
|
|
|
|
public static GameObject GetGameObject(Map owner, string objectId, object[] objectParameter)
|
|
{
|
|
if (!GameObjectTemplates.ObjectSpawner.ContainsKey(objectId))
|
|
return null;
|
|
|
|
if (objectParameter == null)
|
|
{
|
|
objectParameter = MapData.GetParameter(objectId, null);
|
|
objectParameter[1] = 0;
|
|
objectParameter[2] = 0;
|
|
}
|
|
|
|
objectParameter[0] = owner;
|
|
var constructor = GameObjectTemplates.ObjectSpawner[objectId];
|
|
|
|
return constructor(objectParameter);
|
|
}
|
|
|
|
// TODO_End: be careful to not use this often
|
|
public List<GameObject> GetObjectsOfType(Type type)
|
|
{
|
|
_objectTypeList.Clear();
|
|
|
|
for (var i = 0; i < _poolSpawnedObjects.Count; i++)
|
|
if (_poolSpawnedObjects[i].GetType() == type)
|
|
_objectTypeList.Add(_poolSpawnedObjects[i]);
|
|
|
|
return _objectTypeList;
|
|
}
|
|
|
|
public List<GameObject> GetObjects(int left, int top, int width, int height)
|
|
{
|
|
// get possible candidates
|
|
_objectTagAllList.Clear();
|
|
_gameObjectPool.GetObjectList(_objectTagAllList, left, top, width, height);
|
|
|
|
var outputList = new List<GameObject>();
|
|
foreach (var gameObject in _objectTagAllList)
|
|
{
|
|
if (gameObject.EntityPosition != null &&
|
|
left <= gameObject.EntityPosition.X + gameObject.EntitySize.X &&
|
|
gameObject.EntityPosition.X + gameObject.EntitySize.X + gameObject.EntitySize.Width <= left + width &&
|
|
top <= gameObject.EntityPosition.Y + gameObject.EntitySize.Y &&
|
|
gameObject.EntityPosition.Y + gameObject.EntitySize.Y + gameObject.EntitySize.Height <= top + height)
|
|
outputList.Add(gameObject);
|
|
}
|
|
return outputList;
|
|
}
|
|
|
|
public void GetObjectsOfType(List<GameObject> outputList, Type type, int left, int top, int width, int height)
|
|
{
|
|
// get possible candidates
|
|
_objectTagAllList.Clear();
|
|
_gameObjectPool.GetObjectList(_objectTagAllList, left, top, width, height);
|
|
|
|
foreach (var gameObject in _objectTagAllList)
|
|
{
|
|
if (gameObject.GetType() == type &&
|
|
left <= gameObject.EntityPosition.X + gameObject.EntitySize.X + gameObject.EntitySize.Width &&
|
|
gameObject.EntityPosition.X + gameObject.EntitySize.X <= left + width &&
|
|
top <= gameObject.EntityPosition.Y + gameObject.EntitySize.Y + gameObject.EntitySize.Height &&
|
|
gameObject.EntityPosition.Y + gameObject.EntitySize.Y <= top + height)
|
|
outputList.Add(gameObject);
|
|
}
|
|
}
|
|
|
|
public GameObject GetObjectOfType(int left, int top, int width, int height, Type type)
|
|
{
|
|
return _gameObjectPool.GetObjectOfType(left, top, width, height, type);
|
|
}
|
|
|
|
public void GetGameObjectsWithTag(List<GameObject> outputList, Values.GameObjectTag tag, int left, int top, int width, int height)
|
|
{
|
|
// get possible candidates
|
|
_objectTagAllList.Clear();
|
|
_gameObjectPool.GetObjectList(_objectTagAllList, left, top, width, height);
|
|
|
|
outputList.Clear();
|
|
|
|
foreach (var gameObject in _objectTagAllList)
|
|
{
|
|
if ((gameObject.Tags & tag) != 0 &&
|
|
left <= gameObject.EntityPosition.X && gameObject.EntityPosition.X <= left + width &&
|
|
top <= gameObject.EntityPosition.Y && gameObject.EntityPosition.Y <= top + height)
|
|
outputList.Add(gameObject);
|
|
}
|
|
}
|
|
|
|
public void GetComponentList(List<GameObject> gameObjectList,
|
|
int recLeft, int recTop, int recWidth, int recHeight, int componentMask)
|
|
{
|
|
_gameObjectPool.GetComponentList(gameObjectList, recLeft, recTop, recWidth, recHeight, componentMask);
|
|
}
|
|
|
|
public bool Collision(Box box, Box oldBox, Values.CollisionTypes collisionTypes, Values.CollisionTypes ignoreTypes, int dir, int level, ref Box collidingBox)
|
|
{
|
|
// get all the near objects of the rectangle
|
|
_collisionObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_collisionObjectList, (int)box.X, (int)box.Y, (int)box.Width, (int)box.Height, CollisionComponent.Mask);
|
|
|
|
foreach (var gameObject in _collisionObjectList)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var collisionObject = gameObject.Components[CollisionComponent.Index] as CollisionComponent;
|
|
if ((collisionObject.CollisionType & collisionTypes) != 0 &&
|
|
(collisionObject.CollisionType & ignoreTypes) == 0 &&
|
|
collisionObject.Collision(box, dir, level, ref collidingBox) &&
|
|
(oldBox == Box.Empty || !collisionObject.Collision(oldBox, dir, level, ref collidingBox)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool Collision(Box box, Box oldBox, Values.CollisionTypes collisionTypes, int dir, int level, ref Box collidingBox)
|
|
{
|
|
return Collision(box, oldBox, collisionTypes, Values.CollisionTypes.None, dir, level, ref collidingBox);
|
|
}
|
|
|
|
public float GetDepth(Box box, Values.CollisionTypes collisionType, float maxDepth)
|
|
{
|
|
float outDepth = 0;
|
|
|
|
// depth of holes
|
|
var center = new Vector2((box.X + box.Width / 2) / Values.TileSize, (box.Front - 1) / Values.TileSize);
|
|
if (Owner.HoleMap != null && Owner.HoleMap.ArrayTileMap != null &&
|
|
0 <= center.X && center.X < Owner.HoleMap.ArrayTileMap.GetLength(0) &&
|
|
0 <= center.Y && center.Y < Owner.HoleMap.ArrayTileMap.GetLength(1))
|
|
{
|
|
var distance = new Vector2((int)center.X + 0.5f, (int)center.Y + 0.5f) - center;
|
|
outDepth = Owner.HoleMap.ArrayTileMap[(int)center.X, (int)center.Y, 0] < 0 ? 0 : -2 * Math.Clamp(1 - distance.Length() * 1.5f, 0, 1);
|
|
}
|
|
|
|
_depthObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_depthObjectList, (int)box.X, (int)box.Y, (int)box.Width, (int)box.Height, CollisionComponent.Mask);
|
|
|
|
var clampDepth = outDepth;
|
|
|
|
foreach (var gameObject in _depthObjectList)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var newDepth = outDepth;
|
|
var lowestDepth = GetObjectDepth(gameObject, box, collisionType, ref newDepth);
|
|
|
|
// floating point inaccuracy can lead to combined intersection areas greater than 100%
|
|
// to fix this problem the maxDepth value will need to be clamped to not exceed the max depth
|
|
if (lowestDepth < clampDepth)
|
|
clampDepth = lowestDepth;
|
|
|
|
if (newDepth <= maxDepth)
|
|
outDepth = newDepth;
|
|
}
|
|
|
|
return MathF.Max(outDepth, clampDepth);
|
|
}
|
|
|
|
private float GetObjectDepth(GameObject gameObject, Box box, Values.CollisionTypes collisionType, ref float maxDepth)
|
|
{
|
|
var collisionObject = gameObject.Components[CollisionComponent.Index] as CollisionComponent;
|
|
var collidingBox = Box.Empty;
|
|
|
|
// add the object to the list if it is in the mask and is colliding with the provided rectangle
|
|
if ((collisionObject.CollisionType & collisionType) != 0 &&
|
|
collisionObject.Collision(box, 0, 0, ref collidingBox))
|
|
{
|
|
var bottom = collidingBox.Z + collidingBox.Depth;
|
|
if (bottom < 0)
|
|
{
|
|
// combine the depth values lower than 0 to smoothly transition to lower floors
|
|
var bodyRec = box.Rectangle();
|
|
var intersection = collidingBox.Rectangle().GetIntersection(bodyRec);
|
|
maxDepth += bottom * ((intersection.Width * intersection.Height) / (bodyRec.Width * bodyRec.Height));
|
|
return bottom;
|
|
}
|
|
if (bottom > maxDepth)
|
|
{
|
|
maxDepth = bottom;
|
|
return maxDepth;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public GameObject GetCarryableObjects(RectangleF rectangle)
|
|
{
|
|
_carriableObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_carriableObjectList, (int)rectangle.X, (int)rectangle.Y, (int)rectangle.Width, (int)rectangle.Height, CarriableComponent.Mask);
|
|
|
|
foreach (var gameObject in _carriableObjectList)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var component = gameObject.Components[CarriableComponent.Index] as CarriableComponent;
|
|
|
|
if (component.IsActive && component.Rectangle.Rectangle.Intersects(rectangle))
|
|
return gameObject;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public Values.HitCollision Hit(GameObject originObject, Vector2 forceOrigin, Box hitBox, HitType type, int damage, bool doubleDamage, bool multidamage = true)
|
|
{
|
|
return Hit(originObject, forceOrigin, hitBox, type, damage, doubleDamage, out var direction, multidamage);
|
|
}
|
|
|
|
public Values.HitCollision Hit(GameObject originObject, Vector2 forceOrigin, Box hitBox, HitType type, int damage, bool doubleDamage, out Vector2 direction, bool multidamage = true)
|
|
{
|
|
// get all the near objects of the rectangle
|
|
_hittableObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_hittableObjectList, (int)hitBox.X, (int)hitBox.Y, (int)hitBox.Width, (int)hitBox.Height, HittableComponent.Mask);
|
|
|
|
var hitCollision = Values.HitCollision.None;
|
|
direction = Vector2.Zero;
|
|
|
|
foreach (var gameObject in _hittableObjectList)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var hittableComponent = gameObject.Components[HittableComponent.Index] as HittableComponent;
|
|
|
|
if (!hittableComponent.IsActive || !hittableComponent.HittableBox.Box.Intersects(hitBox))
|
|
continue;
|
|
|
|
// direction goes from the hitter towards the center of hittable box
|
|
direction = hittableComponent.HittableBox.Box.Center - forceOrigin;
|
|
if (direction != Vector2.Zero)
|
|
direction.Normalize();
|
|
|
|
hitCollision |= hittableComponent.Hit(originObject, direction, type, damage, doubleDamage);
|
|
|
|
if (!multidamage && hitCollision != Values.HitCollision.None && hitCollision != Values.HitCollision.NoneBlocking)
|
|
return hitCollision;
|
|
}
|
|
|
|
return hitCollision;
|
|
}
|
|
|
|
public PushableComponent PushObject(Box box, Vector2 direction, PushableComponent.PushType type)
|
|
{
|
|
// get all the near objects of the rectangle
|
|
_pushableObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_pushableObjectList, (int)box.X, (int)box.Y, (int)box.Width, (int)box.Height, PushableComponent.Mask);
|
|
|
|
PushableComponent outPushComponent = null;
|
|
|
|
foreach (var gameObject in _pushableObjectList)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var pushableComponent = gameObject.Components[PushableComponent.Index] as PushableComponent;
|
|
|
|
// check for collision and if cooldown time is met
|
|
if (pushableComponent.IsActive &&
|
|
pushableComponent.LastPushTime + pushableComponent.CooldownTime < Game1.TotalGameTime &&
|
|
box.Intersects(pushableComponent.PushableBox.Box))
|
|
{
|
|
// object got inertia?
|
|
if (pushableComponent.InertiaTime > 0 && type == PushableComponent.PushType.Continues)
|
|
{
|
|
// the object was pushed the last frame?
|
|
if (pushableComponent.LastWaitTime >= Game1.TotalGameTimeLast)
|
|
{
|
|
pushableComponent.InertiaCounter -= Game1.DeltaTime;
|
|
if (pushableComponent.InertiaCounter <= 0 && pushableComponent.Push(direction, type))
|
|
{
|
|
pushableComponent.InertiaCounter = pushableComponent.InertiaTime;
|
|
pushableComponent.LastPushTime = Game1.TotalGameTime;
|
|
outPushComponent = pushableComponent;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// reset inertia counter if pushing has just begone
|
|
pushableComponent.InertiaCounter = pushableComponent.InertiaTime;
|
|
}
|
|
|
|
pushableComponent.LastWaitTime = Game1.TotalGameTime;
|
|
}
|
|
else if (pushableComponent.Push(direction, type))
|
|
{
|
|
pushableComponent.LastPushTime = Game1.TotalGameTime;
|
|
outPushComponent = pushableComponent;
|
|
}
|
|
}
|
|
}
|
|
|
|
return outPushComponent;
|
|
}
|
|
|
|
public bool InteractWithObject(Box box)
|
|
{
|
|
// get the interactable objects at the given position
|
|
_interactableObjectList.Clear();
|
|
_gameObjectPool.GetComponentList(_interactableObjectList, (int)box.X, (int)box.Y, (int)box.Width, (int)box.Height, InteractComponent.Mask);
|
|
|
|
// go through all the interactable objects and check for collision before interacting with them
|
|
foreach (var gameObject in _interactableObjectList)
|
|
{
|
|
if (!gameObject.IsActive)
|
|
continue;
|
|
|
|
var component = gameObject.Components[InteractComponent.Index] as InteractComponent;
|
|
if (component.IsActive && component.BoxInteractabel.Box.Intersects(box) && component.InteractFunction())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
} |