mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-14 23:14:45 +01:00
[Wiki] Подгрузка ВСЕГО на вики (#3487)
This commit is contained in:
100
.github/workflows/update-wiki.yml
vendored
100
.github/workflows/update-wiki.yml
vendored
@@ -6,15 +6,11 @@ on:
|
||||
branches: [ master, jsondump ]
|
||||
paths:
|
||||
- '.github/workflows/update-wiki.yml'
|
||||
- 'Content.Shared/Chemistry/**.cs'
|
||||
- 'Content.Server/Chemistry/**.cs'
|
||||
- 'Content.Server/GuideGenerator/**.cs'
|
||||
- 'Content.Server/Corvax/GuideGenerator/**.cs'
|
||||
- 'Resources/Prototypes/Reagents/**.yml'
|
||||
- 'Resources/Prototypes/Chemistry/**.yml'
|
||||
- 'Resources/Prototypes/Recipes/Reactions/**.yml'
|
||||
- 'Content.Shared/'
|
||||
- 'Content.Server/'
|
||||
- 'Content.Clietn/'
|
||||
- 'Resources/'
|
||||
- 'RobustToolbox/'
|
||||
- 'Resources/Locale/**.ftl'
|
||||
|
||||
jobs:
|
||||
update-wiki:
|
||||
@@ -52,52 +48,50 @@ jobs:
|
||||
run: dotnet ./bin/Content.Server/Content.Server.dll --cvar autogen.destination_file=prototypes.json
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload chem_prototypes.json to wiki
|
||||
uses: jtmullen/mediawiki-edit-action@v0.1.1
|
||||
with:
|
||||
wiki_text_file: ./bin/Content.Server/data/chem_prototypes.json
|
||||
edit_summary: Update chem_prototypes.json via GitHub Actions
|
||||
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/chem_prototypes.json"
|
||||
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
|
||||
username: ${{ secrets.WIKI_BOT_USER }}
|
||||
password: ${{ secrets.WIKI_BOT_PASS }}
|
||||
# Проходит по всем JSON-файлам в директории BASE и загружает каждый файл как страницу в MediaWiki.
|
||||
# Имя страницы формируется из относительного пути к файлу.
|
||||
- name: Upload JSON files to wiki
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
- name: Upload react_prototypes.json to wiki
|
||||
uses: jtmullen/mediawiki-edit-action@v0.1.1
|
||||
with:
|
||||
wiki_text_file: ./bin/Content.Server/data/react_prototypes.json
|
||||
edit_summary: Update react_prototypes.json via GitHub Actions
|
||||
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/react_prototypes.json"
|
||||
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
|
||||
username: ${{ secrets.WIKI_BOT_USER }}
|
||||
password: ${{ secrets.WIKI_BOT_PASS }}
|
||||
BASE="./bin/Content.Server/data"
|
||||
ROOT="${{ secrets.WIKI_PAGE_ROOT }}"
|
||||
API="${{ secrets.WIKI_ROOT_URL }}/api.php"
|
||||
USER="${{ secrets.WIKI_BOT_USER }}"
|
||||
PASS="${{ secrets.WIKI_BOT_PASS }}"
|
||||
|
||||
- name: Upload entity_prototypes.json to wiki
|
||||
uses: jtmullen/mediawiki-edit-action@v0.1.1
|
||||
with:
|
||||
wiki_text_file: ./bin/Content.Server/data/entity_prototypes.json
|
||||
edit_summary: Update entity_prototypes.json via GitHub Actions
|
||||
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/entity_prototypes.json"
|
||||
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
|
||||
username: ${{ secrets.WIKI_BOT_USER }}
|
||||
password: ${{ secrets.WIKI_BOT_PASS }}
|
||||
API="$(printf "%s" "$API" | tr -d '\r\n' | sed 's/[[:space:]]*$//')"
|
||||
USER="$(printf "%s" "$USER" | tr -d '\r\n')"
|
||||
PASS="$(printf "%s" "$PASS" | tr -d '\r\n')"
|
||||
ROOT="$(printf "%s" "$ROOT" | tr -d '\r\n' | sed 's/[[:space:]]*$//')"
|
||||
|
||||
- name: Upload mealrecipes_prototypes.json to wiki
|
||||
uses: jtmullen/mediawiki-edit-action@v0.1.1
|
||||
with:
|
||||
wiki_text_file: ./bin/Content.Server/data/mealrecipes_prototypes.json
|
||||
edit_summary: Update mealrecipes_prototypes.json via GitHub Actions
|
||||
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/mealrecipes_prototypes.json"
|
||||
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
|
||||
username: ${{ secrets.WIKI_BOT_USER }}
|
||||
password: ${{ secrets.WIKI_BOT_PASS }}
|
||||
cookiejar="$(mktemp)"
|
||||
trap 'rm -f "$cookiejar"' EXIT
|
||||
|
||||
- name: Upload loc.json to wiki
|
||||
uses: jtmullen/mediawiki-edit-action@v0.1.1
|
||||
with:
|
||||
wiki_text_file: ./bin/Content.Server/data/loc.json
|
||||
edit_summary: Update loc.json via GitHub Actions
|
||||
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/loc.json"
|
||||
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
|
||||
username: ${{ secrets.WIKI_BOT_USER }}
|
||||
password: ${{ secrets.WIKI_BOT_PASS }}
|
||||
login_token=$(curl -sS -c "$cookiejar" --data "action=query&meta=tokens&type=login&format=json" "$API" | jq -r '.query.tokens.logintoken')
|
||||
curl -sS -c "$cookiejar" -b "$cookiejar" \
|
||||
--data-urlencode "action=login" \
|
||||
--data-urlencode "lgname=$USER" \
|
||||
--data-urlencode "lgpassword=$PASS" \
|
||||
--data-urlencode "lgtoken=$login_token" \
|
||||
--data-urlencode "format=json" \
|
||||
"$API" > /dev/null
|
||||
|
||||
find "$BASE" -type f -name '*.json' | while IFS= read -r file; do
|
||||
rel="${file#$BASE/}"
|
||||
rel="$(printf "%s" "$rel" | tr -d '\r\n' | sed 's/:/_/g')"
|
||||
page="$ROOT/$rel"
|
||||
echo "Uploading $rel → $page"
|
||||
|
||||
token=$(curl -sS -b "$cookiejar" --data "action=query&meta=tokens&format=json" "$API" | jq -r '.query.tokens.csrftoken')
|
||||
|
||||
curl -sS -b "$cookiejar" \
|
||||
--data-urlencode "action=edit" \
|
||||
--data-urlencode "title=$page" \
|
||||
--data-urlencode "summary=Update $rel via GitHub Actions" \
|
||||
--data-urlencode "text@${file}" \
|
||||
--data-urlencode "token=$token" \
|
||||
--data-urlencode "format=json" \
|
||||
"$API" | jq -r '.'
|
||||
done
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public static class ComponentJsonGenerator
|
||||
{
|
||||
public static void PublishAll(IResourceManager res, ResPath destRoot)
|
||||
{
|
||||
var proto = IoCManager.Resolve<IPrototypeManager>();
|
||||
var ser = IoCManager.Resolve<ISerializationManager>();
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
// Map: component name -> (entity id -> component fields)
|
||||
var output = new Dictionary<string, Dictionary<string, object?>>();
|
||||
|
||||
foreach (var p in proto.EnumeratePrototypes(typeof(EntityPrototype)))
|
||||
{
|
||||
if (p is not EntityPrototype entProto)
|
||||
continue;
|
||||
|
||||
foreach (var (compName, entry) in entProto.Components)
|
||||
{
|
||||
var node = ser.WriteValueAs<MappingDataNode>(entry.Component.GetType(), entry.Component);
|
||||
var compFields = FieldEntry.DataNodeToObject(node);
|
||||
|
||||
if (!output.TryGetValue(compName, out var map))
|
||||
{
|
||||
map = new Dictionary<string, object?>();
|
||||
output[compName] = map;
|
||||
}
|
||||
|
||||
map[entProto.ID] = compFields;
|
||||
}
|
||||
}
|
||||
|
||||
if (output.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var (compName, map) in output)
|
||||
{
|
||||
// Determine default field for this component.
|
||||
object? defaultObj = null;
|
||||
if (compFactory.TryGetRegistration(compName, out var registration))
|
||||
{
|
||||
var uid = entMan.CreateEntityUninitialized(null);
|
||||
try
|
||||
{
|
||||
var compInstance = compFactory.GetComponent(registration.Type);
|
||||
FieldEntry.EnsureFieldsCollectionsInitialized(compInstance);
|
||||
entMan.AddComponent(uid, compInstance);
|
||||
var node = ser.WriteValueAs<MappingDataNode>(compInstance.GetType(), compInstance, true);
|
||||
defaultObj = FieldEntry.DataNodeToObject(node);
|
||||
}
|
||||
catch
|
||||
{
|
||||
defaultObj = new Dictionary<string, object?>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
entMan.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
||||
var outObj = new Dictionary<string, object?>
|
||||
{
|
||||
["default"] = defaultObj,
|
||||
["id"] = map
|
||||
};
|
||||
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
res.UserData.CreateDir(destRoot);
|
||||
var fileName = PrototypeUtility.CalculatePrototypeName(compName) + ".json";
|
||||
var file = res.UserData.OpenWriteText(destRoot / fileName);
|
||||
file.Write(JsonSerializer.Serialize(outObj, serializeOptions));
|
||||
file.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public static class ComponentListGenerator
|
||||
{
|
||||
public static void PublishJson(StreamWriter file)
|
||||
{
|
||||
var proto = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
// Map: entity id -> list of component names.
|
||||
var output = new Dictionary<string, List<string>>();
|
||||
|
||||
foreach (var p in proto.EnumeratePrototypes(typeof(EntityPrototype)))
|
||||
{
|
||||
if (p is not EntityPrototype entityProto)
|
||||
continue;
|
||||
|
||||
var componentNames = new List<string>();
|
||||
foreach (var (compName, _) in entityProto.Components)
|
||||
{
|
||||
componentNames.Add(compName);
|
||||
}
|
||||
|
||||
if (componentNames.Count > 0)
|
||||
output[entityProto.ID] = componentNames;
|
||||
}
|
||||
|
||||
if (output.Count == 0)
|
||||
return;
|
||||
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
file.Write(JsonSerializer.Serialize(output, serializeOptions));
|
||||
}
|
||||
}
|
||||
220
Content.Server/Corvax/GuideGenerator/FieldEntry.cs
Normal file
220
Content.Server/Corvax/GuideGenerator/FieldEntry.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public static class FieldEntry
|
||||
{
|
||||
public static object? DataNodeToObject(DataNode node)
|
||||
{
|
||||
if (node is MappingDataNode mapping)
|
||||
{
|
||||
var dict = new Dictionary<string, object?>();
|
||||
|
||||
foreach (var kv in mapping)
|
||||
{
|
||||
dict[kv.Key] = DataNodeToObject(kv.Value);
|
||||
}
|
||||
|
||||
if (node.Tag != null)
|
||||
{
|
||||
var wrapped = new Dictionary<string, object?>
|
||||
{
|
||||
[node.Tag] = dict
|
||||
};
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
if (node is SequenceDataNode sequence)
|
||||
{
|
||||
var list = new List<object?>();
|
||||
foreach (var item in sequence)
|
||||
{
|
||||
list.Add(DataNodeToObject(item));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
if (node is ValueDataNode value)
|
||||
{
|
||||
if (value.IsNull)
|
||||
return null;
|
||||
|
||||
var raw = value.Value;
|
||||
|
||||
if (bool.TryParse(raw, out var boolRes))
|
||||
return boolRes;
|
||||
|
||||
if (int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intRes))
|
||||
return intRes;
|
||||
|
||||
// Accept decimals only in a strict single-dot format.
|
||||
if (Regex.IsMatch(raw, @"^[+-]?\d+\.\d+$") &&
|
||||
double.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleRes))
|
||||
return doubleRes;
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
return node.ToString();
|
||||
}
|
||||
|
||||
public static void EnsureFieldsCollectionsInitialized(object instance)
|
||||
{
|
||||
var type = instance.GetType();
|
||||
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
// Fields
|
||||
foreach (var field in type.GetFields(flags))
|
||||
{
|
||||
if (field.IsInitOnly)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var value = field.GetValue(instance);
|
||||
if (value != null)
|
||||
continue;
|
||||
|
||||
var ft = field.FieldType;
|
||||
if (ft == typeof(string))
|
||||
{
|
||||
field.SetValue(instance, string.Empty);
|
||||
}
|
||||
else if ((typeof(IDictionary).IsAssignableFrom(ft) || typeof(IList).IsAssignableFrom(ft) || ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(List<>) || ft.IsArray) && ft is { IsAbstract: false, IsInterface: false })
|
||||
{
|
||||
object? created = null;
|
||||
if (ft.IsArray)
|
||||
{
|
||||
var elemType = ft.GetElementType();
|
||||
if (elemType != null)
|
||||
created = Array.CreateInstance(elemType, 0);
|
||||
}
|
||||
else if (ft.GetConstructor(Type.EmptyTypes) != null)
|
||||
{
|
||||
created = Activator.CreateInstance(ft);
|
||||
}
|
||||
|
||||
if (created != null)
|
||||
field.SetValue(instance, created);
|
||||
}
|
||||
else if (ft.IsClass && ft != typeof(string) && !ft.IsAbstract)
|
||||
{
|
||||
var created = Activator.CreateInstance(ft, true);
|
||||
if (created != null)
|
||||
{
|
||||
field.SetValue(instance, created);
|
||||
EnsureFieldsCollectionsInitialized(created);
|
||||
}
|
||||
}
|
||||
else if ((ft.IsAbstract || ft.IsInterface) && ft != typeof(string))
|
||||
{
|
||||
var concrete = FindConcreteAssignableType(ft);
|
||||
if (concrete != null)
|
||||
{
|
||||
var created = Activator.CreateInstance(concrete);
|
||||
if (created != null)
|
||||
{
|
||||
field.SetValue(instance, created);
|
||||
EnsureFieldsCollectionsInitialized(created);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Properties
|
||||
foreach (var prop in type.GetProperties(flags))
|
||||
{
|
||||
if (!prop.CanWrite || prop.GetIndexParameters().Length != 0)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var value = prop.GetValue(instance);
|
||||
if (value != null)
|
||||
continue;
|
||||
|
||||
var pt = prop.PropertyType;
|
||||
if ((typeof(IDictionary).IsAssignableFrom(pt) || typeof(IList).IsAssignableFrom(pt) || pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(List<>) || pt.IsArray) && pt is { IsAbstract: false, IsInterface: false })
|
||||
{
|
||||
object? created = null;
|
||||
if (pt.IsArray)
|
||||
{
|
||||
var elemType = pt.GetElementType();
|
||||
if (elemType != null)
|
||||
created = Array.CreateInstance(elemType, 0);
|
||||
}
|
||||
else if (pt.GetConstructor(Type.EmptyTypes) != null)
|
||||
{
|
||||
created = Activator.CreateInstance(pt);
|
||||
}
|
||||
|
||||
if (created != null)
|
||||
prop.SetValue(instance, created);
|
||||
}
|
||||
else if (pt.IsClass && pt != typeof(string) && !pt.IsAbstract)
|
||||
{
|
||||
var created = Activator.CreateInstance(pt, true);
|
||||
if (created != null)
|
||||
{
|
||||
prop.SetValue(instance, created);
|
||||
EnsureFieldsCollectionsInitialized(created);
|
||||
}
|
||||
}
|
||||
else if ((pt.IsAbstract || pt.IsInterface) && pt != typeof(string))
|
||||
{
|
||||
var concrete = FindConcreteAssignableType(pt);
|
||||
if (concrete != null)
|
||||
{
|
||||
var created = Activator.CreateInstance(concrete);
|
||||
if (created != null)
|
||||
{
|
||||
prop.SetValue(instance, created);
|
||||
EnsureFieldsCollectionsInitialized(created);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Type? FindConcreteAssignableType(Type target)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
var types = asm.GetTypes();
|
||||
|
||||
foreach (var t in types)
|
||||
{
|
||||
if (t.IsAbstract || t.IsInterface)
|
||||
continue;
|
||||
if (!target.IsAssignableFrom(t))
|
||||
continue;
|
||||
if (t.GetConstructor(Type.EmptyTypes) == null)
|
||||
continue;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
53
Content.Server/Corvax/GuideGenerator/MetaLicenseGenerator.cs
Normal file
53
Content.Server/Corvax/GuideGenerator/MetaLicenseGenerator.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public static class MetaLicenseGenerator
|
||||
{
|
||||
public static void PublishJson(StreamWriter file)
|
||||
{
|
||||
var workingDir = Directory.GetCurrentDirectory();
|
||||
var resourcesRoot = Path.Combine(workingDir, "Resources");
|
||||
if (!Directory.Exists(resourcesRoot))
|
||||
return;
|
||||
|
||||
var output = new Dictionary<string, Dictionary<string, string>>();
|
||||
|
||||
foreach (var metaPath in Directory.EnumerateFiles(resourcesRoot, "meta.json", SearchOption.AllDirectories))
|
||||
{
|
||||
var json = File.ReadAllText(metaPath);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
var license = root.TryGetProperty("license", out var licEl) && licEl.ValueKind == JsonValueKind.String
|
||||
? licEl.GetString() ?? string.Empty
|
||||
: string.Empty;
|
||||
|
||||
var copyright = root.TryGetProperty("copyright", out var copyEl) && copyEl.ValueKind == JsonValueKind.String
|
||||
? copyEl.GetString() ?? string.Empty
|
||||
: string.Empty;
|
||||
var resourceDir = Path.GetDirectoryName(metaPath) ?? metaPath;
|
||||
var relativeResourcePath = Path.GetRelativePath(workingDir, resourceDir).Replace('\\', '/');
|
||||
|
||||
output[relativeResourcePath] = new Dictionary<string, string>
|
||||
{
|
||||
{ "license", license },
|
||||
{ "copyright", copyright }
|
||||
};
|
||||
}
|
||||
|
||||
if (output.Count == 0)
|
||||
return;
|
||||
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
file.Write(JsonSerializer.Serialize(output, serializeOptions));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public static class PrototypeJsonGenerator
|
||||
{
|
||||
public static void PublishAll(IResourceManager res, ResPath destRoot)
|
||||
{
|
||||
var proto = IoCManager.Resolve<IPrototypeManager>();
|
||||
var ser = IoCManager.Resolve<ISerializationManager>();
|
||||
|
||||
foreach (var kind in proto.EnumeratePrototypeKinds().OrderBy(t => t.Name))
|
||||
{
|
||||
// The entity prototype has its own generator due to its size <see cref="EntityJsonGenerator"/>.
|
||||
if (kind == typeof(EntityPrototype))
|
||||
continue;
|
||||
|
||||
// Map: entity id -> prototype fields
|
||||
var map = new Dictionary<string, object?>();
|
||||
|
||||
foreach (var p in proto.EnumeratePrototypes(kind))
|
||||
{
|
||||
var node = ser.WriteValueAs<MappingDataNode>(kind, p);
|
||||
node.Remove("id");
|
||||
map[p.ID] = FieldEntry.DataNodeToObject(node);
|
||||
}
|
||||
|
||||
if (map.Count == 0)
|
||||
continue;
|
||||
|
||||
// Determine default field for this prototype.
|
||||
object? defaultObj = null;
|
||||
try
|
||||
{
|
||||
var instance = Activator.CreateInstance(kind);
|
||||
if (instance != null)
|
||||
{
|
||||
FieldEntry.EnsureFieldsCollectionsInitialized(instance);
|
||||
var defaultNode = ser.WriteValueAs<MappingDataNode>(kind, instance, true);
|
||||
defaultNode.Remove("id");
|
||||
defaultObj = FieldEntry.DataNodeToObject(defaultNode);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
defaultObj = new Dictionary<string, object?>();
|
||||
}
|
||||
|
||||
var outObj = new Dictionary<string, object?>
|
||||
{
|
||||
["default"] = defaultObj,
|
||||
["id"] = map
|
||||
};
|
||||
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
res.UserData.CreateDir(destRoot);
|
||||
var fileName = PrototypeUtility.CalculatePrototypeName(kind.Name) + ".json";
|
||||
var file = res.UserData.OpenWriteText(destRoot / fileName);
|
||||
file.Write(JsonSerializer.Serialize(outObj, serializeOptions));
|
||||
file.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
196
Content.Server/Corvax/GuideGenerator/PrototypeListGenerator.cs
Normal file
196
Content.Server/Corvax/GuideGenerator/PrototypeListGenerator.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
|
||||
namespace Content.Server.Corvax.GuideGenerator;
|
||||
|
||||
public static class PrototypeListGenerator
|
||||
{
|
||||
public static void PublishJson(StreamWriter file)
|
||||
{
|
||||
var proto = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
// Map: entity id -> (prototype kind name -> list of prototype ids that reference it).
|
||||
var output = new Dictionary<string, Dictionary<string, List<string>>>();
|
||||
|
||||
foreach (var kindType in proto.EnumeratePrototypeKinds())
|
||||
{
|
||||
var kindName = PrototypeUtility.CalculatePrototypeName(kindType.Name);
|
||||
foreach (var p in proto.EnumeratePrototypes(kindType))
|
||||
{
|
||||
if (!proto.TryGetMapping(kindType, p.ID, out var mapping))
|
||||
continue;
|
||||
|
||||
var referencedEntityIds = new HashSet<string>();
|
||||
|
||||
InspectTypeForEntityRefs(kindType, mapping, referencedEntityIds);
|
||||
|
||||
if (referencedEntityIds.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (var entId in referencedEntityIds)
|
||||
{
|
||||
if (!output.TryGetValue(entId, out var byKind))
|
||||
{
|
||||
byKind = new Dictionary<string, List<string>>();
|
||||
output[entId] = byKind;
|
||||
}
|
||||
|
||||
if (!byKind.TryGetValue(kindName, out var list))
|
||||
{
|
||||
list = new List<string>();
|
||||
byKind[kindName] = list;
|
||||
}
|
||||
|
||||
list.Add(p.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (output.Count == 0)
|
||||
return;
|
||||
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
file.Write(JsonSerializer.Serialize(output, serializeOptions));
|
||||
}
|
||||
|
||||
private static void InspectTypeForEntityRefs(Type prototypeType, MappingDataNode mapping, HashSet<string> outIds)
|
||||
{
|
||||
// Check fields.
|
||||
foreach (var field in prototypeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var attr = field.GetCustomAttribute<DataFieldAttribute>();
|
||||
if (attr == null)
|
||||
continue;
|
||||
|
||||
var tag = attr.Tag ?? LowerFirst(field.Name);
|
||||
if (!mapping.TryGet(tag, out var node))
|
||||
continue;
|
||||
|
||||
ExtractIdsFromNode(field.FieldType, node, outIds);
|
||||
}
|
||||
|
||||
// Check properties.
|
||||
foreach (var prop in prototypeType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var attr = prop.GetCustomAttribute<DataFieldAttribute>();
|
||||
if (attr == null)
|
||||
continue;
|
||||
|
||||
var tag = attr.Tag ?? LowerFirst(prop.Name);
|
||||
if (!mapping.TryGet(tag, out var node))
|
||||
continue;
|
||||
|
||||
ExtractIdsFromNode(prop.PropertyType, node, outIds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractIdsFromNode(Type memberType, DataNode node, HashSet<string> outIds)
|
||||
{
|
||||
var underlying = Nullable.GetUnderlyingType(memberType) ?? memberType;
|
||||
|
||||
if (IsEntProtoIdType(underlying))
|
||||
{
|
||||
if (node is ValueDataNode v)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(v.Value))
|
||||
outIds.Add(v.Value);
|
||||
}
|
||||
else if (node is MappingDataNode m && m.TryGet("id", out var idNode) && idNode is ValueDataNode idVal)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(idVal.Value))
|
||||
outIds.Add(idVal.Value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (node is SequenceDataNode seq)
|
||||
{
|
||||
var elemType = GetElementType(underlying);
|
||||
if (elemType == null)
|
||||
return;
|
||||
|
||||
foreach (var child in seq.Sequence)
|
||||
{
|
||||
ExtractIdsFromNode(elemType, child, outIds);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (node is MappingDataNode map)
|
||||
{
|
||||
foreach (var field in underlying.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var attr = field.GetCustomAttribute<DataFieldAttribute>();
|
||||
if (attr == null)
|
||||
continue;
|
||||
|
||||
var tag = attr.Tag ?? LowerFirst(field.Name);
|
||||
if (!map.TryGet(tag, out var childNode))
|
||||
continue;
|
||||
|
||||
ExtractIdsFromNode(field.FieldType, childNode, outIds);
|
||||
}
|
||||
|
||||
foreach (var prop in underlying.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var attr = prop.GetCustomAttribute<DataFieldAttribute>();
|
||||
if (attr == null)
|
||||
continue;
|
||||
|
||||
var tag = attr.Tag ?? LowerFirst(prop.Name);
|
||||
if (!map.TryGet(tag, out var childNode))
|
||||
continue;
|
||||
|
||||
ExtractIdsFromNode(prop.PropertyType, childNode, outIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsEntProtoIdType(Type t)
|
||||
{
|
||||
if (t == typeof(EntProtoId))
|
||||
return true;
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(EntProtoId<>))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Type? GetElementType(Type t)
|
||||
{
|
||||
if (t.IsArray)
|
||||
return t.GetElementType();
|
||||
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
var genDef = t.GetGenericTypeDefinition();
|
||||
if (genDef == typeof(List<>) || genDef == typeof(IEnumerable<>) || genDef == typeof(IReadOnlyList<>) || genDef == typeof(ICollection<>))
|
||||
return t.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string LowerFirst(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return s;
|
||||
if (s.Length == 1)
|
||||
return s.ToLowerInvariant();
|
||||
return char.ToLowerInvariant(s[0]) + s.Substring(1);
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,19 @@ namespace Content.Server.Entry
|
||||
file = _res.UserData.OpenWriteText(resPath.WithName("loc.json"));
|
||||
LocJsonGenerator.PublishJson(file);
|
||||
file.Flush();
|
||||
file = _res.UserData.OpenWriteText(resPath.WithName("meta_license.json"));
|
||||
MetaLicenseGenerator.PublishJson(file);
|
||||
file.Flush();
|
||||
file = _res.UserData.OpenWriteText(resPath.WithName("prototype.json"));
|
||||
PrototypeListGenerator.PublishJson(file);
|
||||
file.Flush();
|
||||
file = _res.UserData.OpenWriteText(resPath.WithName("component.json"));
|
||||
ComponentListGenerator.PublishJson(file);
|
||||
file.Flush();
|
||||
PrototypeJsonGenerator.PublishAll(_res, new ResPath("prototype").ToRootedPath());
|
||||
file.Flush();
|
||||
ComponentJsonGenerator.PublishAll(_res, new ResPath("component").ToRootedPath());
|
||||
file.Flush();
|
||||
// Corvax-Wiki-End
|
||||
Dependencies.Resolve<IBaseServer>().Shutdown("Data generation done");
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user