Skip to content

Commit 31c0b24

Browse files
committed
New asset menu for ImMilo
1 parent 1973e83 commit 31c0b24

2 files changed

Lines changed: 259 additions & 13 deletions

File tree

ImMilo/Program.SceneTree.cs

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,13 @@ static async void PromptExportDirectory(DirectoryMeta dir)
293293
}
294294
}
295295

296-
class AddSubDirPrompt : Prompt<(string, string)?>
296+
class AddSubDirPrompt : Prompt<(string name, string type, int revision)?>
297297
{
298298
private string newName = "";
299-
private static readonly List<string> Types = ["ObjectDir", "WorldDir", "RndDir", "PanelDir"];
299+
private static readonly string[] Types = DirectoryMeta.GetSupportedDirectoryTypes();
300300
private int curType = 0;
301-
301+
private int revision = 25;
302+
302303
public AddSubDirPrompt()
303304
{
304305
Title = "Add Inlined Subdirectory";
@@ -309,12 +310,13 @@ public override void Show()
309310
if (BeginModal())
310311
{
311312
ImGui.InputText("Name", ref newName, 32);
312-
ImGui.Combo("Type", ref curType, Types.ToArray(), Types.Count);
313-
313+
ImGui.Combo("Type", ref curType, Types, Types.Length);
314+
ImGui.InputInt("Revision", ref revision);
315+
314316
ImGui.Separator();
315317
if (ImGui.Button("OK"))
316318
{
317-
Complete((newName, Types[curType]));
319+
Complete((newName, Types[curType], revision));
318320
}
319321
ImGui.SameLine();
320322
if (ImGui.Button("Cancel"))
@@ -329,19 +331,87 @@ public override void Show()
329331
static async void PromptAddSubdir(DirectoryMeta dirEntry)
330332
{
331333
ObjectDir dir = (ObjectDir)dirEntry.directory;
332-
334+
333335
var promptInput = await ShowGenericPrompt(new AddSubDirPrompt());
334336
if (promptInput != null)
335337
{
336-
var (newName, newType) = promptInput.Value;
337-
var newDir = DirectoryMeta.New(newType, newName, 27, 25); // this is how it's done in MiloEditor
338+
var (newName, newType, revision) = promptInput.Value;
339+
var newDir = DirectoryMeta.New(newType, newName, dirEntry.revision, (ushort)revision);
338340
dir.inlineSubDirs.Add(newDir);
339341
dir.inlineSubDirNames.Add($"{newName}.milo");
340342
dir.referenceTypes.Add(ObjectDir.ReferenceType.kInlineCached);
341343
dir.referenceTypesAlt.Add(ObjectDir.ReferenceType.kInlineCached);
342344
}
343345
}
344346

347+
class NewAssetPrompt : Prompt<(string type, string name, int revision)?>
348+
{
349+
private string newName = "";
350+
private static readonly string[] Types = DirectoryMeta.GetSupportedEntryTypes();
351+
private int curType = 0;
352+
private int revision = 0;
353+
354+
public NewAssetPrompt()
355+
{
356+
Title = "New Asset";
357+
}
358+
359+
public override void Show()
360+
{
361+
if (BeginModal())
362+
{
363+
ImGui.InputText("Name", ref newName, 64);
364+
ImGui.Combo("Type", ref curType, Types, Types.Length);
365+
ImGui.InputInt("Revision", ref revision);
366+
367+
ImGui.Separator();
368+
if (ImGui.Button("OK"))
369+
{
370+
Complete((Types[curType], newName, revision));
371+
}
372+
ImGui.SameLine();
373+
if (ImGui.Button("Cancel"))
374+
{
375+
Complete(null);
376+
}
377+
ImGui.EndPopup();
378+
}
379+
}
380+
}
381+
382+
static async void PromptNewAsset(DirectoryMeta dir)
383+
{
384+
var promptInput = await ShowGenericPrompt(new NewAssetPrompt());
385+
if (promptInput == null)
386+
return;
387+
388+
var (type, name, revision) = promptInput.Value;
389+
390+
try
391+
{
392+
var obj = DirectoryMeta.CreateDefaultObject(type);
393+
if (obj == null)
394+
{
395+
OpenErrorModal(new Exception($"Type '{type}' is not supported for creation."), "Failed to create asset.");
396+
return;
397+
}
398+
399+
var entry = new DirectoryMeta.Entry(type, name, obj);
400+
401+
// If this is a directory type, also create the inline directory
402+
if (DirectoryMeta.IsDirectoryType(type))
403+
{
404+
entry.dir = DirectoryMeta.New(type, name, dir.revision, (ushort)revision);
405+
}
406+
407+
dir.entries.Add(entry);
408+
}
409+
catch (Exception e)
410+
{
411+
OpenErrorModal(e, "Failed to create asset.");
412+
}
413+
}
414+
345415
static void FindReferencesMenu(string target)
346416
{
347417
if (ImGui.BeginMenu(FontAwesome5.Search + " Find References"))
@@ -516,7 +586,10 @@ void DirectoryContextMenu(ref int iterId)
516586
PromptImportAsset(dir);
517587
}
518588

519-
ImGui.MenuItem(FontAwesome5.PlusCircle + " New Asset", "", false, false);
589+
if (ImGui.MenuItem(FontAwesome5.PlusCircle + " New Asset"))
590+
{
591+
PromptNewAsset(dir);
592+
}
520593

521594
FindReferencesMenu(dir.name);
522595

MiloLib/Assets/DirectoryMeta.cs

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -807,12 +807,12 @@ internal void OnAfterWrite(EndianWriter writer)
807807
/// <summary>
808808
/// The amount of strings in the string table. Usually calculated as (numEntries * 2) + 2.
809809
/// </summary>
810-
private uint stringTableCount;
810+
public uint stringTableCount;
811811

812812
/// <summary>
813813
/// The size of the string table. Not sure how this is calculated, but the game will fix it itself if it's not right.
814814
/// </summary>
815-
private uint stringTableSize;
815+
public uint stringTableSize;
816816

817817
private uint externalResourceCount;
818818

@@ -958,7 +958,7 @@ public void Write(EndianWriter writer)
958958
Symbol.Write(writer, type);
959959
Symbol.Write(writer, name);
960960

961-
writer.WriteInt32((entries.Count * 2) + 4);
961+
writer.WriteInt32((entries.Count + 1) * 2);
962962
writer.WriteUInt32(stringTableSize);
963963

964964
if (revision >= 32)
@@ -1096,6 +1096,179 @@ public void WriteEntry(EndianWriter writer, DirectoryMeta.Entry entry)
10961096
entry.OnAfterWrite(writer);
10971097
}
10981098

1099+
/// <summary>
1100+
/// Creates a default-constructed object instance for the given entry type name.
1101+
/// Returns null if the type is not recognized.
1102+
/// </summary>
1103+
public static Object CreateDefaultObject(string typeName)
1104+
{
1105+
if (EntryObjectFactories.TryGetValue(typeName, out var factory))
1106+
return factory();
1107+
return null;
1108+
}
1109+
1110+
/// <summary>
1111+
/// Returns a sorted array of all supported entry type names (from EntryReadActions).
1112+
/// </summary>
1113+
public static string[] GetSupportedEntryTypes()
1114+
{
1115+
var types = EntryReadActions.Keys.ToArray();
1116+
Array.Sort(types, StringComparer.OrdinalIgnoreCase);
1117+
return types;
1118+
}
1119+
1120+
/// <summary>
1121+
/// Returns a sorted array of all supported directory type names (from DirectoryFactories).
1122+
/// </summary>
1123+
public static string[] GetSupportedDirectoryTypes()
1124+
{
1125+
var types = DirectoryFactories.Keys.ToArray();
1126+
Array.Sort(types, StringComparer.OrdinalIgnoreCase);
1127+
return types;
1128+
}
1129+
1130+
/// <summary>
1131+
/// Returns true if the given type name is a directory type (has an entry in DirectoryFactories).
1132+
/// </summary>
1133+
public static bool IsDirectoryType(string typeName)
1134+
{
1135+
return DirectoryFactories.ContainsKey(typeName);
1136+
}
1137+
1138+
// factories for constructing various objs
1139+
private static readonly Dictionary<string, Func<Object>> EntryObjectFactories = new Dictionary<string, Func<Object>>
1140+
{
1141+
{ "AnimFilter", () => new RndAnimFilter() },
1142+
{ "BandButton", () => new BandButton() },
1143+
{ "BandCamShot", () => new BandCamShot() },
1144+
{ "BandCharDesc", () => new BandCharDesc() },
1145+
{ "BandConfiguration", () => new BandConfiguration() },
1146+
{ "BandDirector", () => new BandDirector() },
1147+
{ "BandFaceDeform", () => new BandFaceDeform() },
1148+
{ "BandLabel", () => new BandLabel() },
1149+
{ "BandList", () => new BandList() },
1150+
{ "BandPlacer", () => new BandPlacer() },
1151+
{ "BandScreen", () => new Object() },
1152+
{ "BandSongPref", () => new BandSongPref() },
1153+
{ "BandSwatch", () => new BandSwatch() },
1154+
{ "BustAMoveData", () => new BustAMoveData() },
1155+
{ "Cam", () => new RndCam() },
1156+
{ "CamShot", () => new CamShot() },
1157+
{ "CharClipGroup", () => new CharClipGroup() },
1158+
{ "CharCollide", () => new CharCollide() },
1159+
{ "CharForeTwist", () => new CharForeTwist() },
1160+
{ "CharGuitarString", () => new CharGuitarString() },
1161+
{ "CharHair", () => new CharHair() },
1162+
{ "CharIKMidi", () => new CharIKMidi() },
1163+
{ "CharIKRod", () => new CharIKRod() },
1164+
{ "CharInterest", () => new CharInterest() },
1165+
{ "CharMeshHide", () => new CharMeshHide() },
1166+
{ "CharPosConstraint", () => new CharPosConstraint() },
1167+
{ "CharServoBone", () => new CharServoBone() },
1168+
{ "CharUpperTwist", () => new CharUpperTwist() },
1169+
{ "CharWalk", () => new CharWalk() },
1170+
{ "CharWeightSetter", () => new CharWeightSetter() },
1171+
{ "CheckboxDisplay", () => new CheckboxDisplay() },
1172+
{ "ColorPalette", () => new ColorPalette() },
1173+
{ "DancerSequence", () => new DancerSequence() },
1174+
{ "Environ", () => new RndEnviron() },
1175+
{ "EventTrigger", () => new EventTrigger() },
1176+
{ "FileMerger", () => new FileMerger() },
1177+
{ "Font", () => new RndFont() },
1178+
{ "Fur", () => new RndFur() },
1179+
{ "Group", () => new RndGroup() },
1180+
{ "View", () => new RndGroup() },
1181+
{ "HamBattleData", () => new HamBattleData() },
1182+
{ "HamMove", () => new HamMove() },
1183+
{ "HamPartyJumpData", () => new HamPartyJumpData() },
1184+
{ "HamSupereasyData", () => new HamSupereasyData() },
1185+
{ "InlineHelp", () => new InlineHelp() },
1186+
{ "InterstitialPanel", () => new Object() },
1187+
{ "Light", () => new RndLight() },
1188+
{ "Mat", () => new RndMat() },
1189+
{ "MatAnim", () => new RndMatAnim() },
1190+
{ "Mesh", () => new RndMesh() },
1191+
{ "MotionBlur", () => new RndMotionBlur() },
1192+
{ "MoveGraph", () => new MoveGraph() },
1193+
{ "MsgSource", () => new Object() },
1194+
{ "Object", () => new Object() },
1195+
{ "OutfitConfig", () => new OutfitConfig() },
1196+
{ "P9Director", () => new P9Director() },
1197+
{ "ParticleSys", () => new RndParticleSys() },
1198+
{ "ParticleSysAnim", () => new RndParticleSysAnim() },
1199+
{ "PollAnim", () => new RndPollAnim() },
1200+
{ "PostProc", () => new RndPostProc() },
1201+
{ "PracticeSection", () => new PracticeSection() },
1202+
{ "PropAnim", () => new RndPropAnim() },
1203+
{ "RandomGroupSeq", () => new RandomGroupSeq() },
1204+
{ "ScreenMask", () => new RndScreenMask() },
1205+
{ "Text", () => new RndText() },
1206+
{ "Set", () => new RndSet() },
1207+
{ "Sfx", () => new Sfx() },
1208+
{ "SpotlightDrawer", () => new SpotlightDrawer() },
1209+
{ "SynthSample", () => new SynthSample() },
1210+
{ "Tex", () => new RndTex() },
1211+
{ "TexBlendController", () => new RndTexBlendController() },
1212+
{ "TexBlender", () => new RndTexBlender() },
1213+
{ "TexMovie", () => new RndTexMovie() },
1214+
{ "TrackWidget", () => new TrackWidget() },
1215+
{ "TrainerChallenge", () => new Object() },
1216+
{ "Trans", () => new RndTrans() },
1217+
{ "TransAnim", () => new RndTransAnim() },
1218+
{ "TransProxy", () => new RndTransProxy() },
1219+
{ "UIButton", () => new UIButton() },
1220+
{ "UIColor", () => new UIColor() },
1221+
{ "UIComponent", () => new UIComponent() },
1222+
{ "UIGuide", () => new UIGuide() },
1223+
{ "UILabel", () => new UILabel() },
1224+
{ "UIList", () => new UIList() },
1225+
{ "UIListArrow", () => new UIListArrow() },
1226+
{ "UIListCustom", () => new UIListCustom() },
1227+
{ "UIListHighlight", () => new UIListHighlight() },
1228+
{ "UIListLabel", () => new UIListLabel() },
1229+
{ "UIListMesh", () => new UIListMesh() },
1230+
{ "UIListSlot", () => new UIListSlot() },
1231+
{ "UIListWidget", () => new UIListWidget() },
1232+
{ "UIPanel", () => new Object() },
1233+
{ "UIPicture", () => new UIPicture() },
1234+
{ "UISlider", () => new UISlider() },
1235+
{ "UITrigger", () => new UITrigger() },
1236+
{ "Wind", () => new RndWind() },
1237+
{ "WorldCrowd", () => new WorldCrowd() },
1238+
{ "WorldReflection", () => new WorldReflection() },
1239+
// Directory types (use revision 0 as default)
1240+
{ "BandCharacter", () => new BandCharacter(0) },
1241+
{ "BandCrowdMeterDir", () => new BandCrowdMeterDir(0) },
1242+
{ "BandScoreboard", () => new BandScoreboard(0) },
1243+
{ "BandStarDisplay", () => new BandStarDisplay(0) },
1244+
{ "Character", () => new Character(0) },
1245+
{ "CharBoneDir", () => new CharBoneDir(0) },
1246+
{ "CharClipSet", () => new CharClipSet(0) },
1247+
{ "CompositeCharacter", () => new CompositeCharacter(0) },
1248+
{ "CrowdMeterIcon", () => new BandCrowdMeterIcon(0) },
1249+
{ "EndingBonusDir", () => new RndDir(0) },
1250+
{ "GemTrackDir", () => new GemTrackDir(0) },
1251+
{ "MoveDir", () => new MoveDir(0) },
1252+
{ "ObjectDir", () => new ObjectDir(0) },
1253+
{ "OverdriveMeterDir", () => new OverdriveMeterDir(0) },
1254+
{ "OvershellDir", () => new OvershellDir(0) },
1255+
{ "P9Character", () => new P9Character(0) },
1256+
{ "PanelDir", () => new PanelDir(0) },
1257+
{ "PitchArrowDir", () => new PitchArrowDir(0) },
1258+
{ "RndDir", () => new RndDir(0) },
1259+
{ "SkeletonDir", () => new SkeletonDir(0) },
1260+
{ "StreakMeterDir", () => new StreakMeterDir(0) },
1261+
{ "SynthDir", () => new SynthDir(0) },
1262+
{ "TrackDir", () => new TrackDir(0) },
1263+
{ "TrackPanelDir", () => new TrackPanelDir(0) },
1264+
{ "UILabelDir", () => new UILabelDir(0) },
1265+
{ "UIListDir", () => new UIListDir(0) },
1266+
{ "UnisonIcon", () => new UnisonIcon(0) },
1267+
{ "VocalTrackDir", () => new VocalTrackDir(0) },
1268+
{ "WorldDir", () => new WorldDir(0) },
1269+
{ "WorldInstance", () => new WorldInstance(0) },
1270+
};
1271+
10991272
public static DirectoryMeta New(string type, string name, uint sceneRevision, ushort rootDirRevision)
11001273
{
11011274
DirectoryMeta dir = new DirectoryMeta();

0 commit comments

Comments
 (0)