Jump to content

Question: Templates vs Classes


aggies11

Recommended Posts

Short Background: I had an idea for a "simple" (aren't they all..) mod for the Enemy Unknown, tried it, was too hard to make work, and promptly forgot about it. Enter in XCOM 2 with official modding support and the old idea came back to me. Good news, I've been able to get the mod working, after many trials and tribulations (while the mod is still simple in terms of what it changes, it's a purely gameplay/programing and the new games systems are much more involved requiring more learning/trial and error). While I'm happy that it's working, I have the feeling that I'm probably not doing it in the "best" way and in order to do that I probably need to know more about how the underlying game is setup. Which leads to my question..

 

What is the difference between Templates and the standard XCom Classes? The best example is probably X2Ability and X2AbilityTemplate? Obviously they are both technically classes, which I understand fine in the Object Oriented stance, but for whatever reason I'm finding it a bit unintutive how the game is actually *using* these things. I've tried to read as much as I can, search, tutorial guides, but can't find anything that lays it out bare (just bits and pieces I've been able to hopefully glean and try and piece together). If there is place where it's all spelled out that I've somehow missed, I'd be terribly grateful if anyone can point me the correct direction.

 

I am *guessing* that many of the "Non-template" classes aren't used in the traditional ways? (where they are instanced into Discrete Objects, possibly many of them, and those Objects are used, modified and manipulated through the course of their existence). My particular mod has the modest goal of changing the RNG Roll probability distribution in toHit Calculations (trying to make it *appear* subjectively to be more "fair". Currently trying out a crude average Sum of 2 Rand rolls, but that discussion is for a different topic...), and that required Overriding a few toHitCalc Class/Object(s) which was straightforward enough and fine. However those object(s) are used by Abilities and are "attached" to them, and due to a quirk in the way the vanilla game is set up, the Abilities that used my new overriden Classes/Objects weren't set up to automatically attach. Which required me to look into those problematic Abilities and try to "fix" it myself, which is where the confusion arouse.

 

What my current (at best incomplete, and more likely completely wrong) idea is that the Game doesn't actually use say X2Ability in the regular sense. This class is NOT instantiated into an X2Ability Object, which would then represent the ability in the game, called to handle any time a weapon uses that ability, and then it uses it's own toHitCalc objects etc, to do the "work" of using that ability? Instead it seems like this X2Ability Class, is maybe instantiated once (or a small number of times) into an object, and this object is then used to "produce" an Ability Template Class/Object (specified by X2AbilityTemplate). Essentially it creates and then "fills out" all the required (predetermined) fields of this Template object, and sends it out into the world. And at that point the work of the X2Ability Object is done?

 

Further then, this newly Created/Filled out X2AbilityTemplate object is also NOT technically used in the typical way in that there are not many copies/instances of it out there being used to handle the "work" of an ability when weapons are fired/used etc like mentioned above. It is not the games internal representation of an Ability either, and doesn't live a life, being changed or whatnot etc? But rather this Template object (and maybe again only a single copy/instance) is kept on hand by the game and actually used to construct another Class/Object, the final result a type of GameState object. These gamestate objects are linked to their base-template, in the sense that they refer to them to do certain things, but they themselves ARE the games internal representation of an entity, and contain all the information (that may be updated,changed etc) as that entity goes through it's existence in the game? This is where things start to really decouple in my mind, as I guess I can follow that train if we are talking about a Soldier or whatnot, but with respect to an Ability, the distinction between AbilityObject, AbilityTemplateObject and then AbilityGameStateObject really starts to break down/blur. I guess it seems odd to me that it would be broken out this way with so many individual pieces, instead of just a single object?

 

With respect to my specific mod, I need to make sure that the correct "Abilities" are refering to my modified toHitCalc Object. I can just start overriding classes left right and center (which in my first version I have), however those numbers start to add up as it seems all "information" regarding what we'd outside of the game describe as an "entity", is spread out over multiple classes/objects. This is the part that feels like I'm not doing it the "right" way, especially if some of those classes exist in only certain, brief, circumstances, it might be unnecessary to go replacing them all at will. A possibly better way to put it, if my above understanding it's too flawed (a big "if"...) is that do I need to replace the entire "factory" (X2Ability Object) if it's only ever producing a single "flawed" product (an X2AbilityTemplate object), or would it be better/easier/possible just to "fix" (modify and/or replace?) directly and leave the factory untouched.

 

My apologies again if I've missed something glaringly obvious. I guess it's only fitting that my initial idea for such a "simple/small" mod would be one that actually touches a variety of system components.

 

Thanks for any input, help and or advice.

 

Link to comment
Share on other sites

I'd still say it's worth having this as a separate discussion, though, as it's fairly central to how XCOM handles its own innards. I say this as someone who doesn't fully understand how that works, exactly, in the sense of when a template is called to create a gamestate object from itself. I had huge issues with this exact thing as I was apparently attempting to tweak templates "too late" or else in a way that wasn't able to affect the game states properly.

Link to comment
Share on other sites

I'm far from having perfect knowledge on this one, but I can offer this much:
In general:
Templates are used to create StateObjects (derived from XComGameState_BaseObject). These StateObjects are what you encounter in the game.
But:
  • There are Templates which will not get used that way, they will get used on their own (for instance: X2CountryTemplate).
  • There are StateObjects which will not get generated by Templates (for instance: XComGameState_Effect)
  • There are Classes like X2Abilty which are derived from X2DataSet. Despite their misleading names these are nothing more than toolboxes which help to define corresponding Templates. The game engine will go ahead and call CreateTemplates() at some early point and thereby create the Template objects, which will be used until you close the game.
If you want to have abilities behave different then they do in vanilla, you could do one of
  1. modify the X2DataSet's that will end up providing the game with Template objects in the first place
  2. modify the Template objects at some point after they got created (but preferably early)
  3. modify the XComGameState_Ability that will get generated from one of the Templates

 

1) as far as I understand class overrides cannot be used for native classes, so you would have to do a total conversion for this. (Is that correct?)
3) might be a complex task, since you have to do your modification whenever some ability gets created
Imho this leaves the second option as the way to do this. Allthough keep in mind, that StateObjects will get saved when the player saves the game. So anything that was created before you modifed the template will not be affected ever.
Edited by eXecator
Link to comment
Share on other sites

