Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using Content.Shared.Humanoid;
using Content.Shared.Imperial.XxRaay.Android;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;

namespace Content.Client.Imperial.XxRaay.Android;

/// <summary>
/// Клиентская визуализирующая система маскировки андроида
///</summary>
public sealed partial class AndroidDisguiseVisualizerSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<AndroidDisguiseComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<AndroidDisguiseComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<AndroidDisguiseComponent, AfterAutoHandleStateEvent>(OnAfterState);
}

private void OnStartup(Entity<AndroidDisguiseComponent> ent, ref ComponentStartup args)
{
var uid = (EntityUid) ent;
UpdateVisuals(uid, ent.Comp);
}
Comment on lines +24 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Избыточная локальная переменная uid — явный каст не нужен.

Entity<T> неявно приводится к EntityUid, поэтому создавать промежуточную переменную через (EntityUid) ent излишне. ent можно передавать напрямую во все вызовы, принимающие EntityUid.

♻️ Предлагаемое исправление
 private void OnStartup(Entity<AndroidDisguiseComponent> ent, ref ComponentStartup args)
 {
-    var uid = (EntityUid) ent;
-    UpdateVisuals(uid, ent.Comp);
+    UpdateVisuals(ent, ent.Comp);
 }

 private void OnShutdown(Entity<AndroidDisguiseComponent> ent, ref ComponentShutdown args)
 {
     if (!TryComp<SpriteComponent>(ent, out var sprite))
         return;

-    var uid = (EntityUid) ent;
-
-    _sprite.LayerSetVisible((uid, sprite), ent.Comp.AndroidBaseLayer, true);
-    _sprite.LayerSetVisible((uid, sprite), ent.Comp.AndroidTransformLayer, false);
-    _sprite.LayerSetVisible((uid, sprite), ent.Comp.AndroidRetransformLayer, false);
-    SetHumanoidVisible((uid, sprite), visible: false);
+    _sprite.LayerSetVisible((ent, sprite), ent.Comp.AndroidBaseLayer, true);
+    _sprite.LayerSetVisible((ent, sprite), ent.Comp.AndroidTransformLayer, false);
+    _sprite.LayerSetVisible((ent, sprite), ent.Comp.AndroidRetransformLayer, false);
+    SetHumanoidVisible((ent, sprite), visible: false);
 }

 private void OnAfterState(Entity<AndroidDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
 {
-    var uid = (EntityUid) ent;
-    UpdateVisuals(uid, ent.Comp);
+    UpdateVisuals(ent, ent.Comp);
 }

Also applies to: 30-41, 43-47

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Content.Client/Imperial/XxRaay/Android/AndroidDisguiseVisualizerSystem.cs`
around lines 24 - 28, Remove the redundant local variable and explicit cast in
OnStartup: do not create "var uid = (EntityUid) ent"; pass the
Entity<AndroidDisguiseComponent> value (ent) directly into UpdateVisuals (and
any other methods that accept EntityUid) since Entity<T> implicitly converts to
EntityUid. Apply the same cleanup to the other occurrences mentioned (the blocks
around lines 30-41 and 43-47) so no methods create an intermediate uid via
(EntityUid) ent and instead pass ent directly; ensure references to
AndroidDisguiseComponent, OnStartup, and UpdateVisuals are preserved.


private void OnShutdown(Entity<AndroidDisguiseComponent> ent, ref ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;

var uid = (EntityUid) ent;

_sprite.LayerSetVisible((uid, sprite), ent.Comp.AndroidBaseLayer, true);
_sprite.LayerSetVisible((uid, sprite), ent.Comp.AndroidTransformLayer, false);
_sprite.LayerSetVisible((uid, sprite), ent.Comp.AndroidRetransformLayer, false);
SetHumanoidVisible((uid, sprite), visible: false);
}

private void OnAfterState(Entity<AndroidDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
{
var uid = (EntityUid) ent;
UpdateVisuals(uid, ent.Comp);
}

private void UpdateVisuals(EntityUid uid, AndroidDisguiseComponent comp)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;


_sprite.LayerSetVisible((uid, sprite), comp.AndroidBaseLayer,
comp.State is AndroidDisguiseState.Android);

_sprite.LayerSetVisible((uid, sprite), comp.AndroidTransformLayer,
comp.State == AndroidDisguiseState.TransformingToHuman);
_sprite.LayerSetVisible((uid, sprite), comp.AndroidRetransformLayer,
comp.State == AndroidDisguiseState.TransformingToAndroid);
Comment on lines +55 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Непоследовательное сравнение enum: is и == в одном методе.

Строка 56 использует паттерн-матчинг is, тогда как строки 59 и 61 — оператор == для значений того же enum AndroidDisguiseState. Стиль следует унифицировать.

♻️ Предлагаемое исправление (унификация на `==`)
-        _sprite.LayerSetVisible((uid, sprite), comp.AndroidBaseLayer,
-            comp.State is AndroidDisguiseState.Android);
+        _sprite.LayerSetVisible((uid, sprite), comp.AndroidBaseLayer,
+            comp.State == AndroidDisguiseState.Android);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Content.Client/Imperial/XxRaay/Android/AndroidDisguiseVisualizerSystem.cs`
around lines 55 - 61, The enum comparison in AndroidDisguiseVisualizerSystem is
inconsistent: replace the pattern-match check using "is" on comp.State with an
equality comparison "==" to match the other comparisons; specifically update the
call to _sprite.LayerSetVisible that references AndroidBaseLayer so it uses
comp.State == AndroidDisguiseState.Android (keeping the existing comparisons for
AndroidTransformLayer and AndroidRetransformLayer unchanged) to make all
comparisons against AndroidDisguiseState consistent.


var humanoidVisible = comp.State != AndroidDisguiseState.Android;
SetHumanoidVisible((uid, sprite), humanoidVisible);
}

