Skip to content

Commit abb9469

Browse files
authored
Merge pull request #1 from Crypto137/dev
Merge 0.2.0
2 parents f86c798 + 9062210 commit abb9469

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+5931
-836
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System.Text;
2+
using OpenCalligraphy.Core.GameData;
3+
using OpenCalligraphy.Core.Logging;
4+
5+
namespace OpenCalligraphy.Core.CodeGeneration
6+
{
7+
public class GeneratedPrototypeClass
8+
{
9+
private static readonly Logger Logger = LogManager.CreateLogger();
10+
11+
private readonly Dictionary<string, GeneratedPrototypeField> _fieldDict = new();
12+
13+
private readonly HashSet<string> _parents = new();
14+
15+
private bool _hasPropertyMixin = false;
16+
17+
public string Name { get; }
18+
19+
public GeneratedPrototypeClass(string name)
20+
{
21+
Name = name;
22+
}
23+
24+
public void AddField(string fieldName, CalligraphyBaseType baseType, CalligraphyStructureType structureType, ulong subtype)
25+
{
26+
if (_fieldDict.ContainsKey(fieldName) == false)
27+
_fieldDict.Add(fieldName, new(this, fieldName, baseType, structureType, subtype));
28+
}
29+
30+
public void AddParent(BlueprintId parentRef)
31+
{
32+
Blueprint parent = GameDatabase.GetBlueprint(parentRef);
33+
if (parent == null)
34+
{
35+
Logger.Warn($"Failed to find parent blueprint {parentRef} for {Name}");
36+
return;
37+
}
38+
39+
// If this is a property mixin reference, just flag this class and exit
40+
if (parent.IsPropertyMixin)
41+
{
42+
if (_hasPropertyMixin == false)
43+
{
44+
Logger.Info($"Found property mixin in {Name}");
45+
_hasPropertyMixin = true;
46+
}
47+
48+
return;
49+
}
50+
51+
if (parent.RuntimeBinding != Name && parent.RuntimeBinding != "Prototype")
52+
_parents.Add(parent.RuntimeBinding);
53+
}
54+
55+
public string GenerateCode()
56+
{
57+
StringBuilder sb = new();
58+
59+
string baseClass;
60+
if (_parents.Count == 0)
61+
{
62+
baseClass = "Prototype"; // No parents (inherit from the base Prototype class)
63+
}
64+
else if (_parents.Count == 1)
65+
{
66+
baseClass = _parents.First(); // Only a single parent, so we can be certain it's the one
67+
}
68+
else if (PrototypeParentLookupTable.TryGetParentForPrototype(Name, out string lookupParentName))
69+
{
70+
// We are using a hardcoded hierarchy lookup (e.g. entity hierarchy which stayed generally the same for all versions)
71+
Logger.Info($"Resolving parent conflict using hardcoded lookup: {Name} => {lookupParentName}");
72+
baseClass = lookupParentName;
73+
}
74+
else
75+
{
76+
// If we have multiple parents, we will default to Prototype, but list all potential parents as comments
77+
Logger.Info($"Multiple potential parents for {Name}:{_parents.Aggregate(string.Empty, (current, next) => $"{current} {next}")}");
78+
baseClass = "Prototype";
79+
foreach (string parentName in _parents)
80+
sb.AppendLine($"// {parentName}");
81+
}
82+
83+
sb.AppendLine($@"public class {Name} : {baseClass}");
84+
sb.AppendLine("{");
85+
if (Name != "PropertyPrototype") // Ignore property mixin fields
86+
{
87+
// Sort fields for consistent output between versions so that we can more easily compare
88+
foreach (GeneratedPrototypeField field in _fieldDict.Values.OrderBy(field => field.Name))
89+
sb.AppendLine($"\t{field.GenerateCode()}");
90+
}
91+
sb.AppendLine("}");
92+
93+
return sb.ToString();
94+
}
95+
}
96+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using OpenCalligraphy.Core.GameData;
2+
using OpenCalligraphy.Core.Logging;
3+
4+
namespace OpenCalligraphy.Core.CodeGeneration
5+
{
6+
public class GeneratedPrototypeField
7+
{
8+
private static readonly Logger Logger = LogManager.CreateLogger();
9+
10+
private readonly GeneratedPrototypeClass _prototypeClass;
11+
12+
public string Name { get; }
13+
public CalligraphyBaseType BaseType { get; }
14+
public CalligraphyStructureType StructureType { get; }
15+
public ulong Subtype { get; }
16+
17+
public GeneratedPrototypeField(GeneratedPrototypeClass prototypeClass, string name, CalligraphyBaseType baseType, CalligraphyStructureType structureType, ulong subtype)
18+
{
19+
_prototypeClass = prototypeClass;
20+
21+
Name = name;
22+
BaseType = baseType;
23+
StructureType = structureType;
24+
Subtype = subtype;
25+
}
26+
27+
public string GenerateCode()
28+
{
29+
string typeName;
30+
31+
if (BaseType == CalligraphyBaseType.RHStruct)
32+
{
33+
Blueprint blueprint = GameDatabase.GetBlueprint((BlueprintId)Subtype);
34+
35+
if (blueprint == null)
36+
{
37+
// HACK: This seems to be happening only for EvalPrototype fields, so we'll just fall back to it
38+
Logger.Warn($"Invalid subtype {Subtype} for RHStruct field {Name} in {_prototypeClass.Name}, falling back to EvalPrototype");
39+
typeName = "EvalPrototype";
40+
}
41+
else
42+
{
43+
typeName = blueprint.RuntimeBinding;
44+
45+
// Replace mixin property prototypes with PropertyId
46+
if (typeName == "PropertyPrototype")
47+
typeName = "PropertyId";
48+
}
49+
}
50+
else
51+
{
52+
typeName = BaseType switch
53+
{
54+
CalligraphyBaseType.Asset => "AssetId",
55+
CalligraphyBaseType.Boolean => "bool",
56+
CalligraphyBaseType.Curve => "CurveId",
57+
CalligraphyBaseType.Double => "double",
58+
CalligraphyBaseType.Long => "long",
59+
CalligraphyBaseType.Prototype => "PrototypeId",
60+
CalligraphyBaseType.String => "LocaleStringId",
61+
CalligraphyBaseType.Type => "AssetTypeId",
62+
_ => throw new()
63+
};
64+
}
65+
66+
string typeSuffix = StructureType == CalligraphyStructureType.List ? "[]" : string.Empty;
67+
68+
// Special handling for property list (appears in ModPrototype only)
69+
if (typeName == "PropertyId" && StructureType == CalligraphyStructureType.List)
70+
{
71+
typeName = "PrototypePropertyCollection";
72+
typeSuffix = string.Empty;
73+
}
74+
75+
return $"public {typeName}{typeSuffix} {Name} {{ get; protected set; }}";
76+
}
77+
}
78+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using OpenCalligraphy.Core.GameData;
2+
using OpenCalligraphy.Core.Logging;
3+
4+
namespace OpenCalligraphy.Core.CodeGeneration
5+
{
6+
public static class PrototypeClassGenerator
7+
{
8+
private static readonly Logger Logger = LogManager.CreateLogger();
9+
10+
public static void Generate(string outputPath)
11+
{
12+
// Generate prototype classes
13+
Dictionary<string, GeneratedPrototypeClass> prototypeClassDict = new();
14+
15+
foreach (Blueprint blueprint in DataDirectory.Instance.IterateBlueprints())
16+
{
17+
string runtimeBinding = blueprint.RuntimeBinding;
18+
19+
if (prototypeClassDict.TryGetValue(runtimeBinding, out GeneratedPrototypeClass prototypeClass) == false)
20+
{
21+
prototypeClass = new(runtimeBinding);
22+
prototypeClassDict.Add(runtimeBinding, prototypeClass);
23+
}
24+
25+
foreach (BlueprintReference blueprintRef in blueprint.Parents)
26+
prototypeClass.AddParent(blueprintRef.BlueprintId);
27+
28+
foreach (BlueprintMember member in blueprint.Members)
29+
prototypeClass.AddField(member.FieldName, member.BaseType, member.StructureType, member.Subtype);
30+
}
31+
32+
Logger.Info($"Found {prototypeClassDict.Count} unique runtime bindings");
33+
34+
// Write the results
35+
string directory = Path.GetDirectoryName(outputPath);
36+
if (Directory.Exists(directory) == false)
37+
Directory.CreateDirectory(directory);
38+
39+
using (StreamWriter writer = new(outputPath))
40+
{
41+
// Sort classes for consistent output between versions so that we can more easily compare
42+
foreach (GeneratedPrototypeClass prototypeClass in prototypeClassDict.Values.OrderBy(prototypeClass => prototypeClass.Name))
43+
writer.WriteLine(prototypeClass.GenerateCode());
44+
}
45+
}
46+
}
47+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace OpenCalligraphy.Core.CodeGeneration
2+
{
3+
public static class PrototypeParentLookupTable
4+
{
5+
// Hardcoded parents for classes that probably had the same hierarchy for the entire lifespan of the game
6+
private static readonly Dictionary<string, string> ParentLookup = new()
7+
{
8+
{ "WorldEntityPrototype", "EntityPrototype" },
9+
{ "AgentPrototype", "WorldEntityPrototype" },
10+
{ "AvatarPrototype", "AgentPrototype" },
11+
{ "AgentTeamUpPrototype", "AgentPrototype" },
12+
{ "HotspotPrototype", "WorldEntityPrototype" },
13+
{ "ItemPrototype", "WorldEntityPrototype" },
14+
{ "CostumePrototype", "ItemPrototype" },
15+
{ "SpawnerPrototype", "WorldEntityPrototype" },
16+
{ "TransitionPrototype", "WorldEntityPrototype" },
17+
// Not including PropPrototype because it seems to differ based on version (WorldEntity vs Agent?)
18+
19+
{ "PlayerStashInventoryPrototype", "InventoryPrototype" },
20+
21+
{ "PowerPrototype", "Prototype" },
22+
{ "MissilePowerPrototype", "PowerPrototype" },
23+
{ "SummonPowerPrototype", "PowerPrototype" },
24+
{ "MovementPowerPrototype", "PowerPrototype" },
25+
{ "PowerReplacementPowerPrototype", "PowerPrototype" },
26+
{ "SpecializationPowerPrototype", "PowerPrototype" },
27+
28+
{ "MissionActionEntityDestroyPrototype", "MissionActionEntityTargetPrototype" },
29+
{ "MissionActionEntityKillPrototype", "MissionActionEntityTargetPrototype" },
30+
{ "MissionActionEntityPerformPowerPrototype", "MissionActionEntityTargetPrototype" },
31+
{ "MissionActionEntitySetStatePrototype", "MissionActionEntityTargetPrototype" },
32+
{ "MissionActionSpawnerTriggerPrototype", "MissionActionEntityTargetPrototype" },
33+
{ "MissionActionShowOverheadTextPrototype", "MissionActionEntityTargetPrototype" },
34+
{ "MissionActionEntSelEvtBroadcastPrototype", "MissionActionEntityTargetPrototype" },
35+
{ "MissionActionAllianceSetPrototype", "MissionActionEntityTargetPrototype" },
36+
37+
{ "PopulationClusterMixedPrototype", "PopulationObjectPrototype" },
38+
{ "PopulationClusterFixedPrototype", "PopulationObjectPrototype" },
39+
{ "PopulationClusterPrototype", "PopulationObjectPrototype" },
40+
{ "PopulationEncounterPrototype", "PopulationObjectPrototype" },
41+
{ "PopulationEntityPrototype", "PopulationObjectPrototype" },
42+
{ "PopulationLeaderPrototype", "PopulationObjectPrototype" },
43+
{ "PopulationFormationPrototype", "PopulationObjectPrototype" },
44+
{ "PopulationObjectListPrototype", "Prototype" },
45+
46+
{ "MobKeywordPrototype", "EntityKeywordPrototype" },
47+
};
48+
49+
public static bool TryGetParentForPrototype(string prototypeClassName, out string parentName)
50+
{
51+
return ParentLookup.TryGetValue(prototypeClassName, out parentName);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)