Thanks for the replies. All that extra reading does seem to help solidify things a bit in my mind.

 

Re: Steelrook

 

My modified understanding that was key to it making sense for me (could still be wrong of course, but things seem to fit together together in my understanding for the moment), is you can think of there being something called the "XCom Game" (that's the native game code/executable that we NEVER see or have access to). This "game" is the thing that does all the work, and all the Unrealscripts/classes that we see/have access to are simply tools for the game to "use". We have no impact/control on what the game does, but it does all it's tasks in a fairly consistent routine/order.

 

So for example, the game will go through all the X2Ability Objects, and for each one call it's CreateTemplates() function. These creates a whole bunch of ability templates that are collected together/handled by it's corresponding category manager. The game will then go through and connect each template with it's potential corresponding gamestate object (this part I'm less clear on as my mod doesn't involve gamestates thankfuly. But the order/direction of these operations isn't too important in terms of high level details). The same thing happens for Weapon(Item) templates (which when they are Created via CreateTemplates() are associated with specific abilities (or ability templates) but only referenced by name).

 

So with the above in mind, the way I am thinking about it is that the "game/exe" has a bunch of "places/pools" that it will look into for content. The way we "mod(ify)" the game then is to add our "additions" to these pools, so that the game will find them and do it's normal routine with them. We don't really have control over this process, however if we do know what/why/how the "game" is doing, then we can both create our additions and make sure they end up in the right "place" so that the game will do with them what we intend. For someone familiar with modding Xcom already, this probably seems like a fairly childish way of looking at something that is already pretty straightforward, but for someone on the outside looking in, it's currently where my level of understanding is. (Maybe? the most natural way of thinking about it would be working backwards from the played game entities and then going back to the script files and classes. So you'd start with a map, that specifies an alien or something. That alien requires a gamestate object, which has references to it's template. That template has connections to it's weapon template, which in turn has connections to the Ability Templates that the weapon can perform. Each of those have their own gamestate objects to keep track of bullets loaded, shots fired maybe, cooldowns, who knows (just guessing, I haven't touched that part of the game). All these templates are created by the specified "DataSet" Subclasses (eg. X2Ability.uc), note we don't really mention the Template class files themselves. We can't touch them as they are "native" and essentially their structure is tied to this "game" concept above, that expects them to work behave in very specific ways that can't/shouldn't be changed). Looking at it that way you can see the relationship between entities in the game played on screen all the way back to the eventual files that we have the option of modifying).

 

So for my desired mod, I know the "rolls" to see if you hit are done in toHitCalc objects. Now a Weapon itself doesn't actually take any actions, these are abstracted into "Abilities" eg "StandardShot" "PistolShot" "SniperShot", so I had to look at those. Each AbilityTemplate is allowed to specify it's toHitCalc object. So not only do I have to create my own modified version of a toHitCalc object, I also have to make sure mine is used on the AbilityTemplates that I want to effect (Specifically all the standard weapon shooting etc). Now as mentioned above, if you want to modify an ability template, you don't mess with the AbilityTemplate.uc themselves, as they are just generic specifications on what a generic Template of that type would contain. You actually have to go to the X2Abilityxxx classes who create/fill out the template fields and specify what toHitCalc objects to use. That was a big source of my initial confusion. I know what/where I wanted to change, but I didn't know how the game actually used any of this stuff, so I didn't know what if any other changes were required to get the game to USE my changes.

 

The above understanding allowed me to get my mod working, although it required me to override/replace several classes that the game was using. (My toHitcalc objects, but also the "factories" that created the templates that would need to use my toHitCalc objects). After the suggestions for more reading in this mod, and some other stuff I was lucky to find reading further, I'm actually not doing that anymore.

 

With the knowledge that I just need to get the AbilityTemplates that I require to use/refer to my modified toHitCalc object(s), and the above understanding how they are produced from the factories in the X2Ability classes, but then go on later to be used by the "game", stored in template managers, and in conjunction with gamestate objects, used to "run/calculate" the tactical game, the seemingly prefered way of doing things would be skip replacing the factory, and just *modify* the Template Objects themselves *after* they have been created/instanced. So don't actually change the *.uc class file, but rather just modify running objects in the game. The tricky part of this is that it steps beyond simply knowing the steps that the "game" takes when setting everything up (and just making sure my changes are picked up by the game automatically in the normal course of it's operation).

 