private void SetHumanoidVisible(Entity<SpriteComponent> ent, bool visible)
{
var spriteEnt = ent.AsNullable();

_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.Chest, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.Head, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.Snout, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.Eyes, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.RArm, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.LArm, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.RLeg, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.LLeg, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.UndergarmentBottom, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.UndergarmentTop, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.LFoot, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.RFoot, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.LHand, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.RHand, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.SnoutCover, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.FacialHair, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.Hair, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.HeadSide, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.HeadTop, visible);
_sprite.LayerSetVisible(spriteEnt, HumanoidVisualLayers.Tail, visible);
}
Comment on lines +67 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Длинный список слоёв в SetHumanoidVisible — рассмотрите итерацию по enum.

20 вызовов LayerSetVisible с захардкоженными значениями HumanoidVisualLayers. Если в будущем добавятся новые слои, их легко пропустить. Можно итерировать по значениям enum, если это допустимо в контексте (некоторые слои могут требовать исключения).

🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/Android/AndroidDisguiseVisualizerSystem.cs`
around lines 67 - 91, В методе SetHumanoidVisible замените 20 ручных вызовов
_sprite.LayerSetVisible на итерацию по значениям enum HumanoidVisualLayers
(через Enum.GetValues / Enum.GetValues<HumanoidVisualLayers>()), приводя
значение к нужному типу и передавая spriteEnt и visible в
_sprite.LayerSetVisible; при этом явно пропустите (фильтруйте) любые слои,
которые не должны управляться здесь (если есть исключения), чтобы будущие
добавления в enum автоматически обрабатывались.

}


80 changes: 80 additions & 0 deletions Content.Client/Imperial/XxRaay/Android/AndroidOverlaySystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Content.Shared.Imperial.XxRaay.Android;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;

namespace Content.Client.Imperial.XxRaay.Android;

/// <summary>
/// Включает клиентский оверлей
/// </summary>
public sealed partial class AndroidOverlaySystem : EntitySystem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

partial модификатор выглядит излишним.

В файле нет второй части класса, и никаких source-генераторов, которые требовали бы partial, не видно. Если он не нужен — уберите его, чтобы не создавать путаницы.

♻️ Предлагаемое исправление
-public sealed partial class AndroidOverlaySystem : EntitySystem
+public sealed class AndroidOverlaySystem : EntitySystem
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Content.Client/Imperial/XxRaay/Android/AndroidOverlaySystem.cs` at line 11,
Класс AndroidOverlaySystem объявлен с модификатором partial, но другой части
класса или source-генераторов не видно — удалите keyword partial из объявления
класса (AndroidOverlaySystem : EntitySystem) в AndroidOverlaySystem.cs; перед
удалением убедитесь, что нет других partial-частей класса или внешних
генераторов/файлов, которые ожидают наличия partial, и затем перестройте проект.

