[Wiki] Способности (#3553)

This commit is contained in:
Pok 2026-04-07 06:16:01 +03:00 • committed by GitHub
parent 438b5c8dda
commit 95c2be9fe2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 392 additions and 157 deletions

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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));

View file

@ -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 =>

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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");

View file

@ -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);