But it looks like all that is already fairly well understood and talked about in a few posts/examples. If you want to actually change things yourself on the fly, rather than just providing tweaks/replacement to existing data that the "game" will use in it's normal way, you need a way of getting anything you write to be run/executed itself. The current way to do this it seems is through the UIScreenListener's (oddly enough explained in the documentation that comes with the SDK, but none of that really stuck with me until I was able to really absorb all the above info enough that it's context finally made sense). With the generous examples in the forums you can see how the game provides certain execution points, where at certain times in the normal game play execution, you can "register" your pieces of new code to be run by the game. The neat part about this is you aren't actually replacing something existing to slot into the normal chain of events like above, and the benefit of that is that it helps with mod compatibility. Currently two mods can't override the same file/class. However theoretically no limit of mods can "registry" for these entry points and all have their code/instructions/alterations run.

 

So the update to my still WIP mod is to avoid having to touch Ability or AbilityTemplate files, overriding etc, and just use the UIScrenListener entry point system to have my own code simply make a few changes to the *already created* existing AbilityTemplates after they are made and being handled by the TemplateManager. I just need to change them (once hopefully?) per time the game is run/loaded to refer to my new toHitCalc object, and that should be it. I have the rough draft of it up and running now and it seems to be working. What remains then is the finer points of figuring out/research exactly what is the proper and "safest" way to do this, since you are now modifying game contents "in flight" so to speak, I'd imagine greater care is required to not really mess things up.

 

Re: eXecator

 

With respect to 1) I think DataSets (The classes/objects required for filling out Templates) aren't native and can be extended/overriden? I believe in my earlier version I replaced a bunch of them to deal with the above issues and it seemed to work ok. But I am definitely going with 2) now, as I feel that the lower the number of classes you need to override/replace the better, especially for inter mod compatibility.

Edited by aggies11
Link to comment
Share on other sites

See, my general "field of expertise" if you will is object-oriented programming, which UnrealScript is rooted for. However, UnrealScript also does a few concepts for which I've never seen counterparts in other programming languages, such as "states" and "actors." I'm more than positive that these concepts are rooted in some fairly basic OOP systems, but I'm just not entirely sure which. The same is true, to a lesser extent, about GameStates. It's clear that GameStates are classes of their own, presumably children of XComGameState_BaseObject. What I don't fully understand is why this distinction is even needed. Why do we need a GameState object as a separate thing from a Template? I presume Templates cannot be instanced, I'm told GameState objects are serialised into save game files to preserve the states of the various objects across game loads, but I don't understand what purpose the distinction serves. I could possibly see the logic if Templates were being used as Interface reference types, but I don't know if UnrealScript even supports those (Google says "yes") and it doesn't seem like any of the Templates are actually interfaces. They hold data, which disqualifies them right there. They're not virtual classes, either, so I don't see anything which would prevent the creation of Template instances.

 

There's something here that I'm clearly missing or don't really understand. Once upon a time, I attempted to alter an ability template in memory before it was used, but the result was broken and incomplete. It would work if you loaded up a saved tactical game where a character already had that skill, but would outright fail if you loaded up a strategic game where a character had that ability. The ability would exist, but none of the effects I was adding into it would be present... And I don't know why. XCOM 2 does a lot of things at run-time in a way that I've not been able to find any real documentation on. I don't know when GameState objects are created out of the base Templates, I don't know when I can replace the individual templates and I generally don't fully understand how to mess with guns and abilities at run-time. I'm not entirely convinced we should be doing that, either. Firaxis seemed to promise a better way to override base templates at a point before they're cloned across difficulty settings, so I'll likely hold off my efforts until/unless that happens.

 

For the time being, my one mod which modifies abilities is using a slash-and-burn approach of overriding (well, more like extending) the X2Ability_RangerAbility class. It seem like "the game" will create objects of any defined class which inherits any of the templates, and it does this after the templates themselves have been created so my class essentially replaces the base game class... Sometimes. I tried the same for X2Item_DefaultWeapons and that worked sometimes but not others. It worked if used an X2Item_DefaultWeapon, but not if I used my own child of that class. Similarly, replacing the ability only works if I forcibly generate a new template within my own ability, otherwise my method is either ignored or written over.

 

