Jump to content

PSA: Adding abilities when entering tactical, example and case study


UlfrStrongarm

Recommended Posts

Hi there fellow Nexus XCOM2 modders!

 

I've got a little story to tell! Just recently I hit a brick wall on my Ironman run - to be exact, my specialist (brand new Colonel and all) hit a brick wall and by that I mean getting shafted by a Muton. And by shafted I mean flanked, critically hit and left for dead - bleeding out in 4 turns. Typical... and it just happened so that nobody else had a Medikit in their inventory and even though the poor bastard is lying right next to his own Medikit there's nothing that can be done to save him. This is bull! I thought... :mad: and dug up the modding tools and decided to fix this gross injustice :laugh:

 

So I came up with my first "real" mod (I had already made a little country mod to add some more nationalities but that doesn't count for much). Anyway I wanted to share the code and things that I found out and the methods I used while making the mod.

 

The mod in question is StabilizeMe.

 

Workshop: http://steamcommunity.com/sharedfiles/filedetails/?id=654174276

Source code and documentation: https://github.com/Jusas/XCOM2StabilizeMe

 

 

The problem was:

How to make soldiers that do not own a Medikit able to stabilize a soldier that is bleeding out and has a Medikit with charges?

 

Figuring out the solution:

Ok, so the problem was obvious. Having no real point of reference of how to achieve it, I just first created a new mod project and then ran a search on all the source files for "stabilize". That led me to the Ability classes and I figured out that stabilize and heal were abilities. Medikit itself was a weapon, with the abilities added to it. Additionally I was curious about the other, "hidden" abilities that soldiers had, abilities that do not really show up until the soldier is adjacent to the ability related elements, like doors friendly bodies. Turns out these are default abilities for units (check out X2Ability_DefaultAbilitySet.uc) and especially the CarryUnit ability helped me understand how those abilities worked. Anyway, I kept digging around to find more information about abilities and how they work and eventually figured out that the right (or at least one) way to do this would be to create a new ability and add it to the standard set of abilities for soldiers.

 

It turned out that the ability itself was pretty easy to hack. For the most part it was just copying the 'MedikitStabilize' ability with only minor adjustments; a) it was an ability attached to a soldier, not a weapon and therefore got it's range calculated in another way and b) I needed to create a new class extending X2Condition that handled the checking that the target had a 'MedikitStabilize' ability (or 'GremlinStabilize', that's important - a Specialist doesn't have both) and that the ability had at least one charge left, and finally that the target soldier was right next to the source soldier. Consuming charges of the Medikit in this particular case didn't concern me; the unit would get stabilized and after that the existence of the ability or the charges in the Medikit would no longer matter.

 

That part was the easiest. The hardest part was actually getting the ability applied at the right time in the right way. Looking at the existing code I'd have loved to add the ability using a .ini entry (XComGameData_CharacterStats.ini seemed promising) - there was the array of Abilities for X2CharacterTemplate in the source file but it wasn't flagged with config. So much for the easy way. Additionally I'm not sure it would have been enough, doesn't the template only get applied for newly created instances? Also somehow injecting this in code seemed problematic as the template creation methods were static. I needed a way to inject the ability to all soldiers and this didn't seem to be the right track. I started to think about using events.

 

So I dug into events. I spent a long night with this one, wondering why things didn't work when they were supposed to. I started by trying to find a suitable event to work with, searching for TriggerEvent calls in the code to see what events were flying around. My goal was to be able to apply the ability globally, meaning it wouldn't matter what the game state was - of course with my own situation in mind meaning that it had to work instantly when I loaded my save. Turned out it was harder than I thought or at least felt like it - in the end I realized I had wasted time meaninglessly trying to chase different events when in the end all I really needed was a UIScreenListener, specifically listening to the UITacticalHUD ScreenClass (found out about this by creating a ScreenListener that logged every ScreenClass). Whenever you enter the tactical mode, that ScreenClass gets added and it proved a reliable delivery method. In the end that listener was all I needed; I didn't need to apply the ability to every soldier in existence, all I needed to do was to ensure that the soldiers entering the mission (the player's selected squad) got the ability if they already hadn't got it when the tactical mode started.

 

A thing to note: OnTacticalBeginPlay event was a miss, I recall it only fired when you launched a new mission and didn't fire at all when you loaded a game. Similarly OnUnitBeginPlay wasn't firing when loading a game.

 

Then there was the History mess. I guess I didn't realize how important it was to get all the History related operations done right. Lessons learnt here:

  • A new XComGameState has to be created every time you want to fiddle with something like adding abilities to units. You apply the changes to the state and then submit the state, the steps being
    • Create new game state
    • Create new unit state
    • Add the unit state to the new game state
    • Initialize the ability
    • Add the game state to the History
  • There are subtle differences in how exactly that should be done. I'm still puzzled why for example this didn't work in all game situations (my Ironman savegame for example) but most of the time did :
    TacticalRules.SubmitGameState(NewGameState);
    But this worked in all situations:
    History.AddGameStateToHistory(NewGameState);

Finally I got everything working right with that last line of code.

 

Anyway making this mod was a nice learning experience. I know it isn't perfect, I've already realized that the code doesn't cover all cases (eg. another modder makes a new ability for the Medikit which replaces MedikitStabilize like GremlinStabilize already does, checking for GremlinStabilize and MedikitStabilize won't hack it anymore, should check for the actual Medikit weapon instead of the abilities) and there's no animation for the ability so it's a little bit crude. I hope this code can help others, you're free to use it as you like and I hope the documentation is of some use. I originally merely intended to make this quickly and for my own use only to save my poor Colonel from great injustice :blush: but ended up released this in the Workshop in case there are others who aren't okay with the ridiculous "I can't save you cuz the Medikit isn't mine!" issue.

 

Thanks for reading! :geek:

 

 

 

 

Link to comment
Share on other sites

  • Recently Browsing   0 members

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