Lucubration Posted March 26, 2016 Share Posted March 26, 2016 ForewordAs mods are getting more complex - my own included - I'm seeing a need to move some mod options from .ini files into the in-game UI to be more accessible to users. I was going to pursue a custom solution for my particular case, but I can imagine that might lead to a cluttered UI if a number of mods all began doing the same. With this in mind, I put together a little system here for inserting a "Mod Options" menu item into the normal, in-game Options menu. The technique uses screen listeners, the event manager and some dynamic type resolution to allow any interested mods to utilize this same pathway for displaying their own mod options screen for the user. I've gone ahead and published this Mod Options Menu despite nothing using it just yet. I'd to submit the design here as a request for comments. If this is a pattern that's of interest to you, I'd very much appreciate it if you would take a minute to help me go over the API design to make sure that it will be robust enough and suitable for all of our use. I'm mostly interested in making sure that the contract between the API and clients is well-designed; I expect that the actual UI design will change over time relatively transparent to any client mods. Nexus link: http://www.nexusmods.com/xcom2/mods/522/?Workshop link: http://steamcommunity.com/sharedfiles/filedetails/?id=652998069 DesignI begin this pattern by using a UIScreenListener to see when the Xcom 2 main Options menu pops up. I'm inserting an extra button right down by the Credits button (at least in the Shell menu) that opens up the mod list. This is where things get interesting. Depending on which XComPlayerController spawned the Options screen, I'm creating one of three subclasses of the mod list screen (for the Shell, HQ, and Tactical game modes). In the superclass, there is much ugly UI code going on to set up the list and so on, and right at the start of it all I'm registering for a new event 'ModOptionsMenu_AddItem', simulated function InitScreen(XComPlayerController InitController, UIMovie InitMovie, optional name InitName) { local X2EventManager EventManager; local Object ThisObj; EventManager = `XEVENTMGR; ThisObj = self; EventManager.RegisterForEvent(ThisObj, 'ModOptionsMenu_AddItem', OnAddItem, ELD_Immediate); super.InitScreen(InitController, InitMovie, InitName); ... } The next piece of the puzzle here is the contract between the client mods and the API, and it involves another UIScreenListener implemented by the client. This one listens for the Mod Options Menu screen(s) and triggers the 'ModOptionsMenu_AddItem' event to advertise itself to the API. In this example I have it listening for all three different subclasses. The event data included in the 'ModOptionsMenu_AddItem' event contains the display name that should appear in the Mod Options Menu list of mods, and the name of a UIScreen subclass that should be instantiated on behalf of the client mod when the user selects that mod's name from the list. As you can see, I'm using this strange 'XGParamTag' class to transfer the event data. I had this hard requirement for myself that I had to use only types available in the Xcom 2 source scripts so that any mod could consume this pattern. The 'XGParamTag' class was the simplest object I could find in their codebase that could actually be cast as an 'object' and still contain the information we need for the pattern to function (a string or two). Yeah, it's a hack. class ExampleMod_UIModMenuScreenListener extends UIScreenListener; event OnInit(UIScreen Screen) { local XGParamTag MenuTextParams; if (Screen.IsA('UIModOptionsMenu_ModOptionsMenu')) { if (Screen.IsA('UIModOptionsMenu_HQModOptionsMenu')) { `LOG("Mod Options Menu: Example Mod observed HQ Mod Options Menu screen. Triggering event."); MenuTextParams = new class'XGParamTag'; MenuTextParams.StrValue0 = "My Example Mod"; MenuTextParams.StrValue1 = "ExampleMod.ExampleMod_UIHQModOptionsScreen"; `XEVENTMGR.TriggerEvent('ModOptionsMenu_AddItem', MenuTextParams,, none); } else if (Screen.IsA('UIModOptionsMenu_ShellModOptionsMenu')) { `LOG("Mod Options Menu: Example Mod observed Shell Mod Options Menu screen. Triggering event."); MenuTextParams = new class'XGParamTag'; MenuTextParams.StrValue0 = "My Example Mod"; MenuTextParams.StrValue1 = "ExampleMod.ExampleMod_UIShellModOptionsScreen"; `XEVENTMGR.TriggerEvent('ModOptionsMenu_AddItem', MenuTextParams,, none); } else if (Screen.IsA('UIModOptionsMenu_TacticalModOptionsMenu')) { `LOG("Mod Options Menu: Example Mod observed Tactical Mod Options Menu screen. Triggering event."); MenuTextParams = new class'XGParamTag'; MenuTextParams.StrValue0 = "My Example Mod"; MenuTextParams.StrValue1 = "ExampleMod.ExampleMod_UITacticalModOptionsScreen"; `XEVENTMGR.TriggerEvent('ModOptionsMenu_AddItem', MenuTextParams,, none); } } } The Mod Options Menu screen then receives the 'ModOptionsMenu_AddItem' event and adds the item to the list. simulated function EventListenerReturn OnAddItem(Object EventData, Object EventSource, XComGameState GameState, Name EventID) { local XGParamTag MenuTextParams; MenuTextParams = XGParamTag(EventData); `LOG("Mod Options Menu: Mod Options Menu received event " @ MenuTextParams.StrValue0 @ ", " @ MenuTextParams.StrValue1 @ "."); if (MenuTextParams == none || MenuTextParams.StrValue0 == "" || MenuTextParams.StrValue1 == "") return ELR_NoInterrupt; CreateListItem(MenuTextParams.StrValue0, MenuTextParams.StrValue1); return ELR_NoInterrupt; } The final step occurs when the client clicks on a mod name in the mod list. The Mod Options Menu screen instantiates the class specified by the client mod and pushes it to the top of the screen stack. simulated function OpenModOptions() { local int ItemIndex; local string ModUIClassName; local class<UIScreen> ModUIClass; local UIScreen ModOptionsScreen; `LOG("Mod Options Menu: Mod Options button clicked."); ItemIndex = List.GetItemIndex(List.GetSelectedItem()); ModUIClassName = ModUIClassNames[ItemIndex]; ModUIClass = class<UIScreen>(PC.Pres.DynamicLoadObject(ModUIClassName, class'Class')); if (ModUIClass != none) { if (Movie.Stack.GetScreen(ModUIClass) == none) { ModOptionsScreen = PC.Pres.Spawn(ModUIClass, PC.Pres); Movie.Stack.Push(ModOptionsScreen); `LOG("Mod Options Menu: Mod " @ ModUIClassName @ " UI spawned."); } } else { `REDSCREEN("Mod Options Menu: Failed to create mod " @ ModDisplayNames[ItemIndex] @ " options screen class " @ ModUIClassName @ "."); } } I've also put together a very simple example mod that uses this API (attached to this post). AfterwordSo. Is this pattern something you'd be interested in using? Would it have to be improved in some way for you to consider using it? Link to comment Share on other sites More sharing options...
zingfharn Posted March 26, 2016 Share Posted March 26, 2016 That is incredibly f*#@ing awesome. Thank you. I have literally just implemented my own notifications system to inform people of updates to my mod, in-game, because no-one ever checks the workshop. I was having a think about how to integrate options into the core game like that, and the problem for me (at least) is that much of my stuff can't be changed at run-time - things like altering settings on tech templates, which are protected by default, and require the game to reload. Annoyingly. That being said, this is still awesome. Good job. Link to comment Share on other sites More sharing options...
davidlallen Posted March 26, 2016 Share Posted March 26, 2016 (edited) In this case posting to both /r/xcom2mods and here has led to an interesting discovery:https://www.reddit.com/r/xcom2mods/comments/4c0pah/rfc_mod_options_menu/ Edited March 28, 2016 by davidlallen Link to comment Share on other sites More sharing options...
Lucubration Posted March 26, 2016 Author Share Posted March 26, 2016 That is incredibly f***ing awesome. Thank you. I have literally just implemented my own notifications system to inform people of updates to my mod, in-game, because no-one ever checks the workshop. I was having a think about how to integrate options into the core game like that, and the problem for me (at least) is that much of my stuff can't be changed at run-time - things like altering settings on tech templates, which are protected by default, and require the game to reload. Annoyingly. That being said, this is still awesome. Good job. Write-protected template members is definitely one of the more annoying things I've run into with the SDK so far. I haven't found any solution other than to just replace the template outright using the template manager override. Link to comment Share on other sites More sharing options...
Zyxpsilon Posted March 27, 2016 Share Posted March 27, 2016 Fantastic solution to add straight into my Modding Tools arsenal!Thanks... a zillion for this stuff. :D Link to comment Share on other sites More sharing options...
Lucubration Posted March 28, 2016 Author Share Posted March 28, 2016 Hey, davidlallen, I don't know if you saw this on Reddit, but it's basically exactly what you were asking about regarding saving .ini file changes. It's really simple and works dead easy, too: https://www.reddit.com/r/xcom2mods/comments/4c6qrr/new_way_to_loadsave_ini_configs/ Link to comment Share on other sites More sharing options...
bunnytrain Posted March 29, 2016 Share Posted March 29, 2016 Awesome work! I love that there are so many of us trying to find a good answer to this problem. Just wanted to add an extra link to the discussion. A few of us had been discussing a different approach that was nearing completion as well. Kept it in a public forum so that anybody could see the details. You can see both the existing code and the lively API design debate here: https://github.com/andrewgu/ModConfigMenu Link to comment Share on other sites More sharing options...
Recommended Posts