mirror of
https://github.com/AlexMacocian/MonoGame.Extended.git
synced 2026-07-24 03:56:31 +00:00
working on an entity component system
This commit is contained in:
@@ -36,9 +36,16 @@
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Entities\Components\EntityComponent.cs" />
|
||||
<Compile Include="Entities\Components\SpriteAnimatorComponent.cs" />
|
||||
<Compile Include="Entities\Components\SpriteComponent.cs" />
|
||||
<Compile Include="Entities\Entity.cs" />
|
||||
<Compile Include="Entities\EntityGameComponent.cs" />
|
||||
<Compile Include="Entities\EntityComponentSystem.cs" />
|
||||
<Compile Include="Entities\Systems\SpriteAnimatorComponentSystem.cs" />
|
||||
<Compile Include="Entities\Systems\UpdatableComponentSystem.cs" />
|
||||
<Compile Include="Entities\Systems\ComponentSystem.cs" />
|
||||
<Compile Include="Entities\Systems\DrawableComponentSystem.cs" />
|
||||
<Compile Include="Entities\Systems\SpriteBatchComponentSystem.cs" />
|
||||
<Compile Include="Game1.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
@@ -74,6 +81,7 @@
|
||||
<Name>MonoGame.Extended</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\MonoGame\v3.0\MonoGame.Content.Builder.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Demo.Platformer.Entities.Components
|
||||
{
|
||||
public abstract class EntityComponent
|
||||
{
|
||||
protected EntityComponent()
|
||||
{
|
||||
}
|
||||
|
||||
public Entity Entity { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using MonoGame.Extended.Animations.SpriteSheets;
|
||||
|
||||
namespace Demo.Platformer.Entities.Components
|
||||
{
|
||||
public class SpriteAnimatorComponent : EntityComponent
|
||||
{
|
||||
public SpriteAnimatorComponent(SpriteSheetAnimationFactory animationFactory, string playAnimation = null)
|
||||
{
|
||||
Animator = new SpriteSheetAnimator(animationFactory);
|
||||
|
||||
if (playAnimation != null)
|
||||
Animator.Play(playAnimation);
|
||||
}
|
||||
|
||||
public SpriteSheetAnimator Animator { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,10 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MonoGame.Extended.SceneGraphs;
|
||||
using MonoGame.Extended.TextureAtlases;
|
||||
|
||||
namespace Demo.Platformer.Entities.Components
|
||||
{
|
||||
public abstract class EntityComponent
|
||||
{
|
||||
protected EntityComponent()
|
||||
{
|
||||
}
|
||||
|
||||
public Entity Entity { get; internal set; }
|
||||
|
||||
public abstract void Update(float deltaTime);
|
||||
public abstract void Draw(SpriteBatch spriteBatch);
|
||||
}
|
||||
|
||||
public class SpriteComponent : EntityComponent, ISpriteBatchDrawable
|
||||
public class SpriteComponent : EntityComponent
|
||||
{
|
||||
public SpriteComponent(TextureRegion2D textureRegion)
|
||||
{
|
||||
@@ -26,24 +13,19 @@ namespace Demo.Platformer.Entities.Components
|
||||
Color = Color.White;
|
||||
Origin = new Vector2(textureRegion.Size.Width, textureRegion.Size.Height)*0.5f;
|
||||
Effect = SpriteEffects.None;
|
||||
Alpha = 1.0f;
|
||||
Depth = 0.0f;
|
||||
}
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
public TextureRegion2D TextureRegion { get; set; }
|
||||
public Color Color { get; }
|
||||
public Vector2 Origin { get; }
|
||||
public SpriteEffects Effect { get; }
|
||||
public Color Color { get; set; }
|
||||
public Vector2 Origin { get; set; }
|
||||
public SpriteEffects Effect { get; set; }
|
||||
public Vector2 Position => Entity.Position;
|
||||
public float Rotation => Entity.Rotation;
|
||||
public Vector2 Scale => Entity.Scale;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
spriteBatch.Draw(TextureRegion, Position, Color, Rotation, Origin, Scale, Effect, 0);
|
||||
}
|
||||
public float Alpha { get; set; }
|
||||
public float Depth { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Demo.Platformer.Entities.Components;
|
||||
using System;
|
||||
using Demo.Platformer.Entities.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MonoGame.Extended;
|
||||
|
||||
@@ -6,24 +7,51 @@ namespace Demo.Platformer.Entities
|
||||
{
|
||||
public class Entity : IMovable, IRotatable, IScalable
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly EntityComponentSystem _entityComponentSystem;
|
||||
|
||||
public Entity(IEntityManager entityManager, long id)
|
||||
public Entity(EntityComponentSystem entityComponentSystem, long id, string name)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_entityComponentSystem = entityComponentSystem;
|
||||
|
||||
Id = id;
|
||||
Name = name;
|
||||
Position = Vector2.Zero;
|
||||
Rotation = 0;
|
||||
Scale = Vector2.One;
|
||||
}
|
||||
|
||||
public long Id { get; }
|
||||
public string Name { get; }
|
||||
public Vector2 Position { get; set; }
|
||||
public float Rotation { get; set; }
|
||||
public Vector2 Scale { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void AttachComponent(EntityComponent component)
|
||||
{
|
||||
if (component.Entity != null)
|
||||
throw new InvalidOperationException("Component already attached to another entity");
|
||||
|
||||
component.Entity = this;
|
||||
_entityManager.AttachComponent(component);
|
||||
_entityComponentSystem.AttachComponent(component);
|
||||
}
|
||||
|
||||
public void DetachComponent(EntityComponent component)
|
||||
{
|
||||
if (component.Entity != this)
|
||||
throw new InvalidOperationException("Component not attached to entity");
|
||||
|
||||
component.Entity = null;
|
||||
_entityComponentSystem.DetachComponent(component);
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
_entityComponentSystem.DestroyEntity(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Demo.Platformer.Entities.Components;
|
||||
using Demo.Platformer.Entities.Systems;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Demo.Platformer.Entities
|
||||
{
|
||||
public class EntityComponentSystem
|
||||
{
|
||||
public EntityComponentSystem()
|
||||
{
|
||||
_entities = new List<Entity>();
|
||||
_components = new List<EntityComponent>();
|
||||
_systems = new List<ComponentSystem>();
|
||||
_nextEntityId = 1;
|
||||
}
|
||||
|
||||
private readonly List<ComponentSystem> _systems;
|
||||
private readonly List<Entity> _entities;
|
||||
private readonly List<EntityComponent> _components;
|
||||
private long _nextEntityId;
|
||||
|
||||
public void RegisterSystem(ComponentSystem system)
|
||||
{
|
||||
if (system.EntityComponentSystem != null)
|
||||
throw new InvalidOperationException("Component system already registered");
|
||||
|
||||
system.EntityComponentSystem = this;
|
||||
_systems.Add(system);
|
||||
}
|
||||
|
||||
public Entity CreateEntity(string name)
|
||||
{
|
||||
var entity = new Entity(this, _nextEntityId, name);
|
||||
_entities.Add(entity);
|
||||
_nextEntityId++;
|
||||
return entity;
|
||||
}
|
||||
|
||||
public Entity FindEntity(string name)
|
||||
{
|
||||
return _entities.FirstOrDefault(e => e.Name == name);
|
||||
}
|
||||
|
||||
public void DestroyEntity(Entity entity)
|
||||
{
|
||||
_entities.Remove(entity);
|
||||
}
|
||||
|
||||
public void AttachComponent(EntityComponent component)
|
||||
{
|
||||
_components.Add(component);
|
||||
}
|
||||
|
||||
public void DetachComponent(EntityComponent component)
|
||||
{
|
||||
_components.Remove(component);
|
||||
}
|
||||
|
||||
internal IEnumerable<T> GetComponents<T>()
|
||||
{
|
||||
return _components.OfType<T>();
|
||||
}
|
||||
|
||||
public void Update(GameTime gameTime)
|
||||
{
|
||||
foreach (var componentSystem in _systems.OfType<UpdatableComponentSystem>())
|
||||
componentSystem.Update(gameTime);
|
||||
}
|
||||
|
||||
public void Draw(GameTime gameTime)
|
||||
{
|
||||
foreach (var componentSystem in _systems.OfType<DrawableComponentSystem>())
|
||||
componentSystem.Draw(gameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Demo.Platformer.Entities.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MonoGame.Extended;
|
||||
|
||||
namespace Demo.Platformer.Entities
|
||||
{
|
||||
public interface IEntityManager
|
||||
{
|
||||
void AttachComponent(EntityComponent component);
|
||||
void DetachComponent(EntityComponent component);
|
||||
}
|
||||
|
||||
public class EntityGameComponent : DrawableGameComponent, IEntityManager
|
||||
{
|
||||
public EntityGameComponent(Game game)
|
||||
: base(game)
|
||||
{
|
||||
_entities = new List<Entity>();
|
||||
_components = new List<EntityComponent>();
|
||||
_nextEntityId = 1;
|
||||
}
|
||||
|
||||
private SpriteBatch _spriteBatch;
|
||||
private readonly List<Entity> _entities;
|
||||
private readonly List<EntityComponent> _components;
|
||||
private long _nextEntityId;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_spriteBatch = new SpriteBatch(GraphicsDevice);
|
||||
}
|
||||
|
||||
public Entity CreateEntity()
|
||||
{
|
||||
var entity = new Entity(this, _nextEntityId);
|
||||
_entities.Add(entity);
|
||||
_nextEntityId++;
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void DestroyEntity(Entity entity)
|
||||
{
|
||||
_entities.Remove(entity);
|
||||
}
|
||||
|
||||
public void AttachComponent(EntityComponent component)
|
||||
{
|
||||
_components.Add(component);
|
||||
}
|
||||
|
||||
public void DetachComponent(EntityComponent component)
|
||||
{
|
||||
_components.Remove(component);
|
||||
}
|
||||
|
||||
public override void Update(GameTime gameTime)
|
||||
{
|
||||
base.Update(gameTime);
|
||||
|
||||
var deltaTime = gameTime.GetElapsedSeconds();
|
||||
|
||||
foreach (var entityComponent in _components)
|
||||
entityComponent.Update(deltaTime);
|
||||
}
|
||||
|
||||
public override void Draw(GameTime gameTime)
|
||||
{
|
||||
base.Draw(gameTime);
|
||||
|
||||
_spriteBatch.Begin();
|
||||
|
||||
foreach (var entityComponent in _components)
|
||||
entityComponent.Draw(_spriteBatch);
|
||||
|
||||
_spriteBatch.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Demo.Platformer.Entities.Systems
|
||||
{
|
||||
public abstract class ComponentSystem
|
||||
{
|
||||
protected ComponentSystem()
|
||||
{
|
||||
}
|
||||
|
||||
internal EntityComponentSystem EntityComponentSystem { get; set; }
|
||||
|
||||
protected IEnumerable<T> GetComponents<T>()
|
||||
{
|
||||
return EntityComponentSystem.GetComponents<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Demo.Platformer.Entities.Systems
|
||||
{
|
||||
public abstract class DrawableComponentSystem : ComponentSystem
|
||||
{
|
||||
public abstract void Draw(GameTime gameTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Demo.Platformer.Entities.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Demo.Platformer.Entities.Systems
|
||||
{
|
||||
public class SpriteAnimatorComponentSystem : UpdatableComponentSystem
|
||||
{
|
||||
public SpriteAnimatorComponentSystem()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update(GameTime gameTime)
|
||||
{
|
||||
var components = GetComponents<SpriteAnimatorComponent>();
|
||||
|
||||
foreach (var component in components)
|
||||
component.Animator.Update(gameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Demo.Platformer.Entities.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Demo.Platformer.Entities.Systems
|
||||
{
|
||||
public class SpriteBatchComponentSystem : DrawableComponentSystem
|
||||
{
|
||||
public SpriteBatchComponentSystem(GraphicsDevice graphicsDevice)
|
||||
{
|
||||
_spriteBatch = new SpriteBatch(graphicsDevice);
|
||||
}
|
||||
|
||||
private readonly SpriteBatch _spriteBatch;
|
||||
|
||||
public override void Draw(GameTime gameTime)
|
||||
{
|
||||
var spriteComponents = GetComponents<SpriteComponent>();
|
||||
|
||||
_spriteBatch.Begin();
|
||||
|
||||
foreach (var sprite in spriteComponents)
|
||||
{
|
||||
if (sprite.IsVisible)
|
||||
{
|
||||
var texture = sprite.TextureRegion.Texture;
|
||||
var sourceRectangle = sprite.TextureRegion.Bounds;
|
||||
|
||||
_spriteBatch.Draw(texture, sprite.Position, sourceRectangle, sprite.Color * sprite.Alpha, sprite.Rotation, sprite.Origin,
|
||||
sprite.Scale, sprite.Effect, sprite.Depth);
|
||||
}
|
||||
}
|
||||
_spriteBatch.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Demo.Platformer.Entities.Systems
|
||||
{
|
||||
public abstract class UpdatableComponentSystem : ComponentSystem
|
||||
{
|
||||
public abstract void Update(GameTime gameTime);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using Demo.Platformer.Entities;
|
||||
using Demo.Platformer.Entities.Components;
|
||||
using Demo.Platformer.Entities.Systems;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using MonoGame.Extended;
|
||||
using MonoGame.Extended.Animations.SpriteSheets;
|
||||
using MonoGame.Extended.Maps.Tiled;
|
||||
using MonoGame.Extended.Sprites;
|
||||
using MonoGame.Extended.TextureAtlases;
|
||||
using MonoGame.Extended.ViewportAdapters;
|
||||
|
||||
@@ -16,16 +16,9 @@ namespace Demo.Platformer
|
||||
{
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly GraphicsDeviceManager _graphicsDeviceManager;
|
||||
private SpriteBatch _spriteBatch;
|
||||
private Camera2D _camera;
|
||||
private TiledMap _tiledMap;
|
||||
private Sprite _sprite;
|
||||
private SpriteSheetAnimator _animator;
|
||||
private EntityGameComponent _entityManager;
|
||||
|
||||
//private Sprite _sprite0;
|
||||
//private Sprite _sprite1;
|
||||
//private Texture2D _logo;
|
||||
private EntityComponentSystem _entityComponentSystem;
|
||||
|
||||
public Game1()
|
||||
{
|
||||
@@ -37,36 +30,33 @@ namespace Demo.Platformer
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Components.Add(_entityManager = new EntityGameComponent(this));
|
||||
_entityComponentSystem = new EntityComponentSystem();
|
||||
_entityComponentSystem.RegisterSystem(new SpriteBatchComponentSystem(GraphicsDevice));
|
||||
_entityComponentSystem.RegisterSystem(new SpriteAnimatorComponentSystem());
|
||||
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
protected override void LoadContent()
|
||||
{
|
||||
_spriteBatch = new SpriteBatch(GraphicsDevice);
|
||||
|
||||
//_logo = Content.Load<Texture2D>("logo-square-128");
|
||||
|
||||
var viewportAdapter = new BoxingViewportAdapter(Window, GraphicsDevice, 800, 480);
|
||||
_camera = new Camera2D(viewportAdapter);
|
||||
|
||||
// first load our resources
|
||||
var texture = Content.Load<Texture2D>("tiny-characters");
|
||||
var atlas = TextureAtlas.Create(texture, 32, 32, 15);
|
||||
var animationFactory = new SpriteSheetAnimationFactory(atlas);
|
||||
animationFactory.Add("idle", new SpriteSheetAnimationData(new[] { 0, 1, 2, 3 }, isReversed: true));
|
||||
|
||||
var entity = _entityManager.CreateEntity();
|
||||
// let's build our dude entity
|
||||
var entity = _entityComponentSystem.CreateEntity("dude");
|
||||
entity.AttachComponent(new SpriteComponent(atlas[0]));
|
||||
entity.AttachComponent(new SpriteAnimatorComponent(animationFactory, "idle"));
|
||||
entity.Position = new Vector2(300, 300);
|
||||
|
||||
animationFactory.Add("idle", new SpriteSheetAnimationData(new[] {0, 1, 2, 3}, isReversed: true));
|
||||
_animator = new SpriteSheetAnimator(animationFactory);
|
||||
_animator.Play("idle");
|
||||
_sprite = _animator.CreateSprite(position: new Vector2(116, 273));
|
||||
//_sprite1 = _animator.CreateSprite(position: new Vector2(132, 273));
|
||||
_tiledMap = Content.Load<TiledMap>("level-1");
|
||||
|
||||
var viewport = GraphicsDevice.Viewport;
|
||||
//var viewport = GraphicsDevice.Viewport;
|
||||
//_alphaTestEffect = new AlphaTestEffect(GraphicsDevice)
|
||||
//{
|
||||
// Projection =
|
||||
@@ -83,19 +73,21 @@ namespace Demo.Platformer
|
||||
|
||||
protected override void Update(GameTime gameTime)
|
||||
{
|
||||
var deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
|
||||
//var deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
|
||||
var keyboardState = Keyboard.GetState();
|
||||
|
||||
if (keyboardState.IsKeyDown(Keys.Escape))
|
||||
Exit();
|
||||
|
||||
if (keyboardState.IsKeyDown(Keys.D) || keyboardState.IsKeyDown(Keys.Right))
|
||||
_sprite.Position += new Vector2(150, 0) * deltaTime;
|
||||
//if (keyboardState.IsKeyDown(Keys.D) || keyboardState.IsKeyDown(Keys.Right))
|
||||
// _sprite.Position += new Vector2(150, 0) * deltaTime;
|
||||
|
||||
if (keyboardState.IsKeyDown(Keys.A) || keyboardState.IsKeyDown(Keys.Left))
|
||||
_sprite.Position -= new Vector2(150, 0) * deltaTime;
|
||||
//if (keyboardState.IsKeyDown(Keys.A) || keyboardState.IsKeyDown(Keys.Left))
|
||||
// _sprite.Position -= new Vector2(150, 0) * deltaTime;
|
||||
|
||||
_animator.Update(deltaTime);
|
||||
//_animator.Update(deltaTime);
|
||||
|
||||
_entityComponentSystem.Update(gameTime);
|
||||
|
||||
base.Update(gameTime);
|
||||
}
|
||||
@@ -107,7 +99,7 @@ namespace Demo.Platformer
|
||||
var viewMatrix = _camera.GetViewMatrix();
|
||||
|
||||
GraphicsDevice.Clear(Color.CornflowerBlue);
|
||||
|
||||
|
||||
//_spriteBatch.Begin(
|
||||
// sortMode: SpriteSortMode.FrontToBack,
|
||||
// samplerState: SamplerState.PointClamp,
|
||||
@@ -123,9 +115,11 @@ namespace Demo.Platformer
|
||||
|
||||
_tiledMap.Draw(viewMatrix);
|
||||
|
||||
_spriteBatch.Begin(samplerState: SamplerState.PointClamp, transformMatrix: viewMatrix);
|
||||
_spriteBatch.Draw(_sprite);
|
||||
_spriteBatch.End();
|
||||
//_spriteBatch.Begin(samplerState: SamplerState.PointClamp, transformMatrix: viewMatrix);
|
||||
//_spriteBatch.Draw(_sprite);
|
||||
//_spriteBatch.End();
|
||||
|
||||
_entityComponentSystem.Draw(gameTime);
|
||||
|
||||
base.Draw(gameTime);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MonoGame.Extended
|
||||
{
|
||||
public interface IDraw
|
||||
{
|
||||
void Draw(GameTime gameTime);
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,6 @@
|
||||
<Compile Include="Content\ContentReaderExtensions.cs" />
|
||||
<Compile Include="EventHandlerExtensions.cs" />
|
||||
<Compile Include="GameTimeExtensions.cs" />
|
||||
<Compile Include="IDraw.cs" />
|
||||
<Compile Include="IMovable.cs" />
|
||||
<Compile Include="InputListeners\GamePadEventArgs.cs" />
|
||||
<Compile Include="InputListeners\GamePadListener.cs" />
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MonoGame.Extended.Screens
|
||||
{
|
||||
public abstract class Screen : IDraw, IDisposable
|
||||
public abstract class Screen : IDisposable
|
||||
{
|
||||
protected Screen()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user