fish-station/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs
Vigers Ray d6c0a011b7 Merge remote-tracking branch 'space-wizards/master'
# Conflicts:
#	.github/workflows/publish.yml
#	Content.Client/Clothing/ClientClothingSystem.cs
#	Content.Client/Clothing/Systems/ChameleonClothingSystem.cs
#	Content.Client/Fax/UI/FaxWindow.xaml
#	Content.Client/Ghost/GhostSystem.cs
#	Content.Client/Holiday/HolidaySystem.cs
#	Content.Client/Humanoid/HumanoidAppearanceSystem.cs
#	Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
#	Content.Client/Lobby/UI/LobbyGui.xaml
#	Content.Client/Lobby/UI/LobbyGui.xaml.cs
#	Content.Client/Mech/MechSystem.cs
#	Content.Client/Stealth/StealthSystem.cs
#	Content.Client/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs
#	Content.Server/Bed/BedSystem.cs
#	Content.Server/Body/Systems/BodySystem.cs
#	Content.Server/Body/Systems/RespiratorSystem.cs
#	Content.Server/Cargo/Systems/CargoSystem.Orders.cs
#	Content.Server/Communications/CommunicationsConsoleComponent.cs
#	Content.Server/EntityEffects/Effects/AreaReactionEffect.cs
#	Content.Server/EntityEffects/Effects/MakeSentient.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs
#	Content.Server/EntityEffects/Effects/PlantMutateGases.cs
#	Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
#	Content.Server/GameTicking/GameTicker.Spawning.cs
#	Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Server/Nutrition/EntitySystems/FoodSystem.cs
#	Content.Server/Power/EntitySystems/PowerNetSystem.cs
#	Content.Server/Preferences/Managers/ServerPreferencesManager.cs
#	Content.Server/RoundEnd/RoundEndSystem.cs
#	Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs
#	Content.Server/Station/Systems/StationSpawningSystem.cs
#	Content.Server/VoiceMask/VoiceMaskSystem.cs
#	Content.Server/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Shared/Bed/Components/HealOnBuckleComponent.cs
#	Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
#	Content.Shared/EntityEffects/Effects/ExplosionReactionEffect.cs
#	Content.Shared/Inventory/InventorySystem.Relay.cs
#	Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
#	Content.Shared/Preferences/Loadouts/RoleLoadout.cs
#	Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
#	Content.Shared/Wieldable/SharedWieldableSystem.cs
#	Resources/Audio/Machines/attributions.yml
#	Resources/Locale/en-US/_strings/atmos/plaque-component.ftl
#	Resources/Locale/en-US/_strings/chat/chat-channel.ftl
#	Resources/Locale/en-US/_strings/components/screen-component.ftl
#	Resources/Locale/en-US/_strings/inventory/components/slot-block-component.ftl
#	Resources/Locale/en-US/_strings/mass-media/news-discord.ftl
#	Resources/Locale/en-US/_strings/objectives/commands/stripall.ftl
#	Resources/Locale/en-US/_strings/salvage/job-board.ftl
#	Resources/Locale/en-US/_strings/ui/spray-painter-window.ftl
#	Resources/Locale/en-US/_strings/ui/turret-controls.ftl
#	Resources/Maps/_Sunrise/Station/box.yml
#	Resources/Prototypes/Access/misc.yml
#	Resources/Prototypes/Catalog/Fills/Boxes/emergency.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/service.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/suit_storage.yml
#	Resources/Prototypes/Entities/Clothing/Head/helmets.yml
#	Resources/Prototypes/Entities/Clothing/Masks/masks.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
#	Resources/Prototypes/Entities/Markers/Spawners/mobs.yml
#	Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Player/dragon.yml
#	Resources/Prototypes/Entities/Mobs/Player/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Species/arachnid.yml
#	Resources/Prototypes/Entities/Mobs/Species/moth.yml
#	Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml
#	Resources/Prototypes/LootTables/suspicion_loot_table.yml
#	Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
#	Resources/Prototypes/Reagents/medicine.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/science.yml
#	Resources/Prototypes/Recipes/Lathes/medical.yml
#	Resources/Prototypes/SoundCollections/NukeMusic.yml
#	Resources/ServerInfo/Guidebook/Cargo/Salvage.xml
#	Resources/Textures/Clothing/Hands/Gloves/KnuckleDusters/brassknuckleduster.rsi/equipped-OUTERCLOTHING-reptilian.png
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/basic.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/alien3.png
#	Resources/Textures/Effects/speech.rsi/alienroyal3.png
#	Resources/Textures/Effects/speech.rsi/default3.png
#	Resources/Textures/Effects/speech.rsi/guardian3.png
#	Resources/Textures/Effects/speech.rsi/holo3.png
#	Resources/Textures/Effects/speech.rsi/lawyer3.png
#	Resources/Textures/Effects/speech.rsi/lizard3.png
#	Resources/Textures/Effects/speech.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/moth3.png
#	Resources/Textures/Effects/speech.rsi/robot3.png
#	Resources/Textures/Effects/speech.rsi/slime3.png
#	Resources/Textures/Effects/speech.rsi/spider3.png
#	Resources/Textures/Effects/speech.rsi/syndibot3.png
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json
#	Resources/Textures/Mobs/Species/Reptilian/parts.rsi/head_f.png
#	Resources/Textures/Mobs/Species/Reptilian/parts.rsi/head_m.png
#	Resources/Textures/Objects/Misc/id_cards.rsi/meta.json
#	Resources/Textures/Structures/Doors/Airlocks/Glass/external.rsi/emergency_unlit.png
#	Resources/Textures/Structures/Piping/disposal.rsi/meta.json
#	Resources/Textures/Tiles/attributions.yml
#	Resources/Textures/Tiles/steel_dirty.png
#	Resources/migration.yml
#	runclient-Tools.bat
#	runclient.bat
2025-05-27 03:41:41 +03:00

