prowler83 Posted May 1, 2016 Share Posted May 1, 2016 Short version: I can detect when the UI I'm trying to listen for is being replaced by a mod, and I can reassign ScreenClass, but I'm not sure if I've come up with a good way to get the mod class I need to set it to. Long version: I was scratching my head last night over how to get my UI listeners compatible with mods that override the screen I need. I can't add a UI listener that triggers on the mod class because the compiler won't recognize it. I could set ScreenClass to none with an IsA() check at the start of all my functions, but possible performance issues aside, that could still cause undesirable behavior (e.g. a UIArmory_Promotion listener that isn't supposed to affect UIArmory_PromotionPsiOp, or mods that extend a UI without overriding it). Then I realized that the array of overrides is accessible by any mod through `XENGINE.ModClassOverrides. (I assume it's read-only.) Testing showed that if I change the ScreenClass of a UI listener it will start triggering on the new class instead. If you so desired, you could theoretically write a single UI listener that chains itself across a series of different UIs. But how to notify the listener to use the new class? The whole point is to handle cases where it never triggers. It would have to be set to none so that it triggers in the main menu, then run the check, then set ScreenClass to whatever it ought to be. I also considered using an event listener for this check (all screen listeners register themselves and one of them runs the check and triggers an event with the class info), but it got overly-complicated, because I had to make sure everyone was registered before the check triggered. So for now every screen listener checks for itself. The other problem is that the base/mod classes are stored as names, and I can't find a way to convert a name to the class it identifies, or vice versa. (If there is one, or a even way to get all screen listeners, this will be so much easier.) Since we know which UI we are looking for, we could hardcode the base class name, but how would we get the mod class? The only thing I've come up with is to use Spawn() and get the class of the result. I ran this code with my own mod, a simple UI override that pauses the geoscape whenever "%UNIT has recovered from wounds" pops up: class EventNoticesListener extends UIScreenListener; event OnInit(UIScreen Screen) { if(none == ScreenClass) { CheckUI(); return; } // do something else } event OnReceiveFocus(UIScreen Screen) { // maybe check first this isn't still the main menu before doing anything else // do other things } function CheckUI() { `log("ScreenClass:"@ScreenClass); ScreenClass = class'UIEventNotices'; `log("Updated ScreenClass:"@ScreenClass); if(IsUIOverridden('UIEventNotices')) { // get mod class `log("UI override detected"); ScreenClass = `XCOMGAME.spawn(class'UIEventNotices').Class; // prove it worked `log("New ScreenClass:"@ScreenClass); } } static function bool IsUIOverridden(name UI) { local int i; local XComEngine XEng; XEng = `XENGINE; // iterate by index because I have no idea what this struct is called for(i = 0; i < XEng.ModClassOverrides.Length; ++i) { if(UI == XEng.ModClassOverrides[i].BaseGameClass) { return true; } } return false; } defaultproperties { ScreenClass = none; } And got log statements indicating it worked as I expected. I haven't worked with actors that much for my mods, so for all I know the UI I spawned sticks around for the rest of the game. To say nothing of if 20 screen listeners tried this at the same time. But I thought I'd see what other people thought, and/or if someone much smarter than me can improve on it. Link to comment Share on other sites More sharing options...
Amineri Posted May 2, 2016 Share Posted May 2, 2016 An alternative way compared to searching the ModClassOverrides list is simply to cast the screen to the base class you are expecting. For example, suppose you wanted to create a listener that listens to UISquadSelect, but want it to handle cases when UISquadSelect has been overridden. The following code will do that : class UIScreenListener_SquadSelectPlusOverrides extends UIScreenListener; event OnInit(UIScreen Screen) { if(UISquadSelect(Screen) != none) { // do stuff here } } defaultproperties { ScreenClass = none; } This works because overrides are required to be a child of the class they are overriding, so you can always cast to the parent class. Basically what we are doing here is moving the filtering from the native code backing the UIScreenListeners into the ScreenListener itself. Because the ScreenListeners only check for an exact match, and there's no option to check for child classes (as yet). So there's some performance hit because the filtering is happening in the slower unrealscript, but in practice its not a huge deal since these aren't called as part of any sort of frame-update. If you want to add a special-check case listening for a particular mod's override implementation, you can do that with the code : if(Screen.IsA('UISquadSelect_LW')) { //Do a mod specific action } Note that the argument to IsA is a name, not a Class, so you don't have to have access to the source of the other mod to check this -- just the name of the class. Link to comment Share on other sites More sharing options...
prowler83 Posted May 3, 2016 Author Share Posted May 3, 2016 I know, I was hoping to figure out a method that didn't involve having to do that. But it's since occurred to me that my method would also introduce a ton of logic issues wrt to a UI that at least two mods depend on being created where it isn't expected, so. *shrug* Link to comment Share on other sites More sharing options...
abeclancy Posted May 3, 2016 Share Posted May 3, 2016 You should be able to convert Name to/from Class via one of the following methods (not going to compile and check right now, but from what I remember this should work): local UIArmory_Promotion ScreenInstance; local name ClassName; local class<UIArmory_Promotion> ClassType; ScreenInstance = SomeInstanceAlreadyCreated; ClassType = ScreenInstance.Class; ClassType = `XENGINE.FindClassType('UIArmory_Promotion'); ClassType = `XENGINE.FindClassType(ClassName); ClassName = name(ScreenInstance.Class); ClassName = name(ClassType); ClassName = name(class'UIArmory_Promotion'); Link to comment Share on other sites More sharing options...
abeclancy Posted May 3, 2016 Share Posted May 3, 2016 You can also use something like this if you just want to know if there is a mod overriding a specific class: local class ReplacementClass; ReplacementClass = `XENGINE.GetModReplacementClass(class'UIArmory_Promotion');I'm not sure if this returns None if there is no replacement class, or if it just returns UIArmory_Promotion. But you can just run some tests to determine which. Link to comment Share on other sites More sharing options...
prowler83 Posted May 4, 2016 Author Share Posted May 4, 2016 I had to cast them to strings first, but yeah that worked. Thanks! Link to comment Share on other sites More sharing options...
Recommended Posts