Animeri tried to explain to me how to replace templates in runtime using UI listeners, but it seems like I'm too slow to understand that... So here we are. I'm fairly confident I can create new weapons or abilities (reusing existing assets), but I'm not in the slightest certain of how to actually put them in the game in place of the abilities already there.

Link to comment
Share on other sites

The Actor concept isn't really a OOP-paradigm thing, it's more of a "3D Game Engine" type of thing. Unity has a similar concept called a GameObject.

 

In both cases, these are classes that come with the basic functionality needed to interact within a 3D environment. For example, both Actors and GameObjects always have a 3D vector position/rotation. In both game engines there is a simpler, more primitive object available, in both cases just called Object, which is the actual base class everything inherits from. In both cases Object can't be instanced into a 3D environment -- it's purely an abstract code class.

 

Gamestates in XCOM 2 are a big example of something that derives from Object. Pawns and UIScreen/UIPanel derived objects are going to derive from Actor, though, since they have to display.

 

States are something that's a little more Unreal-centric. It's kind of like being able to maintain multiple branches of a class override within your own class. Sort of like a private class defined in the original, that extends it. Except that States also have regular code that isn't in a function, which executes when the State is switched to. The state can override or call any function in the class it is within. States can only be defined in classes derived from Actor.

 

If you look in the folder \Steam\SteamApps\common\XCOM 2 SDK\Development\SrcOrig\Engine\Classes, you can find the .uc file for the Actor class. It's definition:

class Actor extends Object

-------------------------

 

Back to Templates and Gamestates and classes.

 

Classes are an OOP programming concept. Every .uc file is a class. Gamestates are classes, and Templates are classes. In fact, both of them derive from Object, not Actor. They serve different purposes.

 

In principle, the primary function of a Template could be served via config data. That's typically how it works in most Unreal games, I gather. However, the templates are code because many of them contain delegates -- allowing mods to define new code via templates and thus inject code into the game in an indirect manner.

 

Most templates have a CreateInstanceFromTemplate method, which creates a Gamestate instance from the template. For example, in X2CharacterTemplate :

function XComGameState_Unit CreateInstanceFromTemplate(XComGameState NewGameState)

Basically this copies the "relevant" data (as determined by the template code®) from the template to the new gamestate.

 

As mentioned previously, there's only a single instance of a template for all copies of a thing in the game. So there's one conventional assault rifle template, no matter how many you have. And there's only one sectoid character template. Also, templates aren't persistent through the save/load process. When the game loads, it rebuilds all of the templates from the current code/config data.

 

Gamestates are what is used to handle multiple instances. If you've got 5 conventional assault rifles with different names, upgrades, and colors/patterns applied, each one has its own gamestate. A gamestate is still a class, but multiple instances are made of the class (and each time some data is copied over from the corresponding template). Each instance stores separate relevant data about that instance of "whatever". So each gamestate instance of a sectoid holds stuff like current HP, current position, remaining ammo, ability cooldowns, etc. Each gamestate that is created from a template remembers the template, so you can always go back and access the original data. Handy for those bits of data that don't change. Note that a sectoid is a particular type of X2CharacterTemplate. And it is X2CharacterTemplate that maps to XComGameState_Unit.

 

Hopefully this is at least a little bit useful :)

 

P.S. Technically there's a third type of thing, which is X2DataSet. Its purpose seems to be to act as a container for the templates. So that class X2Ability_DefaultAbilities actually derives from X2DataSet. It holds the code that creates the templates when the game is launching. After that, the templates are held in memory by the TemplateManager classes.

Link to comment
Share on other sites

Hopefully this is at least a little bit useful :smile:

It really, really was :smile: Thank you kindly. Although I do have a few follow-up questions.

 

Gamestates are what is used to handle multiple instances. If you've got 5 conventional assault rifles with different names, upgrades, and colors/patterns applied, each one has its own gamestate. A gamestate is still a class, but multiple instances are made of the class (and each time some data is copied over from the corresponding template). Each instance stores separate relevant data about that instance of "whatever". So each gamestate instance of a sectoid holds stuff like current HP, current position, remaining ammo, ability cooldowns, etc. Each gamestate that is created from a template remembers the template, so you can always go back and access the original data. Handy for those bits of data that don't change. Note that a sectoid is a particular type of X2CharacterTemplate. And it is X2CharacterTemplate that maps to XComGameState_Unit.

So basically, the game creates a unique object for each individual Template, then uses those to create non-unique objects for specific Gamestate "items" like individual skills, weapons or characters. I have to say, that naming convention is more than a little confusing as I'd expect "gamestates" to handle the state of the game, whereas they're more along the lines of... Well, instances. But I follow what you mean now, though. Templates are unique generic objects which hold the "rules," Gamestates are non-unique specific objects which hold the individual instance of a thing to actually be used, as well as to be serialised into the save game file. With that in mind...

 

