Jump to content

Amineri

Premium Member
  • Posts

    1779
  • Joined

  • Last visited

Everything posted by Amineri

  1. On Item 1, you'll want to tie the display of the icon to the effect, not the ability. Since it lasts 2 turns, presumably you are using some extension of X2Effect_Persistent. In the AbilityTemplate definition code, when you define your effect, look at X2Effect.SetDisplayInfo( ... ). That can be used to tie display of a passive icon in lower left container to the effect being active.
  2. The XComGameState_GameTime is needed so that the time gets "checked in" to the History so it can be serialized into the savefile. Otherwise, most of the helpers for manipulating time are in X2StrategyGameRulesetDataStructures.uc, as CaveRat pointed out.
  3. You'll want to make sure you don't lose the PublishedFileId.ID file. If you lose that, you'd have to upload as a new Workshop item instead of updating the existing one. It's created the first time someone publishes to Steam. Otherwise, it seems to be the version of the mod located in the SDK Mods folder that actually gets uploaded, which is a little annoying since those mod folders have to be removed in order to get other mods' ModShaderCaches to build correctly. JL and I split stuff up for day-0 LWS mods the same we did for LW EW -- he did all the publishing to Steam/Nexus, but we both coded (also JC did all the art). What we'd do is basically zip up for the full mod folder (from Projects folder, not SDK/Game Mods folders), and share them back and forth. Excluding the suo file (which is normally hidden anyhow). As your mod gets bigger and/or you have more people working on it, this gets more problematic, so some sort of VCS is generally the solution. Just keep in mind that source control doesn't really work very well for art, which can comprise the bulk (in size) of a mod.
  4. You don't need to share the suo file. We never did for any of the day-0 LWS mods, and it works out fine. Just the .sln and .x2proj files are enough.
  5. The lack of multidimensional array support in Unrealscript is a bit annoying, I agree. The "official" workaround to that, as I understand it, is to embed the inner array inside a struct, and then make the outer array an array of that struct. struct MyInnerArray { var array<UIIcon> inner; }; var array<MyInnerArray> My2DArray; function Foo() { local MyInnerArray InnerArray; local Bar UIIcon; My2DArray.AddItem(InnerArray); .... My2DArray[i].inner[j] = Bar; }
  6. It's a game state because it inherits from XComGameState_BaseObject, and is managed by the XComGameStateHistory class. Gamestates are what is serialized into the savefile when saving, and deserialized from the savefile when loading, which make trying to override them a tricky business. For example, what happens if a user applies your mod and then loads an existing campaign savefile? Since it's rife with potential errors, Firaxis decided to not allow the game state creation process to allow overrides. Plus, even if it worked, overriding the whole class in order to change this one small aspect isn't really that great of a plan. Only one mod can override a particular class, so overrided XComGameState_Unit for this would make the mod incompatible with any other mod that wanted to do the same. Unfortunately, there's not currently a clear, simple way to implement what you want. The ideal case would be if a TriggerEvent call were in place within the GiveRandomPersonality() method, so that it looked like : function GiveRandomPersonality() { local array<X2StrategyElementTemplate> PersonalityTemplates; local XGUnit UnitVisualizer; local XComHumanPawn HumanPawn; local int iChoice; PersonalityTemplates = class'X2StrategyElementTemplateManager'.static.GetStrategyElementTemplateManager().GetAllTemplatesOfClass(class'X2SoldierPersonalityTemplate'); iChoice = `SYNC_RAND(PersonalityTemplates.Length); PersonalityTemplate = X2SoldierPersonalityTemplate(PersonalityTemplates[iChoice]); PersonalityTemplateName = PersonalityTemplate.DataName; kAppearance.iAttitude = iChoice; // Attitude needs to be in sync //Update the appearance stored in our visualizer if we have one UnitVisualizer = XGUnit(GetVisualizer()); if (UnitVisualizer != none && UnitVisualizer.GetPawn() != none) { HumanPawn = XComHumanPawn(UnitVisualizer.GetPawn()); if (HumanPawn != none) { HumanPawn.SetAppearance(kAppearance, false); } } `X2EVENTMGR.TriggerEvent('OnGiveRandomPersonality', self, self); } This would allow any number of mods to listen for the 'OnGiveRandomPersonality' EventID and then make adjustments. So different mods could handle different cases and (potentially) not conflict. Better still this wouldn't prevent other mods from interacting with XComGameState_Unit. However, we aren't yet living in an ideal world, so we have to use somewhat uglier, hackier workarounds if you want to get things working. How I'd probably approach this would be to set up some sort of ScreenListener that is listening for various events, or triggers on certain UI transitions, then go in and look through the various places that units are stored and "patch up" the personality data the way that you want.
  7. I've updated the "detailed" section of the original post to provide more insight about what's going on.
  8. I had trouble inserting into the AIJobs with the Muton Centurion, so it only updates the behavior trees in XComAI.ini. Most of the AI logic is managed in that config file, which only the details of what particular nods do handled via Unrealscript. You may find this useful as a starting point : https://en.wikipedia.org/wiki/Behavior_tree
  9. From the developer source code in X2DownloadableContentInfo : /// <summary> /// This method is run if the player loads a saved game that was created prior to this DLC / Mod being installed, and allows the /// DLC / Mod to perform custom processing in response. This will only be called once the first time a player loads a save that was /// create without the content installed. Subsequent saves will record that the content was installed. /// </summary> static event OnLoadedSavedGame() So it is supposed to only be called once. However, I've noticed that it is being called when the mod was already active in the savefile, and so have taken to writing that check into my OnLoadedSavedGame handler. I certainly wouldn't rely on it being invoked every time the game is loaded, as that may be patched in the future...
  10. The current SDK build has a small flaw that can result in mod dependencies if you delete your LocalShaderCache file. This shows up only if you are building more than one mod (like... we have 3 mods at LWS). It can happen even if only one mod has 3D assets. SHORT VERSION: As a workaroundDelete the "cached" mods from the \Steam\SteamApps\common\XCOM 2 SDK\XComGame\Mods folder before doing each mod build.Delete the file \Steam\SteamApps\common\XCOM 2 SDK\XComGame\Content\LocalShaderCache-PC-D3D-SM4.upk before doing each mod buildLeave the mods in your game folder \Steam\SteamApps\common\XCOM 2\XComGame\Mods alone.Leave the mods in your ModBuddy project folder alone. ---------------------- LONG VERSION: I'll write up more here about what's going on under the hood later, when I have some more time ^_^ Here's what's going on in a bit more detail... The precompileshaders commandlet works in the following manner : Launch the SDK executable Load up existing shaders (e.g. RefShaderCache, GlobalShaderCache) Load mod asset packages, one at a time When loading mod asset packages, if any fail to find needed shader in already loaded shader caches, then the shader will be "compiled at run time" with the results added to the LocalShaderCache -- this is standard Unreal behavior (https://forums.epicgames.com/threads/695400-Missing-cached-shader-severely-slowing-down-maploadtime) After all mod asset packages have been loaded, the LocalShaderCache is copied and renamed to be the ModShaderCache in the mod folder When game launches, any mods that have been specified as active through the Launcher/ModOptions.ini file have their ModShaderCaches loaded and added to the common shader cache pool in memory---------------------------------------------- Why to delete the LocalShaderCache If you don't delete the LocalShaderCache, then the previous LocalShaderCache is merely added to and then copied to become the ModShaderCache. This results in file bloat, particular if LocalShaderCache happens to be quite large. The release LocalShaderCache was about the same size as the release RefShaderCache, (~85 MB), and this will be added to any mod, regardless if it needs any shaders or not. If a mod has content assets, but doesn't generate any new shaders, I've found that the resulting "empty" ModShaderCache will be 371 bytes. This didn't happen with the LWS release-day mods because the pre-release build of the SDK we were working with didn't have the really large LocalShaderCache. Those mods ended up with a ModShaderCache of ~233 KB, which is actually still a bit larger than it needs to be, but I'll elaborate on that late. One downside to deleting the LocalShaderCache is that it can also be used by UnrealEd. UnrealEd is actually a upk, running under the game engine. So it uses shader caches just like the game does. If you are working with new materials and UnrealEd can't find a shader, it will compile the shader at run-time (with the result being added to the LocalShaderCache). Compiling shaders at run-time makes UnrealEd load slower. --------------------------------------------- Why to delete "cached" mods from the SDK Mods folder In step 2, when the precompileshaders commandlet is loading existing shaders, it appears to load any ModShaderCaches it finds in the SDK Mods folder. Evidence for this theory is as follows :When launching UnrealEd with cached mods in the SDK Mods folder, it can be observed that during the loading splash screen that the ModShaderCaches are being loadedI conducted the following experiment :Start with empty SDK Mods folder Build mod that requires shaders -- I used LW_SMGPack Observe that the LocalShaderCache file is created, as well as ModShaderCache -- this file was ~133 KB Do not delete the LocalShaderCache Build mod that does not require shaders -- I used LW_OfficerPack Observe that the LocalShaderCache file stays the same size, and the OfficerPack ModShaderCache is also ~133 KB Delete the SDK Mods folder LW_SMGPack Delete the LocalShaderCache Do not delete the SDK Mods folder LW_OfficerPack, and leave its ModShaderCache Rebuilt the LW_SMGPack mod Observe that the LocalShaderCache is 371 bytes, and that the LW_SMGPack's ModShaderCache is also 371 bytes Launch game with only LW_SMGPack mod enabled, in debug mode Observe that redscreen messages about missing/incomplete textures appear, and that some texture are "corrupted black" Close game, relaunch game with both the LW_SMGPack and LW_OfficerPack enabled, in debug mode Observe that there are no such redscreen messages, and that the textures display correctlyAccording to the theory, what is happening is that in step 5, the shaders for the SMGPack were already present in the LocalShaderCache, and so get built into the ModShaderCache for the OfficerPack. Then, when I delete the LocalShaderCache, and rebuilt the SMGPack in step 10, the precompileshaders commandlet loads those shaders from the OfficerPack, so it doesn't add any shaders to the LocalShaderCache, with the result being that the SMGPack's ModShaderCache is empty, and doesn't contain the shaders needed by the mod. Thus when the game is run and SMGPack runs by itself, I get the following redscreens/log messages : [0021.84] Log: Missing cached shader map for material Laser, compiling. [0021.84] Warning: Redscreen: Compiling Laser at run-time, please ensure this is part of the cooking process [0023.13] Log: Incomplete cached shader map for material WeaponCustomizable_TC, compiling. [0023.13] Warning: Redscreen: Compiling WeaponCustomizable_TC at run-time, please ensure this is part of the cooking process The WeaponCustomizable_TC is the base material used for weapons and weapon attachments. I also created a duplicate of the Laser material in the package to support the Laser sight attachment. Visually, the effect is that of blackened, darkened textures. When the two mods are both run, the needed shaders for the SMGPack are loaded with the OfficerPack's ModShaderCache, with the result that the textures display as expected. However, the process has created an undesired dependency between the two mods. By deleting the cached Mods from the SDK Mods folder, it ensures that the precompileshaders commandlet won't load them when it runs, and that the ModShaderCache built for a particular mod will include all shaders that the mod actually needs. In separate tests with the Muton Centurion, I would instead get the following redscreens/log messages : [0058.84] Log: Missing cached shader map for material Alien_SD_SSS, compiling. [0058.84] Warning: Warning, Failed to compile Material LWMutonM2.Materials.Alien_SD_SSS for platform PC-D3D-SM4, Default Material will be used in game. [0058.84] Log: Missing cached shader map for material Alien_SD_SSS, compiling. The visual effect of "Default Material will be used" is that the Unreal default blue/white checkerboard material will be used instead of the correct one. ------------------------------ The LWS release day mods were released before we knew about having to delete the LocalShaderCache. However, the SDK at that point didn't have the really large existing LocalShaderCache in it. Thus, as we would build each mod, all of the shaders required by any of the mods (only Centurion and SMGs needed any shaders) would be accumulated into the LocalShaderCache. That LocalShaderCache would then be copied to become the ModShaderCache for each of the 3 mods. The end result is that each of the 3 mods contains all of the shaders necessary for the entire collection of mods.
  11. I don't think that this is true at all, at least it hasn't been in my experience. As an example, in the LW_OfficerPack mod, I define a class : class X2Ability_OfficerAbilitySet extends X2Ability config (LW_OfficerPack); The class this is extending is : class X2Ability extends X2DataSet config(GameCore) native(Core) dependson(XComGameStateContext_Ability); And it works just fine, with me : Changing the config from GameCore to LW_OfficerPack Removing the native(Core) parameter Removing the dependson(XComGameStateContext_Ability) parameterIn the first case, the new Config parameter only applies to new config variable defined in the extended class, not to any config variables defined in the parent class, which still draw from GameCore. So in my X2Ability_OfficerAbilitySet class, I define : var config float BaseCommandRange; and this variable is configured in the XComLW_OfficerPack.ini. However, in the parent X2Ability class, the variable : var private const config float MaxProjectileMissOffset_Standard; still draws from the DefaultGameCore.ini / XComGameCore.ini config files. -------- On the second point, I don't need to have the native(Core) parameter, because none of my functions are native (that is, none of them get routed to execute within XCom2.exe). In the X2Ability class, however, there is : //This method generates a miss location that attempts avoid hitting other units, obstacles near to the shooter, etc. native static function Vector FindOptimalMissLocation(XComGameStateContext AbilityContext, bool bDebug); Therefore it needs the native(Core) parameter. Note that I could override this native function with a non-native version (which can even call the native super) without problem, and without adding the native parameter to my class override definition. The syntax would look like : //This overrides a native function with an unrealscript one static function Vector FindOptimalMissLocation(XComGameStateContext AbilityContext, bool bDebug){ { Vector ReturnVec; ReturnVec = super.FindOptimalMissLocation(AbilityContext, bDebug); // Do stuff you want to do here return ReturnVec; } ----------------- On the third point, I don't need a dependson parameter, because the function being depended on is in XComGame, which is automatically built before my mod is built, so the build order is already guaranteed. You really only need to use dependson within your own mods, if one class defines an enum or struct that another class in your mod uses. Using dependson guarantees that one is built before the other, so that the enum/struct entries are added to the compile tables.
  12. Resources are just X2ItemTemplates. This means that creating a new resource is as simple as creating a new ability, at least mechanically. I'd look in X2Item_DefaultResources for examples of how the games creates resources such as 'supplies', 'AlienAlloy', 'EleriumDust' and 'EleriumCore'.
  13. I'm pretty sure that both of these are false. And here are my counterexamples (I'm actually more of a mathematician by training than a programmer...). 1) Extending native classes In the LW_OfficerPack mod I extend the class : class X2AbilityMultiTarget_AllAllies extends X2AbilityMultiTarget_AllUnits native(Core); And the parent of that definitely has a bunch of native functions inside of it : class X2AbilityMultiTarget_AllUnits extends X2AbilityMultiTarget_Radius native(Core); ... simulated native function GetMultiTargetOptions(const XComGameState_Ability Ability, out array<AvailableTarget> Targets); simulated native function GetMultiTargetsForLocation(const XComGameState_Ability Ability, const vector Location, out AvailableTarget Target); simulated native function GetValidTilesForLocation(const XComGameState_Ability Ability, const vector Location, out array<TTile> ValidTiles); My class extension looks like this : class X2AbilityMultiTarget_LWOfficerCommandRange extends X2AbilityMultiTarget_AllAllies; Note that I'm not including the native class keyword, because my class doesn't define any native functions -- THAT is something we can't do as modders, because native code has to live in the exe. However, we can override the native functions defined in the parent class using unrealscript functions. That was my primary purpose in creating the class. One thing to watch out for is that native-to-native function calls can't be intercepted by unrealscript virtual machine. So I wanted only to functionally override GetValidTilesForLocation, but because I had to override other native stuff in order to intercept all calls to it. Having created this new extended class (extending native classes), we proceeded to utilize them in Ability Template definitions, as usual : //add command range Template.AbilityMultiTargetStyle = new class'X2AbilityMultiTarget_LWOfficerCommandRange'; ------------------------------------- 2) Overriding native classes A really popular mod (which is also included as a sample mod in the SDK) overrides a native class : class XComTacticalInput extends XComInputBase dependson(X2GameRuleset) native(UI); // TODO - move into separate native base class This class is overridden in order to allow definition of the camera rotation angles. And this uses the actual "class override" as configured in the Engine.ini, as well as being an extension of the class. Again, the correct class extension definition doesn't include the native keyword : class XComTacticalInput_LW extends XComTacticalInput; And this has been used by plenty of people, so it seems pretty stable...
  14. Actually, technically, you can actually extend a template. E.g. : class X2AbilityTemplate_MyMod extends X2AbilityTemplate; However, the new template you define won't retroactively apply to all of the existing templates created by the base-game, nor created by other mods. Since those templates are created with new class'X2AblityTemplate', you won't be able to cast to your extension -- Unreal will return a null if you try that. However, if you write NEW code someplace, you can try and cast templates to your new class, and when it succeeds, then you can use new fields/functionality as defined in your template. It's kind of an edge case, but is something I'd contemplated for allowing modded-in new ability to set config-value-based localized strings. For example a localized string "Ability does +X Damage", where the X is replaced at run-time by a value defined in config. The base game accomplishes this through a gargantuan separate switch/case statement, but it could be handled within an AbilityTemplate extension. By thanking me you've actually negated your statement here. But I'm glad the guides I'm able to provide have been of some help :D
  15. To elaborate a bit more, config defintions are always of the form <key> = <value> - removes values that match key and value exactly + adds values that don't already have a match of both key and value If you don't specify a prefix, then I think it adds only if the key is unique. Other options are: ! removes value that match only based on key . adds values unilaterally -- even if the key and value already exist -- potentially adding duplicates.
  16. 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: History is a local variable of type XComGameStateHistory, and the History singleton is typically access via the macro `XCOMHISTORY The object is referenced via the ObjectID in the StateObjectReference -- this is basically a UID (unique per campaign, not universally) The default is to retrieve the current, but optional parameters allow retrieving past states The return of the function is of type XComGameState_BaseObject, so you always have to cast to the child class typeThere'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.
  17. 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.
  18. The other big change is that macros gets swapped out, which generally results in much longer and less readable code. For example, in the original .uc file, CoolUnderPressure reads like this : `CREATE_X2ABILITY_TEMPLATE(Template, 'CoolUnderPressure'); However, when decompiled by UE Explorer, it cannot decompile to the macro, so it ends up looking like this : Template = new (none, string('CoolUnderPressure')) class'X2AbilityTemplate'; Template.SetTemplateName('CoolUnderPressure'); Something else to consider is that comments by Firaxis coders aren't visible in UE Explorer. So you miss out on gems like : // TODO JWATS: Generalize this the association between effects and custom anim sets. i.e., modders could // add a "drunk" effect and anim set. Please modders. You know what needs done.
  19. I think this is only true if you are looking at the code with UE Explorer, which isn't looking at the .uc files as released in the SDK, but instead showing the decompiled code from the .u or .upk. Constants are replaced at compile-time, so it basically is going to look like a constant to UE Explorer. In my source file, the code reads : LootDrop.LootExpirationTurnsRemaining = MaxLootExpirationTurns; indicating that is it pulling from the constant. Of course, better for modding would be if it were pulling from a config variable.
  20. We use .png format for importing into UnrealEd. UnrealEd exports as .tga, but that's likely a by-product of how old Unreal 3 is. Internally to the package files, texture data is stored as DXT1 or DXT5 (depending on whether transparency is or is not needed), which is roughly the .dds format (minus some headers). So back in EU/EW days when we were ripping stuff out with hex editors, we'd deal with .dds, but with XCOM 2 I haven't done so at all.
  21. http://forums.nexusmods.com/index.php?/topic/3849350-when-is-the-earliest-i-can-access-state-objects/page-2&do=findComment&comment=35155610
  22. Sorry you're having trouble getting it to work. I guess I'm not quite sure how a new enemy is displaying as a Muton. Would probably have to see the code to have a chance to figure it out. One thought that occurs to me is to double-check that you've added your art package to your ModBuddy project. Adding it is necessary to get it to copy over to the game/SDK mod folders, as well as to run the precompileshaders commandlet, which builds the ModShaderCache. ------------------------- As for the Centurion, the process worked like this : Exported Muton Skeletal Mesh from UnrealEd to fbx file Imported fbx into Blender (JC Lewis did all the Blender stuff) Created new mesh for helmet, with separate UV mapping to a separate UV space Baked hi-poly helmet mesh to low-poly, generating normal map (NRM) Rigged helmet to a new bone attached to the Muton skeleton in the head Added detailing to DIF and MSK textures Imported skeletal mesh into UnrealEd Imported DIF, NRM, MSK textures into UnrealEd Created new material instance using Firaxis-created material (Alien_SD_SSS I think?) Configured material instance settings, including textures Applied new material to Material[1] of skeletal mesh Set new Centurion archetype to use new skeletal mesh Set Character template to use new archetype
  23. The chain goes : code => archetype => skeletal mesh => material instance/material => texture. Code is set in the Character template, and looks like : CharTemplate.strPawnArchetypes.AddItem("LWMutonM2.ARC_GameUnit_MutonM2"); The reason it's a dynamic array of PawnArchetypes is to support multiple model types per unit. This is currently used to support the male/female ADVENT variants. Each instance randomly gets one of the configured archetypes (so if there's only one, that's it 100%). In the Archetype, the Skeletal Mesh is in Pawn/Mesh/SkeletalMeshComponent/SkeletalMesh. Within the Skeletal Mesh, the material is set in the Properties-Mesh tab, in SkeletalMesh/Materials/[#] -- skeletal mesh can be a "composite" of multiple submeshes, each with its own UVmap and material definition. Within a material instance, textures are typically in the Parameter Groups/Textures. Rarely, a material is used directly instead of a material instance, in which case the texture is basically hard-coded into the material.
  24. I think the only aspect of gamestates you really need for techs is the serializability -- i.e. if a tech is finished, or if not, how much progress has been made. But each tech is more of a singleton with respect to its template, agreed. But yes, it's quite common for gamestate to "pass through" and read template data. For example, a weapon gamestate doesn't store the range table for the weapon internally -- instead it always retrieves it from the corresponding template. This is why you can change the range table in the config and have it take immediate effect, even in an existing tactical save. local XComGameState_Item Weapon; WeaponTemplate = X2WeaponTemplate(Weapon.GetMyTemplate()); if (WeaponTemplate != none) { Tiles = Shooter.TileDistanceBetween(Target); if (WeaponTemplate.RangeAccuracy.Length > 0) { if (Tiles < WeaponTemplate.RangeAccuracy.Length) Modifier = WeaponTemplate.RangeAccuracy[Tiles]; else // if this tile is not configured, use the last configured tile Modifier = WeaponTemplate.RangeAccuracy[WeaponTemplate.RangeAccuracy.Length-1]; } }
  25. You aren't really "duplicating" per se. You are setting up both templates and gamestates. The gamestate is initialized using a particular template. Templates are akin to a class -- there is only one per definition (so not instanced) and they are not serialized into the savefile, but are instead re-created each time the game runs. Gamestates are akin to an object -- there can be many separate instances and they are serialized into the savefile, and so get loaded with a LoadGame. In your particular instance (a tech) the distinction isn't as clear, because you only have one instance of each tech, because that's how techs work. However, for an Character, it becomes much more obvious what the difference is, and why it is there. An X2CharacterTemplate is a generic container for creating character templates. It can create, for example, a "Muton Character Template". However, there is only ever ONE such "Muton Character Template", and it is not saved as part of the savefile, but is loaded/created from code/config each time the game launches. This isn't sufficient for gameplay. When creating multiple instances of Mutons, each instance creates a "Muton Character Gamestate". This is initialized using data from the "Muton Character Template", and keeps a reference to the template for running various common code operations. However, particular data about each instance is kept separate (current HP, status effects, position, and so on), in each instance of the "Muton Character Gamestate", and is serialized into the savefile so that the state is preserved through save/load operations.
×
×
  • Create New...