Amineri Posted February 28, 2016 Share Posted February 28, 2016 So I've been trying to trace down a particularly irksome bug in the OfficerPack mod these last few days, and I thought I'd share the results with the modding community. At a high level, what happens is this :After a dismissing a soldier with leader training, at the end of the next tactical mission, the game crashes. Loading the save without the OfficerPack prevents the crash from occurring. Under the hood, what's going on is the following sequence of events :Unit gets dismissed. Unit State bRemoved is set true, but the LWOfficer component bRemoved remains false. At some point, Unit State gets obliterated from History During History validation (when switching from tactical to Avenger map), an assertion is thrown because there is a component with a reference to a parent that doesn't exist. The assertion triggers program exit.A bit of info on Removed vs Obiliterated... XComGameState.uc has this to say about Removing gamestates: /// This method is intended to be the typical path for 'removing' a state object from the history. A new state is created, and the 'bRemoved' flag is set on the state object /// indicating that it should no longer appear in lists of 'current' state objects. The state object will still show up if history frames are examined prior to its removal. /// <param name="ObjectID">Unique indentifier associated with the object state to be marked as removed</param> native final function RemoveStateObject(int ObjectID); In contrast, Obliterate is in XComGameStateHistory.uc, and has this to say : /// Removes all information related to the last Count states from the history. /// <param name="Count">Number of states to pop from the history.</param> native final function ObliterateGameStatesFromHistory(optional int Count = 1); In theory deleted units should be hanging around with just their bRemoved flag set, so the past states can be accessed, but at some point the unit state is getting obliterated. When forcing a load of the tactical save without the Officer mod, the LWOfficer component can't be deserialized, so it gets discarded from History, thus preventing the assertion from triggering and halting execution. I was able to get the same result without removing the mod by adding the following to Game.ini: +TransientTacticalClassNames=XComGameState_Unit_LWOfficer This forces a purge of all LWOfficer components from History on the end of tactical missions, which happens before the validation code that triggers the assertion. Unfortunately, this isn't a viable "fix" because it also wipes all officer-related data -- so all officers would lose their ranks, etc. However, searching the History at the point of the tactical save fails to find the officer component that is still hanging around, so I can't really patch up saves that have gotten to that point. The native accessor in XComGameStateHistory apparently can't find a component gamestate whose parent has been obliterated. However, if I search History just after Dismissing a unit, I can find the Officer component, and can verify that the component bRemoved flag is not set, but that its owning Unit parent's bRemoved flag is set. So, looks like I'll have to garbage collect my own components, which is kind of a pain... The search method I'm using to find my component states looks like this : local XComGameState_Unit_LWOfficer OfficerState; foreach History.IterateByClassType(class'XComGameState_Unit_LWOfficer', OfficerState,,true) { ... } I created a static garbage collection and validation method to my LWOfficerUtilities class, which uses the above to look through the history for any LWOfficer component states that are attached to unit states that don't exist or have been flagged for removal, and then marks the component state for removal. static function GCandValidationChecks() { local XComGameStateHistory History; local XComGameState NewGameState; local XComGameState_Unit UnitState; local XComGameState_Unit_LWOfficer OfficerState; `LOG("LWOfficerUtilities: Starting Garbage Collection and Validation."); History = `XCOMHISTORY; NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Officer States cleanup"); foreach History.IterateByClassType(class'XComGameState_Unit_LWOfficer', OfficerState,,true) { `LOG("LWOfficerUtilities: Found OfficerState, OwningObjectID=" $ OfficerState.OwningObjectId $ ", Deleted=" $ OfficerState.bRemoved); //check and see if the OwningObject is still alive and exists if(OfficerState.OwningObjectId > 0) { UnitState = XComGameState_Unit(History.GetGameStateForObjectID(OfficerState.OwningObjectID)); if(UnitState == none) { `LOG("LWOfficerUtilities: Officer Component has no current owning unit, cleaning up state."); // Remove disconnected officer state NewGameState.RemoveStateObject(OfficerState.ObjectID); } else { `LOG("LWOfficerUtilities: Found Owning Unit=" $ UnitState.GetFullName() $ ", Deleted=" $ UnitState.bRemoved); if(UnitState.bRemoved) { `LOG("LWOfficerUtilities: Owning Unit was removed, Removing OfficerState"); NewGameState.RemoveStateObject(OfficerState.ObjectID); } } } } if (NewGameState.GetNumGameStateObjects() > 0) `GAMERULES.SubmitGameState(NewGameState); else History.CleanupPendingGameState(NewGameState); } I had to do this in my case because there are no TriggerEvent calls that occur in the FireStaff call chain for me to listen to to call RemoveStateObject for. I also had rather expected that calling RemoveStateObject on a GameState would also remove all of its components, but this seems to not be the case. For my particular case, the only situation in which units are removed is via the UIArmory_MainMenu dismiss button (killed units stick around so they can be displayed in the memorial), so I added a UIScreenListener that listens to UIArmory_MainMenu, and trigger the GC routine OnRemoved : event OnRemoved(UIScreen Screen) { //clear reference to UIScreen so it can be garbage collected ParentScreen = none; //garbage collect officer states in case one was dismissed class'LWOfficerUtilities'.static.GCandValidationChecks(); } ------------------------ I know that using gamestate components is kind of "bleeding edge" right now, but figured I'd toss this out there as a consideration for those of you considering using them. Link to comment Share on other sites More sharing options...
Amineri Posted February 28, 2016 Author Share Posted February 28, 2016 Whoopsie, I meant to post this over in Mod Talk ... :sweat: Link to comment Share on other sites More sharing options...
Deleted32045420User Posted February 29, 2016 Share Posted February 29, 2016 (edited) ahh Thanks for the PSA, would have been a shame to see saves crash when units are deleted. Using components for Hidden Potential where each unit holds it's own randomised stat progression through all the ranks. Gonna take that code of yours and configure it the same way on my end Edited February 29, 2016 by Guest Link to comment Share on other sites More sharing options...
MachDelta Posted February 29, 2016 Share Posted February 29, 2016 Manual garbage collection in UnrealScript? Heresy! :ninja: Seriously though thank you for sharing. I'd be interested to know why Firaxis would wipe a unit from the history, given that their documentation implies that nothing unique should ever be deleted. Or maybe I just read it wrong. Link to comment Share on other sites More sharing options...
Lucubration Posted February 29, 2016 Share Posted February 29, 2016 (edited) I was really banging my head against this one yesterday/today, Amineri. Thank you - a lot - for this post. I'm fairly sure it saved me days of extra trial-and-error. Edited February 29, 2016 by Lucubration Link to comment Share on other sites More sharing options...
kosmo111 Posted February 29, 2016 Share Posted February 29, 2016 Thanks for this tip. I just released a mod using components in much the same way you do for officers-- I never even thought to test dismissing a unit with my new component. Link to comment Share on other sites More sharing options...
Recommended Posts