{
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
[Dependency] private readonly IPlayerManager _player = default!;

private AndroidOverlay? _overlay;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<AndroidOverlayComponent, ComponentInit>(OnOverlayInit);
SubscribeLocalEvent<AndroidOverlayComponent, ComponentRemove>(OnOverlayRemove);
SubscribeLocalEvent<AndroidOverlayComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<AndroidOverlayComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
}

public override void Shutdown()
{
base.Shutdown();
RemoveOverlay();
}

private void OnOverlayInit(Entity<AndroidOverlayComponent> ent, ref ComponentInit args)
{
var local = _player.LocalEntity;
if (local != ent)
return;

AddOverlay(ent);
}

private void OnOverlayRemove(Entity<AndroidOverlayComponent> ent, ref ComponentRemove args)
{
var local = _player.LocalEntity;
if (local != ent)
return;

RemoveOverlay();
}

private void OnPlayerAttached(Entity<AndroidOverlayComponent> ent, ref LocalPlayerAttachedEvent args)
{
AddOverlay(ent);
}

private void OnPlayerDetached(Entity<AndroidOverlayComponent> ent, ref LocalPlayerDetachedEvent args)
{
RemoveOverlay();
}

private void AddOverlay(Entity<AndroidOverlayComponent> ent)
{
if (_overlay != null)
return;

_overlay = new AndroidOverlay(ent.Comp.IconSprite, ent.Comp.IconScale);
_overlayMgr.AddOverlay(_overlay);
}

private void RemoveOverlay()
{
if (_overlay == null)
return;

_overlayMgr.RemoveOverlay(_overlay);
_overlay = null;
}
}

142 changes: 142 additions & 0 deletions Content.Client/Imperial/XxRaay/Android/Overlays/AndroidOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System.Numerics;
using Content.Shared.IdentityManagement;
using Content.Shared.Imperial.XxRaay.Android;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Client.Imperial.XxRaay.Android;

/// <summary>
/// Оверлей, рисующий иконку над всеми сущностями с <see cref="AndroidStressComponent"/>.
/// </summary>
public sealed partial class AndroidOverlay : Overlay
{
[Dependency] private readonly IEntitySystemManager _systems = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IResourceCache _resources = default!;

private readonly SpriteSystem _sprites;
private readonly SharedTransformSystem _xforms;
private readonly MapSystem _maps;

private readonly string _iconSprite;
private readonly float _iconScale;
private readonly Font _font;

public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;

public AndroidOverlay(string iconSprite, float iconScale)
{
IoCManager.InjectDependencies(this);

_sprites = _systems.GetEntitySystem<SpriteSystem>();
_xforms = _systems.GetEntitySystem<SharedTransformSystem>();
_maps = _systems.GetEntitySystem<MapSystem>();
_iconSprite = iconSprite;
_iconScale = iconScale;
_font = new VectorFont(_resources.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 9);
}

protected override void Draw(in OverlayDrawArgs args)
{
if (args.Viewport.Eye == null)
return;

switch (args.Space)
{
case OverlaySpace.WorldSpace:
DrawWorld(args);
break;
case OverlaySpace.ScreenSpace:
DrawScreen(args);
break;
}
}

private void DrawWorld(in OverlayDrawArgs args)
{
if (!_maps.TryGetMap(args.MapId, out var mapUid))
return;

var worldHandle = args.WorldHandle;
var texture = _sprites.Frame0(ParseSpriteSpecifier(_iconSprite));

var worldMatrix = _xforms.GetWorldMatrix(mapUid.Value);
var invMatrix = _xforms.GetInvWorldMatrix(mapUid.Value);

var query = _entManager.EntityQueryEnumerator<AndroidStressComponent>();
while (query.MoveNext(out var uid, out _))
{
if (_player.LocalEntity == uid)
continue;

var coords = _xforms.GetMapCoordinates(uid);
if (coords.MapId != args.MapId)
continue;
Comment on lines +76 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Нет отсечения сущностей за пределами viewport.

DrawWorld перебирает все сущности с AndroidStressComponent на карте, не проверяя, попадают ли они в видимую область (args.WorldBounds). При большом количестве андроидов на карте это может быть заметно.

♻️ Добавить проверку bounds
  var coords = _xforms.GetMapCoordinates(uid);
  if (coords.MapId != args.MapId)
      continue;

+ if (!args.WorldBounds.Contains(coords.Position))
+     continue;
+
  if (!_entManager.TryGetComponent(uid, out AndroidDiodeComponent? diode) || !diode.HasDiode)
      continue;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var query = _entManager.EntityQueryEnumerator<AndroidStressComponent>();
while (query.MoveNext(out var uid, out _))
{
if (_player.LocalEntity == uid)
continue;
var coords = _xforms.GetMapCoordinates(uid);
if (coords.MapId != args.MapId)
continue;
var query = _entManager.EntityQueryEnumerator<AndroidStressComponent>();
while (query.MoveNext(out var uid, out _))
{
if (_player.LocalEntity == uid)
continue;
var coords = _xforms.GetMapCoordinates(uid);
if (coords.MapId != args.MapId)
continue;
if (!args.WorldBounds.Contains(coords.Position))
continue;
if (!_entManager.TryGetComponent(uid, out AndroidDiodeComponent? diode) || !diode.HasDiode)
continue;
🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/Android/Overlays/AndroidOverlay.cs` around
lines 76 - 84, DrawWorld currently iterates all entities from
_entManager.EntityQueryEnumerator<AndroidStressComponent>() and skips only by
player and MapId; add a bounds check using args.WorldBounds to early-continue
for entities outside the viewport: after obtaining coords via
_xforms.GetMapCoordinates(uid) and verifying coords.MapId == args.MapId, test
whether coords (or coords.Position) falls inside args.WorldBounds and if not
continue; update the loop in DrawWorld so only entities whose coordinates
intersect args.WorldBounds are processed to avoid rendering/perf for offscreen
AndroidStressComponent instances.


if (!_entManager.TryGetComponent(uid, out AndroidDiodeComponent? diode) || !diode.HasDiode)
continue;

var localPos = Vector2.Transform(coords.Position, invMatrix);
var halfSize = 0.5f * _iconScale;

var aabb = new Box2(
localPos - new Vector2(halfSize, halfSize),
localPos + new Vector2(halfSize, halfSize));

var box = new Box2Rotated(aabb, Angle.Zero, localPos);

worldHandle.SetTransform(worldMatrix);
worldHandle.DrawTextureRect(texture, box, Color.White);
worldHandle.SetTransform(Matrix3x2.Identity);
Comment on lines +89 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Иконка отрисовывается по центру сущности — вероятно, нужен вертикальный сдвиг.

Документация класса гласит: "рисующий иконку над всеми сущностями", однако AABB строится точно по центру позиции сущности (localPos). Обычно подобные оверлеи рисуют иконку выше спрайта сущности, чтобы она не перекрывала саму сущность. Если это не задумка — добавьте положительный сдвиг по Y:

💡 Пример сдвига иконки вверх
- var localPos = Vector2.Transform(coords.Position, invMatrix);
+ var rawPos = Vector2.Transform(coords.Position, invMatrix);
+ var localPos = rawPos + new Vector2(0f, 0.5f); // смещение вверх над сущностью
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var localPos = Vector2.Transform(coords.Position, invMatrix);
var halfSize = 0.5f * _iconScale;
var aabb = new Box2(
localPos - new Vector2(halfSize, halfSize),
localPos + new Vector2(halfSize, halfSize));
var box = new Box2Rotated(aabb, Angle.Zero, localPos);
worldHandle.SetTransform(worldMatrix);
worldHandle.DrawTextureRect(texture, box, Color.White);
worldHandle.SetTransform(Matrix3x2.Identity);
var rawPos = Vector2.Transform(coords.Position, invMatrix);
var localPos = rawPos + new Vector2(0f, 0.5f); // смещение вверх над сущностью
var halfSize = 0.5f * _iconScale;
var aabb = new Box2(
localPos - new Vector2(halfSize, halfSize),
localPos + new Vector2(halfSize, halfSize));
var box = new Box2Rotated(aabb, Angle.Zero, localPos);
worldHandle.SetTransform(worldMatrix);
worldHandle.DrawTextureRect(texture, box, Color.White);
worldHandle.SetTransform(Matrix3x2.Identity);
🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/Android/Overlays/AndroidOverlay.cs` around
lines 89 - 100, AABB is built at localPos (calculated from coords.Position and
invMatrix) so the icon is centered on the entity; shift localPos upward before
constructing aabb/Box2Rotated to draw the icon above the sprite. Modify the
block that computes localPos, aabb and box in AndroidOverlay.cs: compute a
shifted position (e.g., localPosShifted = localPos + new Vector2(0,
-verticalOffset)) using a suitable verticalOffset (for example based on
_iconScale or sprite half-height), then use localPosShifted when creating the
Box2 and Box2Rotated (and for box origin) so DrawTextureRect renders the icon
above the entity instead of centered.

}
Comment on lines +65 to +101
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Текстура парсится и загружается каждый кадр — кэшируйте результат.

ParseSpriteSpecifier(_iconSprite) и _sprites.Frame0(...) вызываются на каждый вызов DrawWorld. Поскольку _iconSprite неизменна, результат Frame0 следует вычислить один раз (например, в конструкторе или в ленивом поле) и переиспользовать.

♻️ Предлагаемое исправление

Добавьте поле в класс и инициализируйте в конструкторе:

  private readonly Font _font;
+ private readonly Texture _iconTexture;

  ...

  public AndroidOverlay(string iconSprite, float iconScale)
  {
      IoCManager.InjectDependencies(this);

      _sprites = _systems.GetEntitySystem<SpriteSystem>();
      _xforms = _systems.GetEntitySystem<SharedTransformSystem>();
      _maps = _systems.GetEntitySystem<MapSystem>();
      _iconSprite = iconSprite;
      _iconScale = iconScale;
      _font = new VectorFont(_resources.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 9);
+     _iconTexture = _sprites.Frame0(ParseSpriteSpecifier(_iconSprite));
  }

И в DrawWorld:

- var texture = _sprites.Frame0(ParseSpriteSpecifier(_iconSprite));
+ var texture = _iconTexture;
🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/Android/Overlays/AndroidOverlay.cs` around
lines 65 - 101, DrawWorld currently calls ParseSpriteSpecifier(_iconSprite) and
_sprites.Frame0(...) on every frame; cache the resulting texture by adding a
readonly/lazy field (e.g., _iconTexture) on the class and initialize it once
(preferably in the class constructor or an initialized lazy property) using
ParseSpriteSpecifier(_iconSprite) and _sprites.Frame0(...), then replace the
per-frame calls in DrawWorld with the cached _iconTexture to avoid repeated
parsing/loading.

}

private void DrawScreen(in OverlayDrawArgs args)
{
var screenHandle = args.ScreenHandle;

var query = _entManager.EntityQueryEnumerator<AndroidStressComponent>();
while (query.MoveNext(out var uid, out _))
{
if (_player.LocalEntity == uid)
continue;

var coords = _xforms.GetMapCoordinates(uid);
if (coords.MapId != args.MapId)
continue;

if (!_entManager.TryGetComponent(uid, out AndroidDiodeComponent? diode) || !diode.HasDiode)
continue;

var worldPos = _xforms.GetWorldPosition(uid);
var screenPos = _eye.WorldToScreen(worldPos);
var name = Identity.Name(uid, _entManager);
var textOffset = new Vector2(16f, -6f);
screenHandle.DrawString(_font, screenPos + textOffset, name, Color.Cyan);
Comment on lines +124 to +125
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Магические числа для textOffset.

new Vector2(16f, -6f) — неочевидные захардкоженные значения. Рекомендуется вынести в private static readonly или const-поле с понятным именем для читаемости и упрощения будущих правок.

🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/Android/Overlays/AndroidOverlay.cs` around
lines 124 - 125, Замените захардкоженные "магические" числа в new Vector2(16f,
-6f) на понятное именованное поле: добавьте в класс приватное статическое поле
вроде private static readonly Vector2 LabelTextOffset = new Vector2(16f, -6f) и
используйте его вместо textOffset при вызове screenHandle.DrawString(_font,
screenPos + textOffset, name, Color.Cyan); обновите все упоминания переменной
textOffset в методе/классе на новое поле для читаемости и простоты правок.

}
}
Comment on lines +104 to +127
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Дублирование логики запроса и фильтрации сущностей между DrawWorld и DrawScreen, а также двойное обращение к трансформу.

  1. Оба метода (DrawWorld и DrawScreen) выполняют идентичный EntityQueryEnumerator с одинаковыми фильтрами (skip local player, check map, check diode). На каждый кадр это два полных прохода по всем AndroidStressComponent-сущностям.

  2. В DrawScreen строка 114 вызывает GetMapCoordinates(uid) для проверки карты, а строка 121 — GetWorldPosition(uid) для рендеринга. Это два обращения к трансформу для одной и той же сущности. Можно использовать координаты из GetMapCoordinates, т.к. coords.Position уже содержит мировую позицию:

♻️ Устранение дублирующего вызова трансформа
-            var worldPos = _xforms.GetWorldPosition(uid);
-            var screenPos = _eye.WorldToScreen(worldPos);
+            var screenPos = _eye.WorldToScreen(coords.Position);

Для устранения дублирования запроса рассмотрите вынос общей фильтрации в отдельный метод или сбор отфильтрованных данных в единый проход.

🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/Android/Overlays/AndroidOverlay.cs` around
lines 104 - 127, Both DrawWorld and DrawScreen duplicate the same
EntityQueryEnumerator/filters over AndroidStressComponent and DrawScreen calls
the transform twice; refactor by extracting the shared filtering into a helper
(e.g., GetVisibleAndroidEntities or EnumerateFilteredAndroids) that performs the
EntityQueryEnumerator and returns the filtered uids and their MapCoordinates,
then update DrawWorld and DrawScreen to use that single helper to avoid two full
passes; in DrawScreen replace the GetWorldPosition(uid) call with
coords.Position from GetMapCoordinates to eliminate the second transform lookup
and use the returned coords for map check and rendering.


private static SpriteSpecifier ParseSpriteSpecifier(string value)
{
var split = value.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (split.Length >= 2)
{
var state = split[^1];
var path = string.Join('/', split[..^1]);
return new SpriteSpecifier.Rsi(new ResPath(path), state);
}

return new SpriteSpecifier.Texture(new ResPath(value));
}
}

Loading
Loading