SteelRook Posted February 19, 2016 Share Posted February 19, 2016 Components are a way to add generic data to an object. It isn't strictly speaking an OOP design pattern. For example you might have a base Actor object. You could then add a DrawComponent and a MovementComponent to that Actor. The system could then, while drawing, look for all Actors that have a DrawComponent and give them a chance to draw. Then do the same for Movement. Take a look at https://en.wikipedia.org/wiki/Entity_component_system for more information. Ah, I see... Kind of. So basically "components" are objects within the class which can be added to and removed from at will? I'm familiar with the basic concept (now that I know what it's referring to, I studied programming in Bulgarian :)), and it sort of makes sense the way you describe it. The classes are designed to contain multiples of each other, to be used as necessary. Object-oriented programming at its finest and actually kind of a similar thing to what I teach in the university. Provided that's what it is and I don't just misunderstand. Link to comment Share on other sites More sharing options...
Lucubration Posted February 29, 2016 Share Posted February 29, 2016 Amineri, I need to ask for your help with this real stumper I've run into. I'm using the pattern you've described for creating new GameState component objects and attaching them to the effect's GameState, registering some event listeners and making 'interesting abilities'. This method seems to work very well in action, but I'm having some really, really weird side-effects. Namely, hard CTDs from the game during transitions between the tactical and strategy games. Any time within the first mission or two following the soldier gaining that ability, the game gets into some state where a CTD is inevitable on transition, despite saving/loading the game, taking or not taking that particular soldier on the mission, winning or losing, the soldier living or dying, etc. The only way to get past that transition screen is to then disable the mod, transition, and re-enable the mod. I can also comment out the whole "[Engine.ScriptPackages]" block to keep the scripts from loading entirely, and this also appears to stop the crash during the transition, but I can't find any sub-set of the scripts themselves to comment out to avoid the crash. This was tested as the only mod loaded to reproduce the issue, and I can get it to occur reliably (although it's a somewhat lengthy process). The debug log is not very helpful, and I have no idea how to use the other files that get dumped into the Logs directory on crash. The following are the code for an ability template, the effect it creates, and the gamestate object I use for dealing with callbacks. Can you see anything I'm doing wrong here? Ability template: static function X2AbilityTemplate ZoneOfControl(){local X2AbilityTemplate Template;local X2Effect_ZoneOfControl Effect;`LOG("Lucubration Infantry Class: Zone of Control reaction fire radius=" @ string(default.ZoneOfControlReactionFireRadius));`CREATE_X2ABILITY_TEMPLATE(Template, 'ZoneOfControl');Template.AdditionalAbilities.AddItem(default.ZoneOfControlReactionFireAbilityName);Template.AbilitySourceName = 'eAbilitySource_Perk';Template.eAbilityIconBehaviorHUD = eAbilityIconBehavior_NeverShow;Template.Hostility = eHostility_Neutral;Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_sentinel";Template.ShotHUDPriority = class'UIUtilities_Tactical'.const.CLASS_LIEUTENANT_PRIORITY;Template.AbilityToHitCalc = default.DeadEye;Template.AbilityTargetStyle = default.SelfTarget;Template.AbilityTriggers.AddItem(default.UnitPostBeginPlayTrigger);Effect = new class'X2Effect_ZoneOfControl';Effect.EffectName = 'ZoneOfControl';Effect.AbilityToActivate = default.ZoneOfControlReactionFireAbilityName;Effect.ActionPointName = default.ZoneOfControlActionPointName;Effect.ReactionFireRadius = default.ZoneOfControlReactionFireRadius;Effect.DuplicateResponse = eDupe_Ignore;Effect.BuildPersistentEffect(1, true, false);Effect.SetDisplayInfo(ePerkBuff_Passive, Template.LocFriendlyName, Template.GetMyLongDescription(), Template.IconImage,,,Template.AbilitySourceName);Template.AddTargetEffect(Effect);Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;// NOTE: No visualization on purpose!return Template;}static function X2AbilityTemplate ZoneOfControlShot(){local X2AbilityTemplate Template;local X2AbilityCost_ReserveActionPoints ReserveActionPointCost;local X2Condition_ZoneOfControlRange RangeCondition;`CREATE_X2ABILITY_TEMPLATE(Template, default.ZoneOfControlReactionFireAbilityName);class'X2Ability_DefaultAbilitySet'.static.PistolOverwatchShotHelper(Template);Template.bShowActivation = true;RangeCondition = new class'X2Condition_ZoneOfControlRange';RangeCondition.ReactionFireRadius = default.ZoneOfControlReactionFireRadius;Template.AbilityTargetConditions.AddItem(RangeCondition);ReserveActionPointCost = X2AbilityCost_ReserveActionPoints(Template.AbilityCosts[0]);ReserveActionPointCost.AllowedTypes.AddItem(default.ZoneOfControlActionPointName);return Template;} Effect: class X2Effect_ZoneOfControl extends X2Effect_Persistent;var name AbilityToActivate;var name ActionPointName;var int ReactionFireRadius;simulated protected function OnEffectAdded(const out EffectAppliedData ApplyEffectParameters, XComGameState_BaseObject kNewTargetState, XComGameState NewGameState, XComGameState_Effect NewEffectState){local XComGameState_Unit TargetUnit;local XComGameState_Effect_ZoneOfControl ZoneOfControlEffectState;local X2EventManager EventMgr;local Object ListenerObj;if (GetZoneOfControlComponent(NewEffectState) == none){TargetUnit = XComGameState_Unit(kNewTargetState);// Create component and attach it to GameState_Effect, adding the new state object to the NewGameState containerZoneOfControlEffectState = XComGameState_Effect_ZoneOfControl(NewGameState.CreateStateObject(class'XComGameState_Effect_ZoneOfControl'));ZoneOfControlEffectState.AbilityToActivate = AbilityToActivate;ZoneOfControlEffectState.ActionPointName = ActionPointName;ZoneOfControlEffectState.ReactionFireRadius = ReactionFireRadius;ZoneOfControlEffectState.ShooterRef = TargetUnit.GetReference();NewEffectState.AddComponentObject(ZoneOfControlEffectState);NewGameState.AddStateObject(ZoneOfControlEffectState);EventMgr = `XEVENTMGR;// The gamestate component should handle the callbackListenerObj = ZoneOfControlEffectState;EventMgr.RegisterForEvent(ListenerObj, 'ObjectMoved', class'XComGameState_Effect_ZoneOfControl'.static.OnObjectMoved, ELD_OnStateSubmitted);EventMgr.RegisterForEvent(ListenerObj, 'UnitMoveFinished', class'XComGameState_Effect_ZoneOfControl'.static.OnUnitMoveFinished, ELD_OnStateSubmitted);EventMgr.RegisterForEvent(ListenerObj, 'PlayerTurnEnded', class'XComGameState_Effect_ZoneOfControl'.static.OnPlayerTurnEnded, ELD_OnStateSubmitted);`LOG("Lucubration Infantry Class: Zone of Control passive effect registered for events.");}super.OnEffectAdded(ApplyEffectParameters, kNewTargetState, NewGameState, NewEffectState);}simulated function OnEffectRemoved(const out EffectAppliedData ApplyEffectParameters, XComGameState NewGameState, bool bCleansed, XComGameState_Effect RemovedEffectState){local XComGameState_Effect_ZoneOfControl ZoneOfControlEffectState;local X2EventManager EventManager;local Object ListenerObj;super.OnEffectRemoved(ApplyEffectParameters, NewGameState, bCleansed, RemovedEffectState);ZoneOfControlEffectState = GetZoneOfControlComponent(RemovedEffectState);if (ZoneOfControlEffectState != none){EventManager = `XEVENTMGR;// The gamestate component should unregister its callbacks the callbackListenerObj = ZoneOfControlEffectState;EventManager.UnRegisterFromEvent(ListenerObj, 'ObjectMoved');EventManager.UnRegisterFromEvent(ListenerObj, 'UnitMoveFinished');EventManager.UnRegisterFromEvent(ListenerObj, 'PlayerTurnEnded');RemovedEffectState.RemoveComponentObject(ZoneOfControlEffectState);NewGameState.RemoveStateObject(ZoneOfControlEffectState.ObjectID);`LOG("Lucubration Infantry Class: Zone of Control passive effect unregistered from events.");}}static function XComGameState_Effect_ZoneOfControl GetZoneOfControlComponent(XComGameState_Effect Effect){if (Effect != none)return XComGameState_Effect_ZoneOfControl(Effect.FindComponentObject(class'XComGameState_Effect_ZoneOfControl'));return none;} GameState: class XComGameState_Effect_ZoneOfControl extends XComGameState_BaseObjectconfig (LucubrationsInfantryClass);var name AbilityToActivate;var name ActionPointName;var int ReactionFireRadius;var StateObjectReference ShooterRef;var privatewrite array ReactionFireTargets;function EventListenerReturn OnObjectMoved(Object EventData, Object EventSource, XComGameState GameState, Name EventID){local XComGameStateHistory History;local StateObjectReference AbilityRef;local XComGameStateContext_Ability ActiveAbilityContext;local XComGameState_Ability ActiveAbilityState;local XComGameState_Unit Shooter, Target;local name CanShootCode;local XComGameState NewGameState;local int TargetDistanceInTiles;History = `XCOMHISTORY;//`LOG("Lucubration Infantry Class: Zone of Control 'ObjectMoved' event listener delegate invoked.");// Grab the target unitTarget = XComGameState_Unit(EventData);if (Target == none){//`LOG("Lucubration Infantry Class: Zone of Control not activated (no target).");return ELR_NoInterrupt;}// Check if the target has already been fired atif (ReactionFireTargets.Find('ObjectID', Target.ObjectID) != INDEX_NONE){//`LOG("Lucubration Infantry Class: Zone of Control not activated against unit " @ Target.GetFullName() @ " (Zone of Control already fired at this unit).");return ELR_NoInterrupt;}// Grab the shooter unitShooter = XComGameState_Unit(GameState.GetGameStateForObjectID(ShooterRef.ObjectID));if (Shooter == none)Shooter = XComGameState_Unit(History.GetGameStateForObjectID(ShooterRef.ObjectID));if (Shooter == none){`LOG("Lucubration Infantry Class: Zone of Control not activated against unit " @ Target.GetFullName() @ " (no shooter).");return ELR_NoInterrupt;}// Check if the target is an enemy to the shooterif (!Shooter.TargetIsEnemy(Target.ObjectID)){//`LOG("Lucubration Infantry Class: Zone of Control not activated by unit " @ Shooter.GetFullName() @ " against unit " @ Target.GetFullName() @ " (not an enemy).");return ELR_NoInterrupt;}// Check if the target is an enemy to the shooterif (Target.IsMindControlled()){//`LOG("Lucubration Infantry Class: Zone of Control not activated by unit " @ Shooter.GetFullName() @ " against unit " @ Target.GetFullName() @ " (target is mind controlled, probably really our friend).");return ELR_NoInterrupt;}// Check range from shooter to target. Radius in config is given in tilesTargetDistanceInTiles = Shooter.TileDistanceBetween(Target);if (TargetDistanceInTiles > ReactionFireRadius){//`LOG("Lucubration Infantry Class: Zone of Control not activated by unit " @ Shooter.GetFullName() @ " against unit " @ Target.GetFullName() @ " (out of range, " @ string(TargetDistanceInTiles) @ " > " @ string(ReactionFireRadius) @ ").");return ELR_NoInterrupt;}// Get the Zone of Control active ability from the shooter unitforeach Shooter.Abilities(AbilityRef){ActiveAbilityState = XComGameState_Ability(History.GetGameStateForObjectID(AbilityRef.ObjectID));if (ActiveAbilityState.GetMyTemplateName() == AbilityToActivate)break;ActiveAbilityState = none;}if (ActiveAbilityState == none){//`LOG("Lucubration Infantry Class: Zone of Control not activated by unit " @ Shooter.GetFullName() @ " against unit " @ Target.GetFullName() @ " (no reaction fire ability).");return ELR_NoInterrupt;}if (ActionPointName != ''){//`LOG("Lucubration Infantry Class: Zone of Control adding reserved action point to unit " @ Shooter.GetFullName() @ ".");// Grant an ability point for the shooterNewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState(string(GetFuncName()));Shooter = XComGameState_Unit(NewGameState.CreateStateObject(Shooter.Class, Shooter.ObjectID));Shooter.ReserveActionPoints.AddItem(ActionPointName);NewGameState.AddStateObject(Shooter);}CanShootCode = ActiveAbilityState.CanActivateAbilityForObserverEvent(Target, Shooter);if (CanShootCode != 'AA_Success'){// If the shooter can't fire at the target, pick up our toys and go homeHistory.CleanupPendingGameState(NewGameState);//`LOG("Lucubration Infantry Class: Zone of Control not activated by unit " @ Shooter.GetFullName() @ " against unit " @ Target.GetFullName() @ " (shooter can't fire at this unit: " @ string(CanShootCode) @ ").");return ELR_NoInterrupt;}`TACTICALRULES.SubmitGameState(NewGameState);//`LOG("Lucubration Infantry Class: Zone of Control reserved action point added to unit " @ Shooter.GetFullName() @ ".");// Register the target unit so we don't shoot at them twiceReactionFireTargets.AddItem(Target.GetReference());//`LOG("Lucubration Infantry Class: Zone of Control target added " @ Target.GetFullName() @ ".");// Perform reaction fire against the targetActiveAbilityContext = class'XComGameStateContext_Ability'.static.BuildContextFromAbility(ActiveAbilityState, Shooter.ObjectID);if (!ActiveAbilityContext.Validate()){// Testing always seems to display this message, but the movement observer triggers pistol overwatch anyways. I guess it'll do?//`LOG("Lucubration Infantry Class: Zone of Control not activated by unit " @ Shooter.GetFullName() @ " against unit " @ Target.GetFullName() @ " (reaction fire ability context not valid)?");return ELR_NoInterrupt;}`TACTICALRULES.SubmitGameStateContext(ActiveAbilityContext);`LOG("Lucubration Infantry Class: Zone of Control activated by unit " @ Shooter.GetFullName() @ " against unit " @ Target.GetFullName());return ELR_NoInterrupt;}function EventListenerReturn OnUnitMoveFinished(Object EventData, Object EventSource, XComGameState GameState, Name EventID){local XComGameState_Unit Target;local int i;//`LOG("Lucubration Infantry Class: Zone of Control 'UnitMoveFinished' event listener delegate invoked.");Target = XComGameState_Unit(EventData);if (Target == none){//`LOG("Lucubration Infantry Class: Zone of Control target not removed (no target).");return ELR_NoInterrupt;}i = ReactionFireTargets.Find('ObjectID', Target.ObjectID);if (i == INDEX_NONE){//`LOG("Lucubration Infantry Class: Zone of Control target " @ Target.GetFullName() @ " not removed (not a target).");return ELR_NoInterrupt;}// After a unit has finished moving, remove it from our reaction fire targets array. If it moves again, it can be shot at againReactionFireTargets.Remove(i, 1);//`LOG("Lucubration Infantry Class: Zone of Control target removed: " @ Target.GetFullName() @ ".");return ELR_NoInterrupt;}function EventListenerReturn OnPlayerTurnEnded(Object EventData, Object EventSource, XComGameState GameState, Name EventID){//`LOG("Lucubration Infantry Class: Zone of Control 'PlayerTurnEnded' event listener delegate invoked.");//`LOG("Lucubration Infantry Class: Zone of Control targets cleared.");// Just to make sure we didn't miss anything, we're going to clear our reaction fire targets after every turnReactionFireTargets.Length = 0;return ELR_NoInterrupt;} I should note that when I comment out the "meat" of the OnEffectAdded and OnEffectRemove methods so that the gamestate component object isn't created and the callbacks aren't registered prior to the soldier taking the ability the crashes do not occur afterwards (but of course, the ability doesn't do anything), so I'm fairly sure that the gamestate objects or callbacks are involved the situation. Link to comment Share on other sites More sharing options...
davidlallen Posted February 29, 2016 Share Posted February 29, 2016 Not sure why Amineri posted this in the general discussion forum, but she seems to describe exactly the same thing, and how to fix it:http://forums.nexusmods.com/index.php?/topic/3853330-psa-you-have-to-garbage-collect-your-components-yourself/ Link to comment Share on other sites More sharing options...
Lucubration Posted February 29, 2016 Share Posted February 29, 2016 (edited) Not sure why Amineri posted this in the general discussion forum, but she seems to describe exactly the same thing, and how to fix it:http://forums.nexusmods.com/index.php?/topic/3853330-psa-you-have-to-garbage-collect-your-components-yourself/ Oh.... OH! That definitely seems related. I had thought I was removing all my state objects, but I can at least take another look to see if I'm missing something. Thank you for pointing Amineri's post out to me. Edit: Looks like I was marking them for removal but maybe not submitting the new gamestate via the `GAMERULES/`HISTORY? That sound right, Amineri (if you wind up reading this?) I'm trying to implement your clean-up stuff in the end of my method, but I'm probably doing it wrong because it's not working out yet. Edit: Looking more into it, I shouldn't need to submit the new gametate myself as long as I remove the object from it, because it gets submitted up the stack at OnUnitRemovedFromPlay. So maybe back to square one: banging head on wall. Edited February 29, 2016 by Lucubration Link to comment Share on other sites More sharing options...
davidlallen Posted February 29, 2016 Share Posted February 29, 2016 You owe me one set of viper spit and an ability tree :-) Link to comment Share on other sites More sharing options...
Lucubration Posted February 29, 2016 Share Posted February 29, 2016 Hey man, if I can get this to stop killing peoples' games tonight, I'll sleep well and get started on it tomorrow. :) Link to comment Share on other sites More sharing options...
Lucubration Posted February 29, 2016 Share Posted February 29, 2016 (edited) Got it. Took too long, but I darned well got it. OnEffectRemoved isn't always called at the end of a mission. If the unit extracts it is called, but on missions where the action just ends, effects aren't removed because the unit isn't "removed from play". I had each event state additionally hook onto 'TacticalGameEnd' and clean itself up when tactical gameplay, well, ends. Everything is getting cleaned up as it should and I've made it through eight missions straight with no CTD on transition. Just going to thoroughly test the related skills' callbacks now. Edited February 29, 2016 by Lucubration Link to comment Share on other sites More sharing options...
Amineri Posted February 29, 2016 Author Share Posted February 29, 2016 Not sure why Amineri posted this in the general discussion forum, but she seems to describe exactly the same thing, and how to fix it:http://forums.nexusmods.com/index.php?/topic/3853330-psa-you-have-to-garbage-collect-your-components-yourself/ Yes, posting it there was an embarrassing mistake ... :sweat: Link to comment Share on other sites More sharing options...
Amineri Posted February 29, 2016 Author Share Posted February 29, 2016 Got it. Took too long, but I darned well got it. OnEffectRemoved isn't always called at the end of a mission. If the unit extracts it is called, but on missions where the action just ends, effects aren't removed because the unit isn't "removed from play". I had each event state additionally hook onto 'TacticalGameEnd' and clean itself up when tactical gameplay, well, ends. Everything is getting cleaned up as it should and I've made it through eight missions straight with no CTD on transition. Just going to thoroughly test the related skills' callbacks now. Another method you can use to clean up after effect components is what we used for the effect components in officer pack. XComGameState_Effect is always cleaned up after a tactical mission because of its inclusion in TransientTacticalClasses. So, for OfficerPack, I've included an XComGame.ini config with the following : [XComGame.XComGameInfo] ;Transient tactical classes specify objects that should be set as removed in the history upon leaving a tactical session +TransientTacticalClassNames=XComGameState_Effect_FocusFire +TransientTacticalClassNames=XComGameState_Effect_Scavenger +TransientTacticalClassNames=XComGameState_Effect_Collector If you look in DefaultGame.ini, you'll see that XComGameState_Effect is also on the list, which is units don't clean up after themselves post-mission. For debugging purposes, I'd also added the XComGameState_Unit_LWOfficer component as a TransientTacticalClassNames object, and this prevented the CTD on switch from tactical to strategy maps, which indicated the same root cause. Link to comment Share on other sites More sharing options...
Lucubration Posted February 29, 2016 Share Posted February 29, 2016 Got it. Took too long, but I darned well got it. OnEffectRemoved isn't always called at the end of a mission. If the unit extracts it is called, but on missions where the action just ends, effects aren't removed because the unit isn't "removed from play". I had each event state additionally hook onto 'TacticalGameEnd' and clean itself up when tactical gameplay, well, ends. Everything is getting cleaned up as it should and I've made it through eight missions straight with no CTD on transition. Just going to thoroughly test the related skills' callbacks now. Another method you can use to clean up after effect components is what we used for the effect components in officer pack. XComGameState_Effect is always cleaned up after a tactical mission because of its inclusion in TransientTacticalClasses. So, for OfficerPack, I've included an XComGame.ini config with the following : [XComGame.XComGameInfo] ;Transient tactical classes specify objects that should be set as removed in the history upon leaving a tactical session +TransientTacticalClassNames=XComGameState_Effect_FocusFire +TransientTacticalClassNames=XComGameState_Effect_Scavenger +TransientTacticalClassNames=XComGameState_Effect_Collector If you look in DefaultGame.ini, you'll see that XComGameState_Effect is also on the list, which is units don't clean up after themselves post-mission. For debugging purposes, I'd also added the XComGameState_Unit_LWOfficer component as a TransientTacticalClassNames object, and this prevented the CTD on switch from tactical to strategy maps, which indicated the same root cause. Ah, that helps makes sense of why cleaning up effect state is handled so inconsistently in script. While I think I've got it nailed with the explicit removal for now, I may as well also add those to the next release "just in case", since I'm not persisting any of that outside of tactical. Thanks once again. Link to comment Share on other sites More sharing options...
Recommended Posts