using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;
namespace Robust.Client.Placement
{
public abstract class PlacementMode
{
public readonly PlacementManager pManager;
///
/// Holds the current tile we are hovering our mouse over
///
public TileRef CurrentTile { get; set; }
///
/// Local coordinates of our cursor on the map
///
public EntityCoordinates MouseCoords { get; set; }
///
/// Texture resources to draw to represent the entity we are trying to spawn
///
public List? TexturesToDraw { get; set; }
///
/// Color set to the ghost entity when it has a valid spawn position
///
public Color ValidPlaceColor { get; set; } = new(20, 180, 20); //Default valid color is green
///
/// Color set to the ghost entity when it has an invalid spawn position
///
public Color InvalidPlaceColor { get; set; } = new(180, 20, 20); //Default invalid placement is red
///
/// Used for line and grid placement to determine how spaced apart the entities should be
///
protected float GridDistancing = 1f;
///
/// Whether this mode requires us to verify the player is spawning within a certain range of themselves
///
public virtual bool RangeRequired => false;
///
/// Whether this mode can use the line placement mode
///
public virtual bool HasLineMode => false;
///
/// Whether this mode can use the grid placement mode
///
public virtual bool HasGridMode => false;
protected PlacementMode(PlacementManager pMan)
{
pManager = pMan;
}
public virtual string ModeName => GetType().Name;
///
/// Aligns the location of placement based on cursor location
///
///
/// Returns whether the current position is a valid placement position
public abstract void AlignPlacementMode(ScreenCoordinates mouseScreen);
///
/// Verifies the location of placement is a valid position to place at
///
///
///
public abstract bool IsValidPosition(EntityCoordinates position);
public virtual void Render(in OverlayDrawArgs args)
{
var uid = pManager.CurrentPlacementOverlayEntity;
if (!pManager.EntityManager.TryGetComponent(uid, out SpriteComponent? sprite) || !sprite.Visible)
{
// TODO draw something for placement of invisible & sprite-less entities.
return;
}
IEnumerable locationcollection;
switch (pManager.PlacementType)
{
case PlacementManager.PlacementTypes.None:
locationcollection = SingleCoordinate();
break;
case PlacementManager.PlacementTypes.Line:
locationcollection = LineCoordinates();
break;
case PlacementManager.PlacementTypes.Grid:
locationcollection = GridCoordinates();
break;
default:
locationcollection = SingleCoordinate();
break;
}
var dirAng = pManager.Direction.ToAngle();
var spriteSys = pManager.EntityManager.System();
var transformSys = pManager.EntityManager.System();
foreach (var coordinate in locationcollection)
{
if (!coordinate.IsValid(pManager.EntityManager))
return; // Just some paranoia just in case
var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys);
var worldRot = pManager.EntityManager.GetComponent(coordinate.EntityId).WorldRotation + dirAng;
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
var rot = args.Viewport.Eye?.Rotation ?? default;
spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos);
}
}
public IEnumerable SingleCoordinate()
{
yield return MouseCoords;
}
public IEnumerable LineCoordinates()
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
var transformSys = pManager.EntityManager.System();
if (mousePos.MapId == MapId.Nullspace)
yield break;
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
float iterations;
Vector2 distance;
if (Math.Abs(x) > Math.Abs(y))
{
var xSign = Math.Sign(x);
iterations = MathF.Ceiling(Math.Abs((x + GridDistancing / 2f * xSign) / GridDistancing));
distance = new Vector2(x > 0 ? 1 : -1, 0);
}
else
{
var ySign = Math.Sign(y);
iterations = MathF.Ceiling(Math.Abs((y + GridDistancing / 2f * ySign) / GridDistancing));
distance = new Vector2(0, y > 0 ? 1 : -1);
}
for (var i = 0; i < iterations; i++)
{
yield return new EntityCoordinates(pManager.StartPoint.EntityId, pManager.StartPoint.Position + distance * i);
}
}
// This name is a nice reminder of our origins. Never forget.
public IEnumerable GridCoordinates()
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
var transformSys = pManager.EntityManager.System();
if (mousePos.MapId == MapId.Nullspace)
yield break;
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
var xSign = Math.Sign(placementdiff.X);
var ySign = Math.Sign(placementdiff.Y);
var iterationsX = Math.Ceiling(Math.Abs((placementdiff.X + GridDistancing / 2f * xSign) / GridDistancing));
var iterationsY = Math.Ceiling(Math.Abs((placementdiff.Y + GridDistancing / 2f * ySign) / GridDistancing));
for (var x = 0; x < iterationsX; x++)
{
for (var y = 0; y < iterationsY; y++)
{
yield return new EntityCoordinates(pManager.StartPoint.EntityId, pManager.StartPoint.Position + new Vector2(x * xSign, y * ySign) * GridDistancing);
}
}
}
///
/// Returns the tile ref for a grid, or a map.
///
public TileRef GetTileRef(EntityCoordinates coordinates)
{
var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager);
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent(gridUid).GetTileRef(MouseCoords)
: new TileRef(gridUidOpt ?? EntityUid.Invalid,
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager, pManager.EntityManager.System()), Tile.Empty);
}
public TextureResource GetSprite(string key)
{
return pManager.ResourceCache.GetResource(new ResPath("/Textures/") / key);
}
public bool TryGetSprite(string key, [NotNullWhen(true)] out TextureResource? sprite)
{
return pManager.ResourceCache.TryGetResource(new ResPath(@"/Textures/") / key, out sprite);
}
///
/// Checks if the player is spawning within a certain range of his character if range is required on this mode
///
///
public bool RangeCheck(EntityCoordinates coordinates)
{
if (!RangeRequired)
return true;
var controlled = pManager.PlayerManager.LocalEntity ?? EntityUid.Invalid;
if (controlled == EntityUid.Invalid)
{
return false;
}
var range = pManager.CurrentPermission!.Range;
var transformSys = pManager.EntityManager.System();
if (range > 0 && !pManager.EntityManager.GetComponent(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range))
return false;
return true;
}
public bool IsColliding(EntityCoordinates coordinates)
{
var bounds = pManager.ColliderAABB;
var transformSys = pManager.EntityManager.System();
var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys);
var (x, y) = mapCoords.Position;
var collisionBox = Box2.FromDimensions(
bounds.Left + x,
bounds.Bottom + y,
bounds.Width,
bounds.Height);
return EntitySystem.Get().TryCollideRect(collisionBox, mapCoords.MapId);
}
protected Vector2 ScreenToWorld(Vector2 point)
{
return pManager.EyeManager.ScreenToMap(point).Position;
}
protected Vector2 WorldToScreen(Vector2 point)
{
return pManager.EyeManager.WorldToScreen(point);
}
protected EntityCoordinates ScreenToCursorGrid(ScreenCoordinates coords)
{
var mapCoords = pManager.EyeManager.PixelToMap(coords.Position);
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid))
{
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
}
var transformSys = pManager.EntityManager.System();
return EntityCoordinates.FromMap(gridUid, mapCoords, transformSys, pManager.EntityManager);
}
}
}