When are Gamestates created and when can they be modified? I ask, because I've run into a lot of issues with this. Initially, I tried overriding the ability creation classes, which obviously didn't work - you can't override static functions via class extension... And yet it seemed to work. In fact, it seemed to work without the need to overwrite anything. I'm told this is because the game will pick up all classes which inherit the base Template class (I forget what that's called) and create a single object from them. What this seems to mean is I can create a random child of, say, the Ranger ability template creation class (X2Ability_RangerAbilities?) and basically re-write one of its template creation functions and that'll work. Clearly it's not replacing the original function so it's not preventing the original ability from being created, so how does this manage to work? Does my ability override the game's default ability in the array? Does it instead get added "later" in the array and get read because of a naming conflict in search methods? This kind of attempt is very finicky and fickle and tends to work sometimes but not other times and I suspect this comes down to what order templates are created in.

 

And then we have Gamestates. My understanding of the process of serialisation is limited at best, but from what I understand... If Gamestate objects are serialised into a save game file, wouldn't loading a game recreate the exact same objects regardless of templates? Well, if that's the case then why do abilities become altered after loading a saved game if I alter their templates? Wouldn't the serialised objects just ignore the template and be loaded straight from the save game file? Or does that only apply to items which can exist in multiple instances? An issue I had in trying to use a screen listener to replace an ability was that it got added "half-way." Its icon didn't behave like it should - wouldn't colour properly - and the ability would basically disappear entirely once the game went back into the Strategic layer. I have no idea why this happens, and I don't understand whether we can alter gamestates already created at run-time through templates or not. Sometimes I can, sometimes I can't and I don't know what determines this.

 

Sorry to be dense. I'm just having issues grasping the innards of the game.

Link to comment
Share on other sites

So, at a general level, you have these two sorts of things -- gamestates and templates. But they are general tools, and how they are used can be a bit flexible -- which also means more effort to understand each individual case. So, let's look at abilities...

 

When you define an X2Ability_SomethingOrOther class, that class is actually a third type of class -- a DataSet. Your class is extending X2Ability, and :

class X2Ability extends X2DataSet

From the comment header in X2Ability :

/  PURPOSE: Interface for adding new abilities to X-Com 2. Extend this class and then
//           implement CreateAbilityTemplates to produce one or more ability templates
//           defining new abilities.

The only purpose of the DataSet classes is that they are scanned for, automatically detected, and then run once when the game first launch. If you look at an actual useful instance, like X2Ability_RangerAbilitySet.uc, how this works is that at game launch, the function :

static function array<X2DataTemplate> CreateTemplates()

is run. This function returns a dynamic array of DataTemplate. These templates get stored in the X2DataTemplateManager (or child thereof) that the same native code executing at run-time creates.

 

You can access templates at run-time through this DataTemplateManager. For example :

class X2AbilityTemplateManager extends X2DataTemplateManager

This class provides accessor methods to search for and do things with templates of a particular type (there are multiple to enable type-checking when building to help catch errors). A typical pair of accessor functions are :

native static function X2AbilityTemplateManager GetAbilityTemplateManager();

and

function X2AbilityTemplate FindAbilityTemplate(name DataName)

The first returns the singleton instance of the AbilityTemplateManager, so you can cache it locally in your function to avoid having to call to retrieve it repeatedly. The second searches within the AbilityTemplateManager for a particular AbilityTemplate, based on the name used to create it.

 

So that is how the ability templates get created (at game launch, typically), and cached into the TemplateManager. Since they can be retrieved by name, typically a config file will reference the ability by name.

 

--------------------------------------------------

 

The next step is how AbilityTemplates end up getting instanced into Gamestates. In particular, we're looking for how an instance of X2AbilityTemplate is used to create an instance of XComGameState_Ability. As mentioned before, for any particular ability (e.g. Ranger 'SwordSlice') there is only a single X2AbilityTemplate instance, which holds generic information about how the ability works. But if you have 6 Rangers on your squad in a tactical mission, then there will be six instances of XComGameState_Ability corresponding to six instances of 'SwordSlash' -- one for each Ranger.

 

Unfortunately, how the class abilities end up getting instanced to gamestates is a little more complex, so lets start with something simpler -- the ADVENT Captain's 'MarkTarget' ability. This is added into the Captain's X2CharacterTemplate, with the line :

CharTemplate.Abilities.AddItem('MarkTarget');

So here we see some intersection -- the X2CharacterTemplate has an :

var(X2CharacterTemplate) array<name> Abilities;

They are an array<name> instead of array<X2CharacterTemplate> to provide a level of abstraction. In some cases abilities get defined via config file, where you can specify by name, but not by class.

 

For solders and perks on perk tree, the ability is indeed defined in the config ClassData.ini :

aAbilityTree=(	(AbilityName="SwordSlice", ApplyToWeaponSlot=eInvSlot_SecondaryWeapon) 

I'll leave aside the details of why you need to specify the ApplyToWeaponSlot and how the class abilities work in the UIArmory_Promotion screen for now, and elaborate instead on how the abilities get gathered.

 

-----------------------------

 

During the strategy portion of the game, abilities aren't instanced into gamestates. They are instead stored in a more "scattered" fashion, and are typically referenced by name. Some might be in character templates, some in weapon templates, some in class data arrays (for those picked via UIArmory_Promotion). There's also an array for AWCAbilities, which is where I put the OfficerPack abilities.

 

Right when tactical play is starting, inside of XComGameState_Unit, the following function is called :

function array<AbilitySetupData> GatherUnitAbilitiesForInit(optional XComGameState StartState, optional XComGameState_Player PlayerState, optional bool bMultiplayerDisplay)

This function gathers together all of the abilities a unit should have access to from a divers set of places, and returns the information in a single dynamic array. This array includes the ability names. The ability names are then used to retrieve the X2AbilityTemplates from the X2AbilityTemplateManager. These X2AbilityTemplates are then used to create the instances of XComGameState_Ability, which are then attached to the unit here, in XComGameState_Unit :

var() array<StateObjectReference> Abilities;                        //Abilities this unit can use - filled out at the start of a tactical battle

Note that the type here is StateObjectReference, not XComGameState_Ability. The reasons for this are a bit technical, and have to do with how the XComGameStateHistory works. However, note the comment -- Abilities in this array are created at the start of a tactical battle. They also only persist until the end of the tactical battle. They are destroyed at the end by some native validation code that draws data from a config array in Game.ini :

[XComGame.XComGameInfo]
;Transient tactical classes specify objects that should be set as removed in the history upon leaving a tactical session
+TransientTacticalClassNames=XComGameState_Ability

This cleans out any instances of XComGameState_Ability from the XComGameStateHistory at the end of each tactical battle. This means that it's not possible to store persistent data about an ability that accrues over multiple battles within the XComGameState_Ability.

 

----------------------------------------

 

Now, on to the History.

 

As mentioned above, the ability instances of XComGameState_Ability are not referenced directly (via pointers) from XComGameState_Unit. Instead, they are referenced abstractly, via the StateObjectReference type.

 

It may help to conceptualize the History (embodied in the singleton XComGameStateHistory class) as a sort of Version Control System. Except that instead of managing changes to software source code, it manages changes to things that derive from XComGameState_BaseObject. It's not a full VCS, since it doesn't allow branching, but you do have to explicitly "submit" changes that you want to show up in the History.

 

The History maintains past information about submitted changes. It remembers more than just the current state. This is why references to other gamestates are abstracted to StateObjectReference instead of being pointers directly to instances of gamestates -- because some other action may change what the "current" version of a particular gamestate is.

 

As an example, an ability might be set up to receive an event (via X2EventManger.RegisterForEvent(...)). When the event is triggered, it may change something in the XComGameState_Ability, which means that it submits a "delta" change to the History, which technically means a new class instance. The XComGameState_Unit "owner" of the ability need not be informed of this.

 

Instead, when the owning XComGameState_Unit wants to retrieve the Ability, it queries the History for the current version of the ability. A typical call to retrieve the current version of an XComGameState_Ability looks like :

ShootAbilityState = XComGameState_Ability(History.GetGameStateForObjectID(AbilityContext.InputContext.AbilityRef.ObjectID));

Note a few key things:

  1. History is a local variable of type XComGameStateHistory, and the History singleton is typically access via the macro `XCOMHISTORY
  2. The object is referenced via the ObjectID in the StateObjectReference -- this is basically a UID (unique per campaign, not universally)
  3. The default is to retrieve the current, but optional parameters allow retrieving past states
  4. The return of the function is of type XComGameState_BaseObject, so you always have to cast to the child class type

There's a lot more that can be said about the History, but since this is mostly about templates and gamestates (and this post is long enough already...) I'm going to leave it there for now.

Link to comment
Share on other sites

See, my general "field of expertise" if you will is object-oriented programming, which UnrealScript is rooted for. However, UnrealScript also does a few concepts for which I've never seen counterparts in other programming languages, such as "states" and "actors." I'm more than positive that these concepts are rooted in some fairly basic OOP systems, but I'm just not entirely sure which. The same is true, to a lesser extent, about GameStates. It's clear that GameStates are classes of their own, presumably children of XComGameState_BaseObject. What I don't fully understand is why this distinction is even needed. Why do we need a GameState object as a separate thing from a Template? I presume Templates cannot be instanced, I'm told GameState objects are serialised into save game files to preserve the states of the various objects across game loads, but I don't understand what purpose the distinction serves. I could possibly see the logic if Templates were being used as Interface reference types, but I don't know if UnrealScript even supports those (Google says "yes") and it doesn't seem like any of the Templates are actually interfaces. They hold data, which disqualifies them right there. They're not virtual classes, either, so I don't see anything which would prevent the creation of Template instances.

...

 

This is the place that I was at. My above post was to try and show you the way I organized the various components of the Xcom "system" for lack of a better word, in a way that started to make sense.

 

I have some initial notes I wrote down from last week that ended with something like "Buy why 3 seperate classes to represent the same 3? Why use Object Oriented Design if you are just going to short circuit the paradigm whenever its' convenient". Etc. Which sounds similar to what I'm hearing from you. All this discussion has actually been really helpful for me to read/participate in, as it's further helping things digest in my mind and cement my understanding. (They often say the best way to help you learn something, is to try and explain it to someone else).

 

So for the Object Oriented part, I think it's important to realize that OOP is one design paradigm, and it's interesting and has it's benefits and it's downsides. One thing you usually see is if someone takes a naive approach to OOP, (especially when stating out) they start making EVERYTHING an object. Like every last detail gets it's own object. The level of abstraction becomes comical (and unwieldy). One of the tricks to good Object Oriented Design is known where to draw the line with the abstraction, when it stops being a tool and starts getting in the way. That's important to relealize/remember when you get into projects like this (modding an existing game).

 

So I still wonder why 3 classes for a single game entity? But I'm not so obtuse to not have a few ideas/speculation. One thing you hear is that while UnrealScript is nice and useful, it has it's performance issues and so you aren't going to write an entire game in it. So a lot of the Unrealscript classes are going to be sitting ontop of actual gamecode in CPP or whatever. So right away we are probably stuck with an immediate layer of abstraction there. So it might be setup this way ironically, for modders in the first place, as an interface for us to "access" the game.

 

The next part Amineri has touched on(edit: Just read the latest post. 'touched on' barely covers it, more like "explained in glorius detail" :wink: ). They designed the game using the very explicitly idea of game states, and state changes. Which makes sense for a turn based game that isn't realtime. With their synched random seed system they use, the game becomes completely deterministic, which is very helpful from a problem fixing standpoint. Part of that system is the idea that a save game can quite literally be a record of everything that has ever happen. To keep that organized it's based on a series of game states, and changes between one state to another. I'm not sure how big the save files get, but regardless it seems like an interesting idea. But right away then, if you have to save the entire state of your game, on like every single turn, and every turn going forward and looking back, that is a LOT of states. So much so that you probably can't just start serializing all your classes. If there was a single class to say represent a soldier, you don't want every part of that object serialized and taking up space in the save file, if all you really need is just a few of their attributes. So I can see the distinction of splitting the "soldier" into two parts. The savegame or "game-state" info that needs to be in the saves/gamestates. And then all the other common stuff and functionality that can be abstracted into a template, which doesn't get saved. Consider the template the skeleton and the gamestate the mean on the bones that fleshes it out and allows it to "move".

 

So far I can kinda see the above maybe being a reason why they went that way. But of course we have 3 versions of the solider, not just two, so why the third? This last one I'm guessing is for modding. Rather than having modders simply extending templates directly themselves to do their modifcations I'm guessing they wanted to buffer the player changes a bit through the use of the "DataSet classes (whose subclasses never use that word in their name, making it tricky to keep track of initially). Extending a class can be tricky in terms of breaking functionality, and considering how important templates are (they are native classes I belive?) I'm guessing they wanted to make sure they were bullet proof. Even then the modders really only extend datasets to simply replace a single function, one that produces/fills out some templates. So it's all very constrainted.

 

So if you want to boil it down, you have the Soldier gamestate object (yes soldier is a bad example as it comes with some Unreal Engine extra baggage by being an active 3d represented object in the engine, but ignoring that part...) which contains all the info required to save the game, and nothing more. Then you have the GENERIC (this part is important) template, which honestly itself is really just a container for all the fields you "fill out" when constructing one. It is a container that holds all the stuff the Soldier needs to work, but either doesn't change, or at least doesn't need to take up space in a savegame. And then you have the Dataset objects, which are just the places where the templates are filled out. I don't even want to use the word "constructed" because I think that's misleading as far as templates are concerned. It really is just a container with a bunch of predetermined slots (that the person filling out has no control over) that you put your specific items into. These ones are the trickiest as they are the least like "objects" in the true noun sense, they are just a place where you fill out a single template that the game uses to be the "other half" of the gamestate object.

 

At first blush the idea of a "template" might seem better suited towards a typicall OOP "interface" concept. That seems fair, although it seems like there definitely is some data associated with templates, and they aren't just a series of functions, so an "interface" might typically not cut it. I'm also not sure how Robust UnrealScript interface system is, plus that also might be a tough distinction to for modders exposed to the system to absorb, if they aren't already experienced in OOP. So I'm guessing they found this extra layer of abstraction useful, hence even the name "template", despite all the baggage a name like that might unintentionally convey.

 

That's my layman's understanding of it anyhow. I have to admit, you are right about the naming part. The names they have chosen for their classes really aren't that enlightening, and often a bit confusing. Don't get me wrong, having any official mod support at all is wonderful and I don't want to sound ungrateful, just explaining why there is some confusion. Personally I take naming things in software development very seriously, and often am given a hard time about how long it takes me to come up with good/appropriate names. When I design software I try to take the approach that I'm not just instructing the machine to accomplish the task I want it to, but rather I am specifically trying to teach/convey whoever is reading my code exactly what everything is doing and what my intent is. It takes a bit longer, but as I'm often the one going back to software/code much later after it's originally written, I always aim to be kind to my future self and make it as easy as possible for them to get back up to speed.

 

As for the specifics about overriding replacing say the ranger ability: You have to remember the distinction between inheritence, overriding, and then straight up "replacing" an object using the Firaxis given Xcom Modding system. When you extend the RangerAbility class, all you are doing is making your own new class/object, that has everything the Ranger one does "but a bit more". That big more can include changing how the RangerAbility class does some of it's things (ie. overriding/replacing/hiding it's methods/functions). That on it's own doesn't mean you have replaced the parent class. You have just defined an additional class, ontop of the existing one, although they do share some similarities. This goes back to what I was saying before about how I've found it helpful to think of this other entity the "xcom game" itself, as it's the thing that actual does all the work. Just writing a class in your mod in itself doesn't do anything. You need this "xcom game" to actually use your class in the first place, otherwise it will just sit there collecting dust. In order to do this you have to understand the system of how/what/when the xcom game does, and make sure your class/object is in the right place to actually be used. Specific to your Range ability, in this case you don't have to actually do that much. The XCom game will actually go through all the "DataSets" that it finds (the X2Ability classes are all "datasets", even though they are poorly name) and ask each one of those classes to please "construct/fill out all the templates you know how to make, and give them to me", and all those templates are collected and handled by the idea of a template manager. So that is how you get that object into the game, in the case of dataset classes (eg abilities) it happens automatically. Very important to know here is no "overriding" has actually happen, in terms of the Xcom game mod system, although technically in terms of Object Oriented Programing, you technically are overriding the parent class "CreateTemplates()" function. But those words mean different things confusingly enough in each context. Now to continue on with the Abilities example, it's important to know that even though your new/modified/extended ability is now in the game, what that actually means is a single object of the dataset class you made is created, and it's used to produce some templates who are collected by the appropriate manager. BUT nothing more will happen unless something somewhere else in the game is told to use that template. Eg. if you made a new ability that you want a gun to use, the gun itself (whatever that nebulous term "itself" means..) needs to refer to your template. Again you need to be aware of how the Xcom game "idea" operates so you know what to place where to get what you made into the game in the way you intend.

 

Now as far as the term "overrides" goes, it means different things to the Xcom game modding system. In terms of Templates you can't actually extend a template in the game, so we don't worry about it there, the dataset/ X2Ability classes are how you introduce new templates to the game. But for OTHER objects (for example, the toHitCalc objects that are used, (by ability templates) to determine of an ability hits/misses) it does apply. If I want to make a new toHitCalc object, just making/extending the class isn't enough, unlike datasets, the "Xcom game" does not go and look for toHitCalc classes and automatically do stuff with them. They just sit there unused. So what you have to do is TELL the game to "use my object instead of/in place of another object". You do that with the idea of "class overrides" using the Engine.ini part of the mod. What this tells the game to do is "anywhere you were going to use an object of this class-type in the game, use my new object instead". This is something that I believe Firaxis had to add, which seems pretty neat/helpful. This is the "next level" of modding because you aren't just adding something new, additionally. You are actually replacing something that already existed. An important sticking point is that it will only replace objects created with the "new" operator. There are a few other ways to create objects that unfortunately ignore this system, and special steps have to be taken, regrettably, in those cases. This doesn't work for all classes, as certain ones are not overridable. Overriding is more dangerous in the sense that if you stray to far way from doing what the game expects your class to do, you can presumably cause problems, hence why some areas are off limits.

 

So in Summary I'd say it makes sense why it's all a bit tricky. The way the system is set up is a bit unobvious, and the terminology gets really tricky. Some of the terms come with their own baggage/assumptions that don't always live up to how they are used in game. In Objected Oriented Programing some people will describe a "class" as being a "template on how to construct/ an object". When combined with the in game "Template class", (which itself is a class, used to produce objects, those objects are "templates" for corresponding gamestate objects, but the term "template" isn't really accurate as they don't actually describe how to build them, and are not related to them at all in the Object Oriented sense) it can get convoluted pretty fast. When in reality templates are really the "other half" the gamestate objects, the parts that "dont fit" or "don't need to be in" the savegame.

 

After all that is said and done, I'd just like to call out Amineri again for all the amazing amount of effort put in enlightening and educating the rest of us. It really is a thankless task but has made an absolutely immeasurable difference in terms of the spread of knowledge in this community. It's a lot of effort, but it definitely (at least for myself, although I'm sure countless others" is incredibly appreciated. :smile:

Edited by aggies11
Link to comment
Share on other sites

  • Recently Browsing   0 members

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