Jump to content

Request For Event Manager Help


abeclancy

Recommended Posts

Short version: Event Manager accepts my event registration, sends a few events to it, then deletes my registration at some point during game execution. How/Why/When does the Event Manager delete my registration, and how can I prevent it from doing so?

 

Long version: I'm trying to register the event SoldierTacticalToStrategy. The event does get properly registered, and appears at the end of the list when EventManagerDebugString('SoldierTacticalToStrategy') is called and logged. When a tactical mission ends, this event fires once for each soldier that was on the mission. Upon immediately going in to another mission and ending THAT mission, there are no events being fired, and my registration is no longer in the debug string output.

 

I guess the biggest questions are, how and when do event registrations get purged? If you add an event registration on the main shell menu, do those registrations get purged when loading a save game? What are the rules for using events properly, eg the most ideal times to add registrations, times during which they will get cleared, etc? Why doesn't bPersistent=true actually persist the registrations? It looks like "`XEVENTMGR.ResetToDefaults(false);" is called in a few places in the vanilla game code, but theoretically this should KEEP bPersistent=true registrations intact.

 

I'm basically forced to override functions all over the place to get my code to execute reliably. If I can figure out how these events actually work, I can stop overriding classes to help reduce the possibilities of mod conflicts.

 

Link to comment
Share on other sites

i'd just initiate a new listener for each time you get into a mission, there are a few screen listeners you can use to do so. it seems that a lot of objects/data gets purged regularly between the different shells (main game, tactical and strategic) for example the UI stack. for getting Red Fog to initiate and keep going i set up a listener to the AbilityBar TacHUD UI class because it loads early on the tac mode

Link to comment
Share on other sites

Did a bunch of research. Came to the following conclusions:

 

= Event registrations are confusing.

 

= Event registrations can sometimes (?) persist through quitting the game and relaunching it (possibly with saving/reloading too?), even registrations whose sources are not game state objects. If you register an event with an event source that is not a game state object, then this won't do anything abnormal because the registration will be removed during the next "clean up" phase of the event manager, as the game state object ID of the registration is 0. But if you register an event source that is a game state object, the game state's object ID will be written with the event registration and it may persist.

If someone with additional information regarding this could elaborate, I would appreciate it for the purposes of this information thread. Since I don't currently plan on using game state objects for event registration, this doesn't affect me much so I didn't test this persistence fully.

 

= Attempting to register the same event (event name/source object) twice causes the second registration to overwrite the first. Keep this in mind if you ever register an event once with bPersist=true then a second time with bPersist=false. Your registration will be active but with bPersist=false.

Note that this also includes trying to use a different function with the same source object/event name. The function defined in the 2nd call to RegisterForEvent() will be the target of the event registration, while the function defined in the 1st call will be nowhere to be found.

= There is either a bug in the native C++ code for AnyListenersForEvent() that can cause the function to return 'true' even though there is no combination EventID/EventSource defined in the event manager, OR there is a very, VERY large caveat to using this function.

At first glance, it would appear that this function can tell you if a specific object instance (event source) has an event registration active for it. However, what this function appears to actually check is only the object's game state ID values. This means that if you have two different object instances that are not game states, and you attempt to register two events to these two objects, the registration will be successful for both objects. But if you only register one event to one object, then attempt to use AnyListenersForEvent(), it will return true for both objects because they both have the same effective game state object ID (0).

This seems like a bug, because you can in fact have multiple event registrations against multiple objects with game state ID (0), but this function doesn't seem to actually use the fact that these are separate objects during its comparison.

The only workaround I know of right now is to manually try and detect your event listener via one of the debug functions attached to the event manager, that return string representations of the registration system:

EventExistsStr  = "UnitDied: EventID: 'UnitDied'  Priority: 50  Deferral: 'ELD_Immediate'  EventDelegate: 'No Valid Object' 'DoDebugEventsCallback' 'XComGame.MyMod_OptionsMenu'";
EventExists = InStr(`XEVENTMGR.AllEventListenersToString('UnitDied'), EventExistsStr) >= 0;

As an example, the following shows the incorrect behavior:

`XEVENTMGR.RegisterForEvent(DiffObject, 'UnitDied', Func);
EventExists1 = `XEVENTMGR.AnyListenersForEvent('UnitDied', SelfObject);
EventExists2 = `XEVENTMGR.AnyListenersForEvent('UnitPanicked', SelfObject);
`log("Checking registration persistence (AnyListenersForEvent) -- 1(" $ EventExists1 $ ") 2(" $ EventExists2 $ ")");
//[0565.00] ScriptLog: Checking registration persistence (AnyListenersForEvent) -- 1(True) 2(False)
// should be 1(False) 2(False)

Neither DiffObject nor SelfObject are game state objects, but because they both resolve to the same game state object ID (0), the event manager function cannot correctly determine that SelfObject actually has no events defined for it.

 

= ALL event registrations are cleared as soon as the player loads any save game, regardless of bPersist or game state object IDs. There is no apparent event or callback that can be used to determine that this occurred or exactly when it occurs. This seems to be desirable for the most part, as the attached game state object IDs of the registrations for the loaded savegame may not match the object IDs of the current game that was left. This allows the event registration to clear itself out entirely and start over from scratch.

Currently do not know exactly where this registration clearing occurs in the code's execution, haven't looked in to this yet.

 

= When a tactical mission ends, event registrations that are defined with bPersist=false are cleared. The last two events (that I know of) that are called are 'TacticalGameEnd' followed by 'PlayerTurnEnded'. After these two events are called, the end-of-mission popup is displayed. Upon clicking it to return to the skyranger loading screen, the event registration purges in the following stack:

State XComGame.X2TacticalGameRuleset:EndTacticalGame
Function XComGame.X2TacticalGameRuleset:EndTacticalGame.NextSessionCommand

= When a tactical mission begins, event registrations with bPersist=false are cleared. HOWEVER, a second stage of registration-clearing occurs, during which objects that are not actually game state objects are purged from the event registration.

First, only bPersist=false registrations are purged. The last few events (that I know of) that are called are 'OnSkyrangerArrives' (when the skyranger arrives at the mission destination and you are given one final chance to cancel the mission), 'LaunchMissionSelected' (when you click the final launch button), and 'UnitChangedTeam' (which is called just afterwards). Then, event registrations with bPersist=false are purged in the following stack:

Function XComGame.UIMovie:FlashRaiseMouseEvent
Function XComGame.UIButton:OnMouseEvent
Function XComGame.UIButton:Click
Function XComGame.UISkyrangerArrives:OnLaunchClicked
Function XComGame.XComGameState_MissionSite:ConfirmMission
Function XComGame.XGStrategy:LaunchTacticalBattle

Note that at this point in time bPersist=true registrations remain in the event manager. The skyranger loading screen then occurs. Upon clicking the launch/continue button from within the skyranger screen, the screen goes black and the tactical mission loads, however if you press ESC before the screen is fully closed out and faded to black, the game state will pause.

While bPersist=true registrations remain in the event manager, if you've hooked up a debug function to this pause menu somewhere, you will find that the current state of the event manager is such that these persisted registrations are completely non-functional if the source object of the registrations was not a game state object. And after you press ESC again to resume, the tactical mission will load and the event manager will do a clean-up phase, during which these non-functional registrations are removed.

From the output of AllEventListenersToString(), you can see how this process works. When a registration is first made, the source object is indicated but is given a zero ID:

UnitDied: EventID: 'UnitDied'  Priority: 50  Deferral: 'ELD_Immediate'  EventDelegate: 'No Valid Object' 'DebugFunc' 'XComGame.MyModClass'  SourceObject: 'MyModClass_0'(0)  PreFilterObject: 'No Valid Object'(0)

Then, if you've paused the game during the skyranger loading screen just after you press a button to continue but before the screen has completely faded to black, you will notice that the source objects for the registration have had their references changed from their actual instances, to 'No Valid Object'.

UnitDied: EventID: 'UnitDied'  Priority: 50  Deferral: 'ELD_Immediate'  EventDelegate: 'No Valid Object' 'DebugFunc' 'XComGame.MyModClass'  SourceObject: 'No Valid Object'(0)  PreFilterObject: 'No Valid Object'(0)

Then, as soon as the skyranger loading screen vanishes, the cleanup phase of the event manager will be run, and these (essentially NULL) event sources will be completely removed from the event manager registration list.

The cause for this as far as I can tell, is some native C++ process has gone through and marked all zero-id source object registrations as invalid for later cleanup. Why? I'm not entirely sure. But the end result is that event registrations that were perfectly valid and desirable during the strategy game have been removed upon entry to the tactical game.

 

= Getting around these event registration issues is doable.

One way to get around this is to only use event registrations that are attached to game state objects. However, this is not ideal for many mods that do not modify the game state and do not wish to add game state objects that will persist in the save game.

Another way to get around these registration issues is to use screen listeners to inject your event registrations as soon as is feasibly possible in the strategy or tactical layers, or the main shell menu if that is what you mod deals with.

I am just getting in to using the event manager, so I honestly do not know which screen listeners are best for these purposes, or how best to use game state objects to ensure that your event registrations properly persist. But what I do know is that after researching this, the event manager system seems to be slightly less than straight-forward for mods to use, that do not need to use game states. At least until you learn its caveats.

Edited by abeclancy
Link to comment
Share on other sites

Did a little bit of looking at the UIScreens that are pushed to the screen stack, to find the best screens to listen to in order to ensure that any event registrations are maintained.

Main Menu:
UIFinalShell / UIShell - always present (and UIFinalShell extends UIShell)

Tactical:
UIDropShipBriefing_MissionStart - after strategy ends, before tactical begins (with UIEventNotices)
UITacticalHUD / UIUnitFlagManager - always present

Strategy:
UIDropShipBriefing_MissionEnd - after tactical ends, before strategy begins (without UIEventNotices)
UIAvengerHUD - always present

Seems like your mod will need to set up UIScreenListeners for the main menu, strategy layer, and tactical layers, depending on your requirements. Likely this means the UIShell, UITacticalHUD, or UIAvengerHUD screens.

The UIDropShipBriefing_MissionStart/MissionEnd screens ARE present before the other strategy/tactical screens, but there are a couple issues using those screens for anything other than event registrations. First, these screens are not present when you load a game into the tactical or strategy screens. It might also be the case that not all mission loading/ending passes through these screens, I didn't test special missions and I don't know if someone might mod a new type of mission start/end screen to use.

Second, the mission has not yet been constructed at this point, so you don't have access to the generated world data at this time. Generic event registrations can be done here, especially if you need some events that only occur super early in the tactical or strategy loading, but this probably isn't the best place to do event registration.

I've also noticed that UIEventNotices is a screen that is always present on the screen stack, regardless of whether we are in the main menu, strategy, or tactical layers. It is always present and always the lowest screen, and when the game state changes from Main Menu to Strategy to Tactical, a new instance of UIEventNotices is created and placed on the screen stack after it is cleared.

The exception to this is when UIDropShipBriefing_MissionEnd is on the stack, in which case there is no UIEventNotices screen. (UIDropShipBriefing_MissionStart seems to have an instance of UIEventNotices on the screen, but not MissionEnd for some reason. I have to wonder if this is a bug, and the UIEventNotices is just accidentally missing )

UIEventNotices being removed from the stack also correlates with the presentation layer changing the game mode from eUIMode_Strategy to Tactical to Shell, so it is possible to determine the current game state just through that one UIScreen instance alone.

Seems like the most important thing to figure out when using event registrations tied to screen listeners, is to make sure that the timing of the listeners and registrations makes sense. For instance if you are trying to capture the SoldierTacticalToStrategy event, you actually can't use the UIAvengerHUD screen to set up your event registration, as that event fires before the screen is added to the stack. You'd have to register while you are within the tactical layer, and bPersist=true the registration so that it will persist through the manager's purging of all bPersist=false registrations between the tactical-strategy layers. You can always remove the registration afterwards manually if you do not intend it to be permanent.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...