627 lines
23 KiB
C#

using System.Buffers;
using System.Numerics;
using Content.Client.Shuttles.Systems;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.UI.MapObjects;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Collections;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Shuttles.UI;
[GenerateTypedNameReferences]
public sealed partial class ShuttleMapControl : BaseShuttleControl
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IInputManager _inputs = default!;
private readonly SharedMapSystem _mapSystem;
private readonly ShuttleSystem _shuttles;
private readonly SharedTransformSystem _xformSystem;
protected override bool Draggable => true;
public bool ShowBeacons = true;
public MapId ViewingMap = MapId.Nullspace;
private EntityUid? _shuttleEntity;
private readonly Font _font;
private readonly EntityQuery<PhysicsComponent> _physicsQuery;
/// <summary>
/// Toggles FTL mode on. This shows a pre-vis for FTLing a grid.
/// </summary>
public bool FtlMode;
private Angle _ftlAngle;
/// <summary>
/// Are we currently in FTL.
/// </summary>
public bool InFtl;
/// <summary>
/// Raised when a request to FTL to a particular spot is raised.
/// </summary>
public event Action<MapCoordinates, Angle>? RequestFTL;
public event Action<NetEntity, Angle>? RequestBeaconFTL;
/// <summary>
/// Set every draw to determine the beacons that are clickable for mouse events
/// </summary>
private List<IMapObject> _beacons = new();
// Per frame data to avoid re-allocating
private readonly List<IMapObject> _mapObjects = new();
private readonly Dictionary<Color, List<Vector2>> _verts = new();
private readonly Dictionary<Color, List<Vector2>> _edges = new();
private readonly Dictionary<Color, List<(Vector2, string)>> _strings = new();
private readonly List<ShuttleExclusionObject> _viewportExclusions = new();
public ShuttleMapControl() : base(256f, 512f, 512f)
{
RobustXamlLoader.Load(this);
_mapSystem = EntManager.System<SharedMapSystem>();
_shuttles = EntManager.System<ShuttleSystem>();
_xformSystem = EntManager.System<SharedTransformSystem>();
var cache = IoCManager.Resolve<IResourceCache>();
_physicsQuery = EntManager.GetEntityQuery<PhysicsComponent>();
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
public void SetMap(MapId mapId, Vector2 offset, bool recentering = false)
{
ViewingMap = mapId;
TargetOffset = offset;
Recentering = recentering;
}
public void SetShuttle(EntityUid? entity)
{
_shuttleEntity = entity;
}
protected override void MouseMove(GUIMouseMoveEventArgs args)
{
// No move for you.
if (FtlMode)
return;
base.MouseMove(args);
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
if (FtlMode && ViewingMap != MapId.Nullspace)
{
if (args.Function == EngineKeyFunctions.UIClick)
{
var mapUid = _mapSystem.GetMapOrInvalid(ViewingMap);
var beaconsOnly = EntManager.TryGetComponent(mapUid, out FTLDestinationComponent? destComp) &&
destComp.BeaconsOnly;
var mapTransform = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero);
if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePixelPosition, PixelRect, out var foundBeacon, out _))
{
RequestBeaconFTL?.Invoke(foundBeacon.Entity, _ftlAngle);
}
else
{
// We'll send the "adjusted" position and server will adjust it back when relevant.
var mapCoords = new MapCoordinates(InverseMapPosition(args.RelativePosition), ViewingMap);
RequestFTL?.Invoke(mapCoords, _ftlAngle);
}
}
}
base.KeyBindUp(args);
}
protected override void MouseWheel(GUIMouseWheelEventArgs args)
{
// Scroll handles FTL rotation if you're in FTL mode.
if (FtlMode)
{
_ftlAngle += Angle.FromDegrees(15f) * args.Delta.Y;
_ftlAngle = _ftlAngle.Reduced();
return;
}
base.MouseWheel(args);
}
private void DrawParallax(DrawingHandleScreen handle)
{
if (!EntManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform) || shuttleXform.MapUid == null)
return;
// TODO: Figure out how the fuck to make this common between the 3 slightly different parallax methods and move to parallaxsystem.
// Draw background texture
var tex = _shuttles.GetTexture(shuttleXform.MapUid.Value);
// Size of the texture in world units.
var size = tex.Size * MinimapScale * 1f;
var position = ScalePosition(new Vector2(-Offset.X, Offset.Y));
var slowness = 1f;
// The "home" position is the effective origin of this layer.
// Parallax shifting is relative to the home, and shifts away from the home and towards the Eye centre.
// The effects of this are such that a slowness of 1 anchors the layer to the centre of the screen, while a slowness of 0 anchors the layer to the world.
// (For values 0.0 to 1.0 this is in effect a lerp, but it's deliberately unclamped.)
// The ParallaxAnchor adapts the parallax for station positioning and possibly map-specific tweaks.
var home = Vector2.Zero;
var scrolled = Vector2.Zero;
// Origin - start with the parallax shift itself.
var originBL = (position - home) * slowness + scrolled;
// Place at the home.
originBL += home;
// Centre the image.
originBL -= size / 2;
// Remove offset so we can floor.
var botLeft = new Vector2(0f, 0f);
var topRight = botLeft + Size;
var flooredBL = botLeft - originBL;
// Floor to background size.
flooredBL = (flooredBL / size).Floored() * size;
// Re-offset.
flooredBL += originBL;
for (var x = flooredBL.X; x < topRight.X; x += size.X)
{
for (var y = flooredBL.Y; y < topRight.Y; y += size.Y)
{
handle.DrawTextureRect(tex, new UIBox2(x, y, x + size.X, y + size.Y));
}
}
}
/// <summary>
/// Gets the map objects that intersect the viewport.
/// </summary>
/// <param name="mapObjects"></param>
/// <returns></returns>
private List<IMapObject> GetViewportMapObjects(Matrix3x2 matty, List<IMapObject> mapObjects)
{
var results = new List<IMapObject>();
var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale));
var viewBox = new UIBox2i(Vector2i.Zero - enlargement, PixelSize + enlargement);
foreach (var mapObj in mapObjects)
{
// If it's a grid-map skip it.
if (mapObj is GridMapObject gridObj && EntManager.HasComponent<MapComponent>(gridObj.Entity))
continue;
var mapCoords = _shuttles.GetMapCoordinates(mapObj);
var relativePos = Vector2.Transform(mapCoords.Position, matty);
relativePos = relativePos with { Y = -relativePos.Y };
var uiPosition = ScalePosition(relativePos);
if (!viewBox.Contains(uiPosition.Floored()))
continue;
results.Add(mapObj);
}
return results;
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (ViewingMap == MapId.Nullspace)
return;
var mapObjects = _mapObjects;
DrawRecenter();
if (InFtl || mapObjects.Count == 0)
{
DrawBacking(handle);
DrawNoSignal(handle);
return;
}
DrawParallax(handle);
var viewedMapUid = _mapSystem.GetMapOrInvalid(ViewingMap);
var matty = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero);
var realTime = _timing.RealTime;
var viewBox = new Box2(Offset - WorldRangeVector, Offset + WorldRangeVector);
var viewportObjects = GetViewportMapObjects(matty, mapObjects);
_viewportExclusions.Clear();
// Draw our FTL range + no FTL zones
// Do it up here because we want this layered below most things.
if (FtlMode)
{
if (EntManager.TryGetComponent<TransformComponent>(_shuttleEntity, out var shuttleXform))
{
var gridUid = _shuttleEntity.Value;
var gridPhysics = _physicsQuery.GetComponent(gridUid);
var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform);
gridPos = Maps.GetGridPosition((gridUid, gridPhysics), gridPos, gridRot);
var gridRelativePos = Vector2.Transform(gridPos, matty);
gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
var gridUiPos = ScalePosition(gridRelativePos);
var range = _shuttles.GetFTLRange(gridUid);
range *= MinimapScale;
handle.DrawCircle(gridUiPos, range, Color.Gold, filled: false);
}
}
var exclusionColor = Color.Red;
// Exclusions need a bumped range so we check all the ones on the map.
foreach (var mapObj in mapObjects)
{
if (mapObj is not ShuttleExclusionObject exclusion)
continue;
// Check if it even intersects the viewport.
var coords = EntManager.GetCoordinates(exclusion.Coordinates);
var mapCoords = _xformSystem.ToMapCoordinates(coords);
var enlargedBounds = viewBox.Enlarged(exclusion.Range);
if (mapCoords.MapId != ViewingMap ||
!enlargedBounds.Contains(mapCoords.Position))
{
continue;
}
var adjustedPos = Vector2.Transform(mapCoords.Position, matty);
var localPos = ScalePosition(adjustedPos with { Y = -adjustedPos.Y});
handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor.WithAlpha(0.05f));
handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor, filled: false);
_viewportExclusions.Add(exclusion);
}
_verts.Clear();
_edges.Clear();
_strings.Clear();
// Add beacons if relevant.
var beaconsOnly = _shuttles.IsBeaconMap(viewedMapUid);
var controlLocalBounds = PixelRect;
_beacons.Clear();
if (ShowBeacons)
{
var beaconColor = Color.AliceBlue;
foreach (var (beaconName, coords, mapO) in GetBeacons(viewportObjects, matty, controlLocalBounds))
{
var localPos = Vector2.Transform(coords.Position, matty);
localPos = localPos with { Y = -localPos.Y };
var beaconUiPos = ScalePosition(localPos);
var mapObject = GetMapObject(localPos, Angle.Zero, scale: 0.75f, scalePosition: true);
var existingVerts = _verts.GetOrNew(beaconColor);
var existingEdges = _edges.GetOrNew(beaconColor);
AddMapObject(existingEdges, existingVerts, mapObject);
_beacons.Add(mapO);
var existingStrings = _strings.GetOrNew(beaconColor);
existingStrings.Add((beaconUiPos, beaconName));
}
}
foreach (var mapObj in viewportObjects)
{
if (mapObj is not GridMapObject gridObj || !EntManager.TryGetComponent(gridObj.Entity, out MapGridComponent? mapGrid))
continue;
Entity<MapGridComponent> grid = (gridObj.Entity, mapGrid);
IFFComponent? iffComp = null;
// Rudimentary IFF for now, if IFF hiding on then we don't show on the map at all
if (grid.Owner != _shuttleEntity &&
EntManager.TryGetComponent(grid, out iffComp) &&
(iffComp.Flags & IFFFlags.Hide) != 0x0)
{
continue;
}
var gridColor = _shuttles.GetIFFColor(grid, self: _shuttleEntity == grid.Owner, component: iffComp);
var existingVerts = _verts.GetOrNew(gridColor);
var existingEdges = _edges.GetOrNew(gridColor);
var gridPhysics = _physicsQuery.GetComponent(grid.Owner);
var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(grid.Owner);
gridPos = Maps.GetGridPosition((grid, gridPhysics), gridPos, gridRot);
var gridRelativePos = Vector2.Transform(gridPos, matty);
gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
var gridUiPos = ScalePosition(gridRelativePos);
var mapObject = GetMapObject(gridRelativePos, Angle.Zero, scalePosition: true);
AddMapObject(existingEdges, existingVerts, mapObject);
// Text
if (iffComp != null && (iffComp.Flags & IFFFlags.HideLabel) != 0x0)
continue;
// Force drawing it at this point.
var iffText = _shuttles.GetIFFLabel(grid, self: true, component: iffComp);
if (string.IsNullOrEmpty(iffText))
continue;
var existingStrings = _strings.GetOrNew(gridColor);
existingStrings.Add((gridUiPos, iffText));
}
// Batch the colors whoopie
// really only affects forks with lots of grids.
foreach (var (color, sendVerts) in _verts)
{
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, sendVerts, color.WithAlpha(0.05f));
}
foreach (var (color, sendEdges) in _edges)
{
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, sendEdges, color);
}
foreach (var (color, sendStrings) in _strings)
{
var adjustedColor = Color.FromSrgb(color);
foreach (var (gridUiPos, iffText) in sendStrings)
{
var textWidth = handle.GetDimensions(_font, iffText, 1f);
handle.DrawString(_font, gridUiPos + textWidth with { X = -textWidth.X / 2f, Y = textWidth.Y * UIScale }, iffText, adjustedColor);
}
}
var mousePos = _inputs.MouseScreenPosition;
var mouseLocalPos = GetLocalPosition(mousePos);
// Draw dotted line from our own shuttle entity to mouse.
if (FtlMode)
{
if (mousePos.Window != WindowId.Invalid)
{
// If mouse inbounds then draw it.
if (_shuttleEntity != null && controlLocalBounds.Contains(mouseLocalPos.Floored()) &&
EntManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform) &&
shuttleXform.MapID != MapId.Nullspace)
{
// If it's a beacon only map then snap the mouse to a nearby spot.
ShuttleBeaconObject foundBeacon = default;
// Check for beacons around mouse and snap to that.
if (beaconsOnly && TryGetBeacon(viewportObjects, matty, mouseLocalPos, controlLocalBounds, out foundBeacon, out var foundLocalPos))
{
mouseLocalPos = foundLocalPos;
}
var grid = EntManager.GetComponent<MapGridComponent>(_shuttleEntity.Value);
var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform);
gridPos = Maps.GetGridPosition(_shuttleEntity.Value, gridPos, gridRot);
// do NOT apply LocalCenter operation here because it will be adjusted in FTLFree.
var mouseMapPos = InverseMapPosition(mouseLocalPos);
var ftlFree = (!beaconsOnly || foundBeacon != default) &&
_shuttles.FTLFree(_shuttleEntity.Value, new EntityCoordinates(viewedMapUid, mouseMapPos), _ftlAngle, _viewportExclusions);
var color = ftlFree ? Color.LimeGreen : Color.Magenta;
var gridRelativePos = Vector2.Transform(gridPos, matty);
gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
var gridUiPos = ScalePosition(gridRelativePos);
// Draw FTL buffer around the mouse.
var ourFTLBuffer = _shuttles.GetFTLBufferRange(_shuttleEntity.Value, grid);
ourFTLBuffer *= MinimapScale;
handle.DrawCircle(mouseLocalPos, ourFTLBuffer, Color.Magenta.WithAlpha(0.01f));
handle.DrawCircle(mouseLocalPos, ourFTLBuffer, Color.Magenta, filled: false);
// Draw line from our shuttle to target
// Might need to clip the line if it's too far? But my brain wasn't working so F.
handle.DrawDottedLine(gridUiPos, mouseLocalPos, color, (float) realTime.TotalSeconds * 30f);
// Draw shuttle pre-vis
var mouseVerts = GetMapObject(mouseLocalPos, _ftlAngle, scale: MinimapScale);
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, mouseVerts.Span, color.WithAlpha(0.05f));
handle.DrawPrimitives(DrawPrimitiveTopology.LineLoop, mouseVerts.Span, color);
// Draw a notch indicating direction.
var ftlLength = GetMapObjectRadius() + 16f;
var ftlEnd = mouseLocalPos + _ftlAngle.RotateVec(new Vector2(0f, -ftlLength));
handle.DrawLine(mouseLocalPos, ftlEnd, color);
}
}
}
// Draw the coordinates
var mapOffset = MidPointVector;
if (mousePos.Window != WindowId.Invalid &&
controlLocalBounds.Contains(mouseLocalPos.Floored()))
{
mapOffset = mouseLocalPos;
}
mapOffset = InverseMapPosition(mapOffset);
var coordsText = $"{mapOffset.X:0.0}, {mapOffset.Y:0.0}";
DrawData(handle, coordsText);
}
private void AddMapObject(List<Vector2> edges, List<Vector2> verts, ValueList<Vector2> mapObject)
{
var bottom = mapObject[0];
var right = mapObject[1];
var top = mapObject[2];
var left = mapObject[3];
// Diamond interior
verts.Add(bottom);
verts.Add(right);
verts.Add(top);
verts.Add(bottom);
verts.Add(top);
verts.Add(left);
// Diamond edges
edges.Add(bottom);
edges.Add(right);
edges.Add(right);
edges.Add(top);
edges.Add(top);
edges.Add(left);
edges.Add(left);
edges.Add(bottom);
}
/// <summary>
/// Returns the beacons that intersect the viewport.
/// </summary>
private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List<IMapObject> mapObjs, Matrix3x2 mapTransform, UIBox2i area)
{
foreach (var mapO in mapObjs)
{
if (mapO is not ShuttleBeaconObject beacon)
continue;
var beaconCoords = _xformSystem.ToMapCoordinates(EntManager.GetCoordinates(beacon.Coordinates));
var position = Vector2.Transform(beaconCoords.Position, mapTransform);
var localPos = ScalePosition(position with {Y = -position.Y});
// If beacon not on screen then ignore it.
if (!area.Contains(localPos.Floored()))
continue;
yield return (beacon.Name, beaconCoords, mapO);
}
}
private float GetMapObjectRadius(float scale = 1f) => WorldRange / 40f * scale;
private ValueList<Vector2> GetMapObject(Vector2 localPos, Angle angle, float scale = 1f, bool scalePosition = false)
{
// Constant size diamonds
var diamondRadius = GetMapObjectRadius();
var mapObj = new ValueList<Vector2>(4)
{
localPos + angle.RotateVec(new Vector2(0f, -2f * diamondRadius)) * scale,
localPos + angle.RotateVec(new Vector2(diamondRadius, 0f)) * scale,
localPos + angle.RotateVec(new Vector2(0f, 2f * diamondRadius)) * scale,
localPos + angle.RotateVec(new Vector2(-diamondRadius, 0f)) * scale,
};
if (scalePosition)
{
for (var i = 0; i < mapObj.Count; i++)
{
mapObj[i] = ScalePosition(mapObj[i]);
}
}
return mapObj;
}
private bool TryGetBeacon(IEnumerable<IMapObject> mapObjects, Matrix3x2 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos)
{
Logger.Info("TryGetBeacon");
// In pixels
const float BeaconSnapRange = 32f;
float nearestValue = float.MaxValue;
foundLocalPos = Vector2.Zero;
foundBeacon = default;
foreach (var mapObj in mapObjects)
{
if (mapObj is not ShuttleBeaconObject beaconObj)
continue;
var beaconCoords = _xformSystem.ToMapCoordinates(EntManager.GetCoordinates(beaconObj.Coordinates));
if (beaconCoords.MapId != ViewingMap)
continue;
// Invalid beacon?
if (!_shuttles.CanFTLBeacon(beaconObj.Coordinates))
continue;
Logger.Info("Can FTL Beacon");
var position = Vector2.Transform(beaconCoords.Position, mapTransform);
var localPos = ScalePosition(position with {Y = -position.Y});
// If beacon not on screen then ignore it.
if (!area.Contains(localPos.Floored()))
continue;
Logger.Info("area contains localPos.Floored()");
var distance = (localPos - mousePos).Length();
if (distance > BeaconSnapRange * UIScale ||
distance > nearestValue)
{
Logger.Info("distance > BeaconSnapRange");
continue;
}
Logger.Info("Found beacon");
foundLocalPos = localPos;
nearestValue = distance;
foundBeacon = beaconObj;
}
return foundBeacon != default;
}
/// <summary>
/// Sets the map objects for the next draw.
/// </summary>
public void SetMapObjects(Dictionary<MapId, List<IMapObject>> mapObjects)
{
_mapObjects.Clear();
if (mapObjects.TryGetValue(ViewingMap, out var obbies))
{
_mapObjects.AddRange(obbies);
}
}
}