Units deployment - XCOM:EU 2012
Contents
Overview
The following is derived from the Nexus Forums thread Modding EXALT.
There are three separate deployment mechanisms by which units are placed on a map: one for XCOM Soldiers, one for 'walk-in' or 'static' aliens, and one for 'drop-in' or 'dynamic' aliens. All of these units are placed on defined 'spawn point' locations on each map. (See the related wiki articles Maps - XCOM:EU 2012 and Spawn Points - XCOM:EU 2012 for discussion on those specific topics, and in particular instructions on how to extract the relevant map data to examine that information.)
Programs and Tools
- Modding Tools - XCOM:EU 2012
- UPKDecompressor
- UPKUtils
XCOM soldiers deployment
Soldiers deployment is tied in to TheWorld.PersistentLevel.PlayerStart_X map objects (where "X" is a number), which determine properties of the player start location. Each start location has 6 spawn points determined by TheWorld.PersistentLevel.XComSpawnPoint_X objects. Spawn points are tied in to player start locations by the "Tag" property.
For example, for map CSmallScout_Badlands there are 3 possible starting points: TheWorld.PersistentLevel.PlayerStart_0, TheWorld.PersistentLevel.PlayerStart_1 and TheWorld.PersistentLevel.PlayerStart_2. PlayerStart_0' has "Tag" property set to "Group2". There are 6 spawn points with the same tag: TheWorld.PersistentLevel.XComSpawnPoint_0 through TheWorld.PersistentLevel.XComSpawnPoint_5. Spawn points for XCOM Soldiers have the "XComSpawnPoint" type property.
On a side note: Meld Canisters spawn points in EW are tied in to the player start locations via "Tag" as well. For example, there are 4 possible spawn points for meld canisters for each of the three player start points. Meld container spawn points have the "XComMeldContainerSpawnPoint" type property.
Static aliens deployment
Alien pods deployment is handled via XGDeployAI in XComGame. GetPossibleSpawns function requests WorldInfo on AllActors of class XComAlienPod. XComAlienPod_X objects are defined in TheWorld.PersistentLevel of each map. EXALT maps have only two XComAlienPod objects, hence only two pods will be spawned on the map during deployment.
TheWorld.PersistentLevel.XComAlienPod_X objects contain default properties for each XComAlienPod class instance variable. For example, map URB_PierA_Terror_CovExt has two such objects: TheWorld.PersistentLevel.XComAlienPod_1 and TheWorld.PersistentLevel.XComAlienPod_3. Their properties are almost identical, only the Location property is different and sets the pod spawn location:
(See #FindObjectEntry TheWorld.PersistentLevel.XComAlienPod_x in the Separate Content section.)
Another example is map CSmallScout_Badlands, which has four XComAlienPod objects. One of those contains bCommanderPod property set to 1 and stands for commander pod spawn point. XGDeployAI contains GetPossibleCommanderSpawns, GetPossibleSecondarySpawns and GetPossibleSoldierSpawns functions to determine spawn points for corresponding pod types. During deployment only pods of types defined via XComAlienPod map objects are spawned.
Spawn points for alien pods are extracted using the XComAlienPod.GetSpawnPoint function. (See #XComAlienPod.GetSpawnPoint in the Separate Content section.0
Dynamic aliens deployment: Wave System
Council Missions do not use the Wave System; it was introduced for EW. They use triggers instead.
XComWaveSystem is a class in XComGame.UPK which is used to spawn aliens dynamically. Wave system parameters are defined for each map directly in the map's UPK as archetype objects in TheWorld.PersistentLevel. Usually maps contain just one wave system: TheWorld.PersistentLevel.XComWaveSystem_0. You can extract information on wave objects from map file by decompressing it (with UPKDecompressor) and running FindObjectEntry.exe (from UPKUtils):
FindObjectEntry.exe URB_PierA_Terror_CovExt.upk TheWorld.PersistentLevel.XComWaveSystem_0 > TheWorld.PersistentLevel.XComWaveSystem_0.txt
This will place the object information in the file TheWorld.PersistentLevel.XComWaveSystem_0.txt for examination.
XGOvermind.Init function initializes all map spawns and contains InitWaveSystem call (along with DeployPods call).
InitWaveSystem requests WorldInfo on AllActors of class XComWaveSystem and calls XComWaveSystem.Init function. This function checks if a wave is valid and calls InitScalableList function.
InitScalableList iterates through all Pods in AlienSquad and calls AddCharTypeToScalableList for eMain, eSupport1 and eSupport2 types.
AddCharTypeToScalableList sorts aliens by type and adds them to three lists: m_arrScalableList contains all the types available, m_arrScalableFlyInList contains only flying units, and m_arrScalableDropInList contains only characters with HasTraversal property (i.e. those, who can climb ladders).
When the time comes for another wave, SpawnGroup function is called, which in turn calls to the SetScalingAliens function.
If alien group is Scalable (which is set in properties of alien wave), SetScalingAliens initializes AlienType with random alien from one of the Scalable Groups, described above. If the group is not Scalable, it forces ExaltOperatives for Exalt maps and ThinMan for the other maps.
In DefaultMaps.ini AlienType is defined explicitly for Council Missions via DynamicAliens array, but not defined (eChar_None) for Exalt maps. (I think it's done to allow for two different Exalt types (regular and elite) for the same maps.)
Spawn point for the dynamic alien is chosen randomly from the array of available spawn points. And there are plenty of spawn points for Exalt maps. NumAliens is defined via XComWaveSystem_0 for those maps, and changing those values does alter the results.
- DropIn spawns use overwatch. Any EXALT unit can spawn as a DropIn unit, not only snipers. WalkIn units spawn without overwatch. In theory this can be changed, but it won't be easy, as there is no spawn method entry for those waves.
- Spawn time seems to be controlled through linked variables, which are set by events. One can determine which wave spawns when by setting a different number of units per spawn and observing the result in actual gameplay. Or by digging through all sequence action objects, which will be a pain.
Managed to decompile EXALT linked events, which trigger the waves. But it is still a pain. Most triggers are already known from experience: approaching an array, hacking first array, hacking second array, operative approaching evac zone. The trick is to determine which event triggers which wave. And as linked event names are buried inside default properties of SeqEvent object, need to decompile all to find proper triggers.
"Capture and hold" missions are actually easier to comprehend as events have human-friendly names.
URB_CommercialRestaurant_CNH has 2 alien pods too. All exalt maps do. Wave system for this map contains two sets:
- 1st set:
- disabled when TransmitterCaptured
- has 3 waves
- 1st wave appears on turn 2, has 2 groups.
- Groups 0 and 1: 1 unit with 6 possible spawn points, walk in, no overwatch.
- 2nd wave appears on turn 4, has 3 groups.
- Groups 0 and 1: 1 unit with 6 possible spawn points, walk in, no overwatch.
- Group 2: 1 unit with 6 possible spawn points, drop in, overwatch.
- 3rd wave appears on turn 6, has 3 groups.
- Group 0: 2 units, 6 spawn points, walk in, no overwatch.
- Group 1: 1 unit, 6 spawn points, walk in, no overwatch.
- Group 2: 2 units, 6 spawn points, drop in, overwatch.
- 2nd set:
- enabled with TransmitterCaptured
- disabled with DataRelayCaptured
- has 2 waves
- 1st wave: 0 turn after enabled, 3 groups.
- Group 0 and 1: 1 unit with 6 possible spawn points, walk in, no overwatch.
- Group 2: 1 unit with 6 possible spawn points, drop in, overwatch.
- 2nd wave: 2 turn after enabled, 3 groups.
- Group 0 and 1: 1 unit with 6 possible spawn points, walk in, no overwatch.
- Group 2: 1 unit with 6 possible spawn points, drop in, overwatch.
All the maps seem to follow the same pattern, only spawn points are different.
I've been thinking... Spawn point or pod point - it doesn't actually make any difference, as only coordinates are used in most cases. It is possible to iterate through all the XComSpawnPoint_Alien map objects, extract coordinates and set spawn locations, based on those coordinates instead of XComAlienPod's. This will allow for more stationary pods per map.
To summarize the results: since spawn locations are extremely limited (2 for covert extraction maps, 6 for capture and hold maps), we can't increase the number of stationary EXALT pods. So, to increase mission difficulty we need to mod the wave system. There are plenty of spawn points for dynamic aliens (like 20-30 for each map), so it is very possible to increase a number of EXALT reinforcements. Spawn timings are tied in to events. It is theoretically possible to mod those, but, IMO, it won't do any good, as spawn points for each wave are tied in to specific trigger locations. Hence, we need to determine which event triggers which wave and adjust it accordingly. Maps will still be scripted, but will be more difficult.
As a side note, it is possible to mod dynamic alien types inside the wave system class itself. Remember: It cannot help in modding Council Missions, as those use triggers instead of the Wave System.
Separate Content
FindObjectEntry TheWorld.PersistentLevel.XComAlienPod_x
FindObjectEntry Name to find: TheWorld.PersistentLevel.XComAlienPod_1 Found Export Object: 0x000001AA (426): XComAlienPod'TheWorld.PersistentLevel.XComAlienPod_1' TypeRef: 0xFFFFFFB7 -> XComAlienPod ParentClassRef: 0x00000000 -> OwnerRef: 0x0000000C -> PersistentLevel NameIdx: 0x000001E3 (Index) 0x00000002 (Numeric) -> XComAlienPod_1 ArchetypeRef: 0x00000000 -> ObjectFlagsH: 0x00000000 ObjectFlagsL: 0x02070001 0x00000001: Transactional 0x00010000: LoadForClient 0x00020000: LoadForServer 0x00040000: LoadForEdit 0x02000000: HasStack SerialSize: 0x0000012F (303) SerialOffset: 0x00057958 ExportFlags: 0x00000000 NetObjectCount: 0 GUID: 00000000000000000000000000000000 Unknown1: 0x00000000 Attempting deserialization: UObject: PrevObjRef = 0xFFFFFFB7 -> XComAlienPod UDefaultPropertiesList: UDefaultProperty: NameIdx: 0x00000140 (Index) 0x00000000 (Numeric) -> PodIndex TypeIdx: 0x000000B9 (Index) 0x00000000 (Numeric) -> IntProperty PropertySize: 0x00000004 ArrayIdx: 0x00000000 Integer: 0x00000000 = 0 UDefaultProperty: NameIdx: 0x000000C5 (Index) 0x00000000 (Numeric) -> LightEnvironment TypeIdx: 0x0000010D (Index) 0x00000000 (Numeric) -> ObjectProperty PropertySize: 0x00000004 ArrayIdx: 0x00000000 Object: 0x00000004 = DynamicLightEnvironmentComponent_1387 UDefaultProperty: NameIdx: 0x00000101 (Index) 0x00000000 (Numeric) -> NumAliens_Min TypeIdx: 0x000000B9 (Index) 0x00000000 (Numeric) -> IntProperty PropertySize: 0x00000004 ArrayIdx: 0x00000000 Integer: 0x00000001 = 1 UDefaultProperty: NameIdx: 0x00000100 (Index) 0x00000000 (Numeric) -> NumAliens_Max TypeIdx: 0x000000B9 (Index) 0x00000000 (Numeric) -> IntProperty PropertySize: 0x00000004 ArrayIdx: 0x00000000 Integer: 0x00000003 = 3 UDefaultProperty: NameIdx: 0x00000141 (Index) 0x00000000 (Numeric) -> PodMesh TypeIdx: 0x0000010D (Index) 0x00000000 (Numeric) -> ObjectProperty PropertySize: 0x00000004 ArrayIdx: 0x00000000 Object: 0x00000196 = StaticMeshComponent_272 UDefaultProperty: NameIdx: 0x0000004C (Index) 0x00000000 (Numeric) -> CenterpieceMesh TypeIdx: 0x0000010D (Index) 0x00000000 (Numeric) -> ObjectProperty PropertySize: 0x00000004 ArrayIdx: 0x00000000 Object: 0x00000197 = StaticMeshComponent_273 UDefaultProperty: NameIdx: 0x000000D1 (Index) 0x00000000 (Numeric) -> Location TypeIdx: 0x000001A8 (Index) 0x00000000 (Numeric) -> StructProperty PropertySize: 0x0000000C ArrayIdx: 0x00000000 InnerNameIdx: 0x000001D0 (Index) 0x00000000 (Numeric) -> Vector Vector (X, Y, Z) = (0x44341EF0, 0x43D73EFE, 0x3A8FFFFA) = (720.483, 430.492, 0.00109863) UDefaultProperty: NameIdx: 0x00000018 (Index) 0x00000000 (Numeric) -> bDirtyComponents TypeIdx: 0x0000002B (Index) 0x00000000 (Numeric) -> BoolProperty PropertySize: 0x00000000 ArrayIdx: 0x00000000 Boolean value: 0x00 = false UDefaultProperty: NameIdx: 0x000001AA (Index) 0x00000000 (Numeric) -> Tag TypeIdx: 0x000000F8 (Index) 0x00000000 (Numeric) -> NameProperty PropertySize: 0x00000008 ArrayIdx: 0x00000000 Name: 0x000001E3 (Index) 0x00000000 (Numeric) = XComAlienPod UDefaultProperty: NameIdx: 0x000000FD (Index) 0x00000000 (Numeric) -> None Stream relative position: 0x0000012F (303) UObjectUnknown: Object unknown, can't deserialize!
XComAlienPod.GetSpawnPoint
function XComSpawnPoint_Alien GetSpawnPoint(int iAlien, out Vector vLoc_Out, optional bool bUseDefault) { local XComSpawnPoint_Alien kSpawnPoint; bUseDefault = false; if(!bUseDefault && CustomStartLocations.Length != 0) { if(iAlien < CustomStartLocations.Length) { vLoc_Out = CustomStartLocations[iAlien].Location; vLoc_Out = XComTacticalGRI(WorldInfo.GRI).GetClosestValidLocation(vLoc_Out, none,, false); kSpawnPoint = Spawn(class'XComSpawnPoint_Alien',,, vLoc_Out, rotator(Location - vLoc_Out),, true); return kSpawnPoint; } } else { if(iAlien < NumAliens) { vLoc_Out = GetDistributedLocationAround(Location, iAlien, Rotation, true); kSpawnPoint = Spawn(class'XComSpawnPoint_Alien',,, vLoc_Out, rotator(Location - vLoc_Out),, true); return kSpawnPoint; } } }
References
Referred to by this article:
That refer to this article: