mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
[Wiki] Способности (#3553)
This commit is contained in:
parent
438b5c8dda
commit
95c2be9fe2
10 changed files with 392 additions and 157 deletions
|
|
@ -1,17 +1,23 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Corvax.GuideGenerator;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Client;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Corvax.ExportSprites;
|
||||
|
|
@ -29,6 +35,7 @@ public sealed class EntityScreenshotGenerator
|
|||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
|
@ -120,7 +127,7 @@ public sealed class EntityScreenshotGenerator
|
|||
var prototypes = _prototypeManager.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(proto =>
|
||||
!proto.Abstract &&
|
||||
proto.Components.ContainsKey("Sprite") &&
|
||||
HasExportableSprite(proto) &&
|
||||
EntityProjectHelper.MatchesAllowedIds(proto.ID, allowedIds))
|
||||
.OrderBy(proto => proto.ID)
|
||||
.ToList();
|
||||
|
|
@ -136,11 +143,18 @@ public sealed class EntityScreenshotGenerator
|
|||
|
||||
try
|
||||
{
|
||||
entity = _entityManager.SpawnEntity(proto.ID, new EntityCoordinates(previewGrid.Owner, default));
|
||||
if (proto.HasComponent<SpriteComponent>(_entityManager.ComponentFactory))
|
||||
{
|
||||
entity = _entityManager.SpawnEntity(proto.ID, new EntityCoordinates(previewGrid.Owner, default));
|
||||
|
||||
await WaitForEntityAppearanceAsync(entity);
|
||||
ApplyPrototypeAppearance(entity, proto);
|
||||
await WaitForEntityAppearanceAsync(entity, 1);
|
||||
await WaitForEntityAppearanceAsync(entity);
|
||||
ApplyPrototypeAppearance(entity, proto);
|
||||
await WaitForEntityAppearanceAsync(entity, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = SpawnIconEntity(proto);
|
||||
}
|
||||
|
||||
await _renderService.Export(entity, Direction.South, outputDir / $"{proto.ID}.png");
|
||||
exported++;
|
||||
|
|
@ -241,4 +255,128 @@ public sealed class EntityScreenshotGenerator
|
|||
if (solution.GetPrimaryReagentId() is { } reagent)
|
||||
appearanceSystem.SetData(entity, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearance);
|
||||
}
|
||||
|
||||
private bool HasExportableSprite(EntityPrototype prototype)
|
||||
{
|
||||
if (prototype.HasComponent<SpriteComponent>(_entityManager.ComponentFactory))
|
||||
return true;
|
||||
|
||||
return TryGetPrototypeIcon(prototype, out _);
|
||||
}
|
||||
|
||||
private EntityUid SpawnIconEntity(EntityPrototype prototype)
|
||||
{
|
||||
if (!TryGetPrototypeIcon(prototype, out var icon) || icon == null)
|
||||
throw new InvalidOperationException($"Prototype {prototype.ID} has no exportable icon.");
|
||||
|
||||
var entity = _entityManager.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
var sprite = _entityManager.EnsureComponent<SpriteComponent>(entity);
|
||||
var spriteSystem = _entitySystemManager.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
spriteSystem.AddBlankLayer((entity, sprite), 0);
|
||||
if (icon is SpriteSpecifier.EntityPrototype entityIcon)
|
||||
spriteSystem.LayerSetTexture((entity, sprite), 0, spriteSystem.Frame0(new SpriteSpecifier.EntityPrototype(entityIcon.EntityPrototypeId)));
|
||||
else
|
||||
spriteSystem.LayerSetSprite((entity, sprite), 0, icon);
|
||||
sprite.LayerSetShader(0, "unshaded");
|
||||
spriteSystem.LayerSetVisible((entity, sprite), 0, true);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private bool TryGetPrototypeIcon(EntityPrototype prototype, out SpriteSpecifier? icon)
|
||||
{
|
||||
icon = null;
|
||||
|
||||
foreach (var (_, entry) in prototype.Components)
|
||||
{
|
||||
if (TryExtractSpriteSpecifier(entry.Component.GetType(), entry.Mapping, out icon))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryExtractSpriteSpecifier(Type? expectedType, DataNode? node, out SpriteSpecifier? icon)
|
||||
{
|
||||
icon = null;
|
||||
|
||||
if (node == null)
|
||||
return false;
|
||||
|
||||
if (expectedType != null &&
|
||||
typeof(SpriteSpecifier).IsAssignableFrom(expectedType) &&
|
||||
TryParseSpriteSpecifier(node, out icon))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node is MappingDataNode mapping)
|
||||
{
|
||||
foreach (var (key, child) in mapping.Children)
|
||||
{
|
||||
Type? childType = null;
|
||||
|
||||
if (expectedType != null &&
|
||||
_serialization.TryGetVariableType(expectedType, key, out var resolvedType))
|
||||
{
|
||||
childType = resolvedType;
|
||||
}
|
||||
|
||||
if (TryExtractSpriteSpecifier(childType, child, out icon))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node is SequenceDataNode sequence)
|
||||
{
|
||||
var elementType = GetSequenceElementType(expectedType);
|
||||
foreach (var child in sequence.Sequence)
|
||||
{
|
||||
if (TryExtractSpriteSpecifier(elementType, child, out icon))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Type? GetSequenceElementType(Type? type)
|
||||
{
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
if (type.IsArray)
|
||||
return type.GetElementType();
|
||||
|
||||
var genericArguments = type.GenericTypeArguments;
|
||||
if (genericArguments.Length == 1)
|
||||
return genericArguments[0];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryParseSpriteSpecifier(DataNode node, out SpriteSpecifier? icon)
|
||||
{
|
||||
icon = null;
|
||||
|
||||
try
|
||||
{
|
||||
icon = _serialization.Read<SpriteSpecifier>(node, notNullableOverride: true);
|
||||
if (icon == SpriteSpecifier.Invalid)
|
||||
{
|
||||
icon = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
icon = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
|
@ -27,6 +27,7 @@ public sealed class EntityScreenshotRenderService
|
|||
private EntityScreenshotRenderControl? _control;
|
||||
private bool _initialized;
|
||||
private readonly Dictionary<(ResPath Path, string State), Image<Rgba32>> _rsiStateImageCache = new();
|
||||
private readonly Dictionary<Texture, Image<Rgba32>> _textureImageCache = new();
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public void Initialize()
|
||||
|
|
@ -48,6 +49,13 @@ public sealed class EntityScreenshotRenderService
|
|||
|
||||
_rsiStateImageCache.Clear();
|
||||
|
||||
foreach (var image in _textureImageCache.Values)
|
||||
{
|
||||
image.Dispose();
|
||||
}
|
||||
|
||||
_textureImageCache.Clear();
|
||||
|
||||
if (_control == null)
|
||||
return;
|
||||
|
||||
|
|
@ -360,7 +368,7 @@ public sealed class EntityScreenshotRenderService
|
|||
|
||||
private static int ToDelayMilliseconds(float seconds)
|
||||
{
|
||||
return Math.Max(1, (int)MathF.Round(seconds * 1000f));
|
||||
return Math.Max(1, (int) MathF.Round(seconds * 1000f));
|
||||
}
|
||||
|
||||
private void WriteAnimationMetadata(ResPath animationDir, IReadOnlyList<AnimationFrameInfo> animationFrames)
|
||||
|
|
@ -388,7 +396,9 @@ public sealed class EntityScreenshotRenderService
|
|||
return false;
|
||||
|
||||
// Keep the old render-target path for uncommon transformed sprites.
|
||||
if (spriteComp.Scale != Vector2.One || spriteComp.Rotation != Angle.Zero)
|
||||
if (spriteComp.Scale != Vector2.One ||
|
||||
spriteComp.Rotation != Angle.Zero ||
|
||||
spriteComp.EnableDirectionOverride)
|
||||
return false;
|
||||
|
||||
var size = renderBounds.Size;
|
||||
|
|
@ -407,7 +417,7 @@ public sealed class EntityScreenshotRenderService
|
|||
return false;
|
||||
|
||||
if (!TryGetLayerImage(spriteLayer, direction, out var sourceImage, out var sourceRect))
|
||||
continue;
|
||||
return false;
|
||||
|
||||
var drawColor = spriteComp.Color * spriteLayer.Color;
|
||||
var drawOffset = ToPixelOffset(spriteComp.Offset + spriteLayer.Offset) - renderBounds.Min;
|
||||
|
|
@ -429,7 +439,7 @@ public sealed class EntityScreenshotRenderService
|
|||
|
||||
private static void BlitImage(
|
||||
Image<Rgba32> sourceImage,
|
||||
Rectangle sourceRect,
|
||||
PixelRect sourceRect,
|
||||
Color modulation,
|
||||
Span<Rgba32> destination,
|
||||
Vector2i destinationSize,
|
||||
|
|
@ -519,14 +529,17 @@ public sealed class EntityScreenshotRenderService
|
|||
SpriteComponent.Layer layer,
|
||||
Direction direction,
|
||||
out Image<Rgba32> image,
|
||||
out Rectangle sourceRect)
|
||||
out PixelRect sourceRect)
|
||||
{
|
||||
image = default!;
|
||||
sourceRect = default;
|
||||
|
||||
// Raw texture layers need a separate cache path. Use render target fallback for them.
|
||||
if (layer.Texture != null)
|
||||
return false;
|
||||
{
|
||||
image = GetTextureImage(layer.Texture);
|
||||
sourceRect = new PixelRect(0, 0, image.Width, image.Height);
|
||||
return true;
|
||||
}
|
||||
|
||||
var rsi = layer.ActualRsi;
|
||||
var stateId = ((ISpriteLayer) layer).RsiState;
|
||||
|
|
@ -569,10 +582,33 @@ public sealed class EntityScreenshotRenderService
|
|||
var target = (int) rsiDirection * framesPerDirection + frame;
|
||||
var targetY = target / statesX;
|
||||
var targetX = target % statesX;
|
||||
sourceRect = new Rectangle(targetX * frameWidth, targetY * frameHeight, frameWidth, frameHeight);
|
||||
sourceRect = new PixelRect(targetX * frameWidth, targetY * frameHeight, frameWidth, frameHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Image<Rgba32> GetTextureImage(Texture texture)
|
||||
{
|
||||
if (_textureImageCache.TryGetValue(texture, out var cached))
|
||||
return cached;
|
||||
|
||||
var image = new Image<Rgba32>(texture.Width, texture.Height);
|
||||
var pixels = image.GetPixelSpan();
|
||||
|
||||
for (var y = 0; y < texture.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < texture.Width; x++)
|
||||
{
|
||||
var color = texture.GetPixel(x, y);
|
||||
pixels[y * texture.Width + x] = new Rgba32(color.RByte, color.GByte, color.BByte, color.AByte);
|
||||
}
|
||||
}
|
||||
|
||||
_textureImageCache[texture] = image;
|
||||
return image;
|
||||
}
|
||||
|
||||
private readonly record struct PixelRect(int Left, int Top, int Width, int Height);
|
||||
|
||||
private sealed class EntityScreenshotRenderControl : Control
|
||||
{
|
||||
private static readonly Color ExportBackgroundColor = new(128, 128, 128, 0);
|
||||
|
|
|
|||
|
|
@ -1,117 +0,0 @@
|
|||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public sealed class EntityEntry
|
||||
{
|
||||
private static string[]? GetRootParents(EntityPrototype proto, IPrototypeManager prototypeManager)
|
||||
{
|
||||
if (proto.Parents is not { Length: > 0 })
|
||||
return null;
|
||||
|
||||
var roots = new HashSet<string>(StringComparer.Ordinal);
|
||||
var visited = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
static IEnumerable<string> GetParentIds(MappingDataNode mapping)
|
||||
{
|
||||
if (mapping.TryGet("parent", out ValueDataNode? parentValue))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(parentValue.Value))
|
||||
yield return parentValue.Value;
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!mapping.TryGet("parent", out SequenceDataNode? parentSequence))
|
||||
yield break;
|
||||
|
||||
foreach (var parentNode in parentSequence)
|
||||
{
|
||||
if (parentNode is not ValueDataNode valueNode)
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(valueNode.Value))
|
||||
yield return valueNode.Value;
|
||||
}
|
||||
}
|
||||
|
||||
void Visit(string id)
|
||||
{
|
||||
if (!visited.Add(id))
|
||||
return;
|
||||
|
||||
if (!YAMLEntry.TryGetRawMapping(prototypeManager, typeof(EntityPrototype), id, out var mapping) ||
|
||||
mapping == null)
|
||||
{
|
||||
roots.Add(id);
|
||||
return;
|
||||
}
|
||||
|
||||
var parents = GetParentIds(mapping).ToArray();
|
||||
if (parents.Length == 0)
|
||||
{
|
||||
roots.Add(id);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
Visit(parent);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var parent in proto.Parents)
|
||||
{
|
||||
Visit(parent);
|
||||
}
|
||||
|
||||
return roots.Count > 0 ? roots.OrderBy(x => x, StringComparer.Ordinal).ToArray() : null;
|
||||
}
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
[JsonPropertyName("desc")]
|
||||
public string Description { get; }
|
||||
|
||||
[JsonPropertyName("suffix")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Suffix { get; }
|
||||
|
||||
[JsonPropertyName("label")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Label { get; }
|
||||
|
||||
[JsonPropertyName("parents")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string[]? Parents { get; }
|
||||
|
||||
public EntityEntry(EntityPrototype proto)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
|
||||
Id = proto.ID;
|
||||
Name = TextTools.CapitalizeString(TextTools.GetDisplayName(proto, prototypeManager, loc));
|
||||
Description = proto.Description;
|
||||
Suffix = string.IsNullOrWhiteSpace(proto.EditorSuffix) ? null : proto.EditorSuffix;
|
||||
Parents = GetRootParents(proto, prototypeManager);
|
||||
|
||||
Label = proto.Components.Values
|
||||
.Select(x => x.Component)
|
||||
.OfType<LabelComponent>()
|
||||
.Select(lc => lc.CurrentLabel)
|
||||
.Where(label => !string.IsNullOrEmpty(label))
|
||||
.Select(label => Loc.GetString(label!))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,53 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public sealed class EntityJsonGenerator
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
[JsonPropertyName("desc")]
|
||||
public string Description { get; }
|
||||
|
||||
[JsonPropertyName("suffix")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Suffix { get; }
|
||||
|
||||
[JsonPropertyName("label")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Label { get; }
|
||||
|
||||
public EntityJsonGenerator(EntityPrototype proto)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
|
||||
Id = proto.ID;
|
||||
Name = TextTools.CapitalizeString(TextTools.GetDisplayName(proto, prototypeManager, loc));
|
||||
Description = proto.Description;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(proto.EditorSuffix))
|
||||
Suffix = TextTools.GetEditorSuffix(proto.EditorSuffix, EntityNameDuplicatesJsonGenerator.IgnoredSuffixTokens, TextTools.NormalizeSuffixToken);
|
||||
|
||||
Label = proto.Components.Values
|
||||
.Select(x => x.Component)
|
||||
.OfType<LabelComponent>()
|
||||
.Select(lc => lc.CurrentLabel)
|
||||
.Where(label => !string.IsNullOrEmpty(label))
|
||||
.Select(label => Loc.GetString(label!))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public static void PublishJson(StreamWriter file)
|
||||
{
|
||||
var prototype = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
|
@ -14,12 +55,13 @@ public sealed class EntityJsonGenerator
|
|||
prototype
|
||||
.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(x => !x.Abstract)
|
||||
.Select(x => new EntityEntry(x))
|
||||
.Select(x => new EntityJsonGenerator(x))
|
||||
.ToDictionary(x => x.Id, x => x);
|
||||
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
file.Write(JsonSerializer.Serialize(prototypes, serializeOptions));
|
||||
|
|
|
|||
|
|
@ -2,19 +2,28 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Corvax.GuideGenerator;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public static class EntityNameDuplicatesJsonGenerator
|
||||
{
|
||||
private const string AbilitySuffix = "(способность)";
|
||||
|
||||
public static readonly string[] AllowedNameComponents =
|
||||
[
|
||||
"Fixtures",
|
||||
"Physics",
|
||||
"Action"
|
||||
];
|
||||
|
||||
// Suffix parts that should be ignored when building display names.
|
||||
private static readonly HashSet<string> IgnoredSuffixTokens = new(StringComparer.OrdinalIgnoreCase)
|
||||
public static readonly HashSet<string> IgnoredSuffixTokens = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"DO NOT MAP",
|
||||
"do not map",
|
||||
"не маппить",
|
||||
};
|
||||
|
||||
|
|
@ -31,10 +40,11 @@ public static class EntityNameDuplicatesJsonGenerator
|
|||
|
||||
public static bool MatchesEntityNameFilter(EntityPrototype proto, IReadOnlySet<string> allowedIds)
|
||||
{
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
var hasAllowedComponent = AllowedNameComponents.Any(proto.Components.ContainsKey);
|
||||
|
||||
return !proto.Abstract &&
|
||||
proto.TryGetComponent<FixturesComponent>(out _, compFactory) &&
|
||||
EntityProjectHelper.MatchesAllowedIds(proto.ID, allowedIds);
|
||||
hasAllowedComponent &&
|
||||
EntityProjectHelper.MatchesAllowedIds(proto.ID, allowedIds);
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<string>> GetDuplicatesName(
|
||||
|
|
@ -42,6 +52,7 @@ public static class EntityNameDuplicatesJsonGenerator
|
|||
bool duplicatesOnly)
|
||||
{
|
||||
var loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
var allowedIds = EntityProjectGenerator.GetProjectEntityIds();
|
||||
return prototypeManager
|
||||
.EnumeratePrototypes<EntityPrototype>()
|
||||
|
|
@ -50,24 +61,17 @@ public static class EntityNameDuplicatesJsonGenerator
|
|||
{
|
||||
var name = TextTools.CapitalizeString(TextTools.GetDisplayName(p, prototypeManager, loc));
|
||||
|
||||
if (p.TryGetComponent<ActionComponent>(out _, compFactory))
|
||||
name = $"{name} {AbilitySuffix}";
|
||||
|
||||
var label = GetLabel(p);
|
||||
|
||||
var rawSuffix = p.EditorSuffix;
|
||||
var suffix = string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(rawSuffix))
|
||||
{
|
||||
var parts = rawSuffix
|
||||
.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(part => part.Trim())
|
||||
.Where(part => !IgnoredSuffixTokens.Contains(part))
|
||||
.ToArray();
|
||||
|
||||
if (parts.Length > 0)
|
||||
suffix = string.Join(", ", parts).ToLowerInvariant();
|
||||
}
|
||||
var suffix = TextTools.GetEditorSuffix(rawSuffix, IgnoredSuffixTokens, TextTools.NormalizeSuffixToken);
|
||||
|
||||
return (Name: name, Label: label, Suffix: suffix);
|
||||
})
|
||||
.Where(g => !string.IsNullOrWhiteSpace(g.Key.Name))
|
||||
.Where(g => !duplicatesOnly || g.Count() > 1)
|
||||
.ToDictionary(
|
||||
g =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Encodings.Web;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public sealed class EntityParentJsonGenerator
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
[JsonPropertyName("parents")]
|
||||
public string[] Parents { get; }
|
||||
|
||||
public EntityParentJsonGenerator(EntityPrototype proto)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
Id = proto.ID;
|
||||
Parents = GetParents(proto, prototypeManager);
|
||||
}
|
||||
|
||||
public static string[] GetParents(EntityPrototype proto, IPrototypeManager prototypeManager)
|
||||
{
|
||||
var parents = new HashSet<string>(StringComparer.Ordinal);
|
||||
var visited = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
void Visit(string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id) || !visited.Add(id))
|
||||
return;
|
||||
|
||||
parents.Add(id);
|
||||
|
||||
if (!YAMLEntry.TryGetRawMapping(prototypeManager, typeof(EntityPrototype), id, out var mapping) ||
|
||||
mapping == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var parent in GetParentIds(mapping))
|
||||
{
|
||||
Visit(parent);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var parent in proto.Parents ?? [])
|
||||
{
|
||||
Visit(parent);
|
||||
}
|
||||
|
||||
return parents.OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetParentIds(MappingDataNode mapping)
|
||||
{
|
||||
if (mapping.TryGet("parent", out ValueDataNode? parentValue))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(parentValue.Value))
|
||||
yield return parentValue.Value;
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!mapping.TryGet("parent", out SequenceDataNode? parentSequence))
|
||||
yield break;
|
||||
|
||||
foreach (var parentNode in parentSequence)
|
||||
{
|
||||
if (parentNode is not ValueDataNode valueNode)
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(valueNode.Value))
|
||||
yield return valueNode.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void PublishJson(StreamWriter file)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var prototypes = prototypeManager
|
||||
.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(x => !x.Abstract)
|
||||
.Select(x => new EntityParentJsonGenerator(x))
|
||||
.ToDictionary(x => x.Id, x => x.Parents);
|
||||
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
file.Write(JsonSerializer.Serialize(prototypes, serializeOptions));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,7 @@ using System.Reflection;
|
|||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
|
|
@ -154,8 +152,9 @@ public static class PrototypeJsonGenerator
|
|||
}
|
||||
}
|
||||
|
||||
return type.GetCustomAttributes(inherit: true).Any(attr =>
|
||||
return type.GetCustomAttributes(inherit: true)
|
||||
.Any(attr =>
|
||||
attr.GetType().Name is nameof(DataDefinitionAttribute) or nameof(SerializableAttribute))
|
||||
&& HasUnsafeSerializedDataField(type, visited);
|
||||
&& HasUnsafeSerializedDataField(type, visited);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
|
@ -60,4 +62,33 @@ public sealed class TextTools
|
|||
|
||||
return proto.Name;
|
||||
}
|
||||
|
||||
private static readonly Regex SuffixTokenEdgeGarbageRegex =
|
||||
new(@"^[\p{P}\p{S}\s]+|[\p{P}\p{S}\s]+$", RegexOptions.Compiled);
|
||||
|
||||
public static string NormalizeSuffixToken(string token)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(token)
|
||||
? string.Empty
|
||||
: SuffixTokenEdgeGarbageRegex.Replace(token, string.Empty);
|
||||
}
|
||||
|
||||
public static string GetEditorSuffix(
|
||||
string? editorSuffix,
|
||||
IReadOnlySet<string> ignoredTokens,
|
||||
Func<string, string> normalizeToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(editorSuffix))
|
||||
return string.Empty;
|
||||
|
||||
var parts = editorSuffix
|
||||
.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(part => part.Trim())
|
||||
.Where(part => !ignoredTokens.Contains(normalizeToken(part)))
|
||||
.ToArray();
|
||||
|
||||
return parts.Length > 0
|
||||
? string.Join(", ", parts).ToLowerInvariant()
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public static class YAMLEntry
|
|||
{
|
||||
mapping = null;
|
||||
|
||||
if (KindsField?.GetValue(proto) is not object kinds)
|
||||
if (KindsField?.GetValue(proto) is not { } kinds)
|
||||
return false;
|
||||
|
||||
var itemProperty = kinds.GetType().GetProperty("Item");
|
||||
|
|
|
|||
|
|
@ -161,7 +161,8 @@ namespace Content.Server.Entry
|
|||
write(file);
|
||||
file.Flush();
|
||||
}
|
||||
WriteFile("entity_" + dest, EntityJsonGenerator.PublishJson);
|
||||
WriteFile("entity_prototypes.json", EntityJsonGenerator.PublishJson);
|
||||
WriteFile("entity_parent.json", EntityParentJsonGenerator.PublishJson);
|
||||
WriteFile("loc.json", LocJsonGenerator.PublishJson);
|
||||
WriteFile("meta_license.json", MetaLicenseGenerator.PublishJson);
|
||||
WriteFile("prototype.json", PrototypeListGenerator.PublishJson);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue