Jump to content

R&D Changing Item Meshes


Amineri

Recommended Posts

I thought I'd start a new R&D thread for this, since it's a pretty major component of a lot of mods for other games, but is noticably lacking (so far) in XCOM.

 

Essentially there are two different things that could be done in this regard :

1) Changing any existing model into something else

2) Adding additional models (new items, new aliens, etc)

 

The first I think is a bit easier, so I'm going to touch on that first.

 

The good news is that Unreal Engine is actually designed to be fairly moddable, but its moddability is really designed around the "core market" of 3D First Person Shooters.

 

Pretty much all UDK creations use the format of putting all unreal assets into a single folder with "<name>Game" for a name. XCOM is no exception, as most all of the assets are in the "XComGame" folder, with the notable exception of the binaries. This appears to be consistent with other UDK creations.

 

When the game launches, most all upk assets are loaded, scanning all subfolders of XComGame. Note that for the "expansion" Enemy Within there is actually a separate "XComGame" folder which duplicates most of the unreal assets. It is an expansion in the sense that the Enemy Within code was built on the existing Enemy Unknown code, but install-wise it functions as a completely separate game.

 

To see how to add new art assets, it's most useful to look at the DLC for Enemy Unknown. Most of the regular assets reside in the CookedPCConsole folder, including the bulk of the unrealscript in XComGame.upk and XComStrategyGame.upk, but also including all of the model/animation/sound assets. However there is also content added in the XComGame/DLC folder.

 

Digging into the Slingshot DLC, it contains its own CookedPCConsole and Config folder, which contains separately "cooked" upks and config files. This is part of the reason for the distinction between the "Default<config>.ini" files in the main config folder and the "XCom<config>.ini" files in the My Games folder -- the "XCom<config>.ini" files are combined forms of the various config files scattered throughout the XComGame root folder.

 

For example, the Slingshot (Day60DLC) has a DefaultContent.ini that includes various deco assets, new hair/helmet options, as well as Zhang's "civilian" body for the Low Places mission :

+BodyPackageInfo=(Id=309,CustomTag="Zhang",ArchetypeName="Body_Zhang.ARC_Body_Zhang", Gender=eGender_Male, Character=eChar_Civilian, Race=eRace_Asian, Type=eCivilian_Cold)

The DLC_Day60/CookedPCConsole folder contains these art assets, "Body_Zhang_SF.upk", along with a local texture files, "CharTextures_DLC_Day060.tfc", "Lighting_DLC_Day060.tfc", "Textures_DLC_Day060.tfc".

 

Presumably Firaxis used UDK to create this separately cooked DLC which can reside in a separate folder within the main XComGame folder, yet still be loaded. Unfortunately I'm not very proficient with either UDK or 3D modelling in general.

 

However, there is a further step beyond simply creating new content via UDK and getting Unreal Engine to load the assets -- the assets have to actually be called during the game.

 

For this I'm going to describe the steps by which we converting the Genemod Armors included in Enemy Within to new armors (which in vanilla are visually distinct but have no other gameplay purpose other than to designate genemodded soldiers). In principle if a 3D modeller were able to create a brand new armor model/animation set, this same technique could be used to swap/insert it into the game.

 

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

 

1) The mappings from "pawntype" to "model" for all of the genemod armors had already been done in DefaultContent.ini. For example :

UnitPackageInfo=(PawnType=ePawnType_Male_2_Skeleton_GM,ArchetypeName="Soldier_MaleSkeleton_GM.ARC_MaleSkeleton_GM")
UnitPackageInfo=(PawnType=ePawnType_Male_3_Ghost_GM,ArchetypeName="Soldier_MaleGhost_GM.ARC_MaleGhost_GM")
UnitPackageInfo=(PawnType=ePawnType_Male_1_Kevlar_GM,ArchetypeName="Soldier_MaleKevlar_GM.ARC_MaleKevlar_GM")

This performs the mapping from byte values (e.g. ePawnType_Male_2_Skeleton_GM = 78) into the actual model (e.g. Soldier_MaleSkeleton_GM_SF.upk is a seek-free unreal packed in the CookedPCConsole folder, and is distinct from the Soldier_MaleSkeleton_SF.upk package). I'm presuming that ARC_MaleSkeleton_GM is a particular asset within the packagefile, but I'm not very familiar with how Unreal 3D models are defined.

 

2) I rewrote the XGBattleDesc.MapSoldierToPawn function to handle new item IDs.

 

We handle new items typically be taking over "marker" positions within the existing EItemType enumeration. For example, eItem_END_WEAPONS = 57 is in EItemType but doesn't represent an existing item in the game. Since we've figured out how to expand enumerations, in general adding new items could be a little simpler. However replaceing existing items is even easier.

 

Following the Skeleton GM armor model example, I put in the code :

case 58:  // eItem_BEGIN_ARMOR, light skeleton / Skeleton Suit
	return(iGender == 1 ? 78 : 71)  // ePawnType_Male_2_Skeleton_GM, ePawnType_Female_2_Skeleton_GM

This creates the mapping from the new EItemID 58 (eItem_BEGIN_ARMOR) to either pawn type for the Skeleton GM (either male or female, depending). The mapping from ePawnType to the actual model was accomplished in step 1.

 

3) Added Deco armor options for the new armor type

 

This is also handled in the DefaultContent.ini, via the new line :

ArmorDeco=(Armor=eItem_BEGIN_ARMOR,ArmorKit=eKit_Deco_Skeleton0)  ; light skeleton / Banshee

In this case I mapped the new item type to use the existing set of Skeleton armor decos.

 

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

 

Essentially this is all that was done. Adding the new armor types was actually quite straightforward, requiring far fewer hex changes than something like the psionic rework or officer system. (like 2 or 3 hex changes for new armors, but 40-50 for each of psionics/officer systems).

 

In principle I think that to change out an existing model for a new one would require only the following :

1) Create the new model, which has to include all necessary animations -- not having an animation generally results in the game hanging

2) Compile the model (apparently UE3 can work with uncooked models on the PC) into a separate subfolder inside XComGame

3) Either create new config files that override the UnitPackageInfo settings in the core game (to map an existing pawn or itemtype to the new art asset), or modify the DefaultContent.ini in the core game itself

 

The different types of PackageInfos in DefaultContent.ini appear to be:

  • UnitPackageInfo -- defines armors -- this doesn't include the head, as the head as another attached "item"
  • HeadPackageInfo -- defines possible heads that can be attached to the armor
  • WeaponPackageInfo -- defines all possible weapons in the game -- basically anything "equippable" is considered a weapon (e.g. SCOPE, nano-fiber vest)
  • ArmorKitPackageInfo -- armor kits, including both weapon kits (e.g. Shotgun or LMG, or deco kits)
  • HairPackageInfo -- this is hair or helmet, and is an attachment to a head

As an example of a particular HairPackageInfo:

HairPackageInfo=(Id=422,CustomTag="Annette",ArchetypeName="Hair_FemHair_DLC0.FemHair_DLC0",Gender=eGender_Female)

is how the Annette special character custom hair is configured. This corresponds to the "Hair_FemHair_DLC0_SF.upk" file in CookedPCConsole.

 

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

 

Unfortunately I can't easily test much of this out since I'm not familiar with either 3D modeling in general or UDK in particular.

Link to comment
Share on other sites

I've done a bit more searching and it looks as though Gildor's UE Viewer tool (http://www.gildor.org/en/projects/umodel) can extract all of the skeleton/mesh/texture/animation components from an existing upk, and his ActorX plug-in for 3DS Max (http://www.gildor.org/projects/unactorx) will allow the importing of these files into 3DS Max.

 

From Gildor's page:

 

ActorX Importer complements functionality of the Epic Games ActorX Exporter. Epic's plugin can export psk and psa files from 3ds Max, Maya and XSI. Plugin from this page has opposite functionality: you can load psk and psa files back to the 3ds Max (there is no versions for Maya and XSI).

 

So probably the best starting point would be:

1) Extract psk/psa files from an XCOM upk with umodel

2) Import psk/psa files into 3DS Max with ActorX Exporter

3) Use existing animations/skeletons/wireframes as template to create new model

4) Import 3DS Max object into UDK

5) Use UDK to create "plug in" upk/tfc files

6) Place new upk/tfc files in subfolder of XComGame

7) Alter config files to load new art assets

 

This should allow the complete replacement of 3D art assets.

Link to comment
Share on other sites

Pretty intriguing stuff. I've dabbled in 3D modeling myself, but unfortunately there doesn't seem to be a lot of documentation about the Unreal Engine's native internal model format around. Gildor's UE Viewer (formerly umodel) apparently can parse that format for display/export purposes, but contrary to several statements he made throughout the years he hasn't released the source code to it so far (bummer).

 

Furthermore, from what I can tell the ActorX format seems to be a legacy format used in earlier renditions of the engine but has largely been superseded by the FBX format as the preferred 3D interchange format. In any case, if UE Viewer's export function in conjunction with an ActorX import plugin for some 3D modelling software creates a scene that when re-imported into the UDK is functionally equivalent to the original model we should be good to go.

 

To test this out I'd propose to start off with low-complexity model like hair styles/headgear before moving on to creating/modifying more complex models like heads, bodies (i.e. armours), weapons or whole aliens. Besides, judging from the popularity of hair mods in other games like The Sims or the Elder Scrolls games I believe people would be all over this :laugh:

 

X.

Link to comment
Share on other sites

exalt cultist with robes or alien armor? finally! let's just hope we don't get our mandatory xcom nudity mod first.

no, seriously guys, if you manage to replace or even add some meshes all we need to do is to recruit some of the community artists and we are up to a truly epic modding experience. please keep trying!

Link to comment
Share on other sites

I'd agree that low-complexity things like hats/helmets would be the ideal first test subjects, even better than hair (since hair has animations, which hats/helmets don't AFAIK). Hair models are also generally usable on civilians, also this might be configurable (I say "might" because Firaxis has sometimes set up similar flags that had their functionality removed.

 

However, both hair and helmet options are configured together (unsurprisingly, since they are put together in a single spinner).

 

The basic structure that is filled out is :

struct native XComHairPackageInfo extends XComPackageInfo
{
    var config int Id;
    var config name CustomTag;
    var config XGTacticalGameCoreNativeBase.EGender Gender;
    var config XGGameData.ECharacter Character;
    var config XGTacticalGameCoreNativeBase.ECharacterRace Race;
    var config XGGameData.EPawnType Pawn;
    var config bool bCanUseOnCivilian;
    var config bool bIsHelmet;
};

The parent class XComPackageInfo contains:

struct native XComPackageInfo
{
    var init config string ArchetypeName;
    var transient Object Archetype;
    var init array<init ArchetypeLoadedCallback> LoadedCallbacks;

};

Not all structure elements need be filled out -- any undefined structure elements get the default values (e.g. 0, false, "", none)

 

All of the existing XComHairPackageInfo configuration is done in DefaultContent.ini. For EnemyWithin this looks like :

 

 

HairPackageInfo=(Id=1,ArchetypeName="Hair_FemHair_A.ARC_Hair_FemHair_A", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=2,CustomTag="EscapedAbductee",ArchetypeName="Hair_FemHair_B.ARC_Hair_FemHair_B", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=3,ArchetypeName="Hair_FemHair_C.ARC_Hair_FemHair_C", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=4,ArchetypeName="Hair_FemHair_D.ARC_Hair_FemHair_D", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=5,ArchetypeName="Hair_FemHair_E.ARC_Hair_FemHair_E", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=6,ArchetypeName="Hair_FemHair_F.ARC_Hair_FemHair_F", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=7,ArchetypeName="Hair_FemHair_G.ARC_Hair_FemHair_G", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=8,ArchetypeName="Hair_FemHair_H.ARC_Hair_FemHair_H", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=9,ArchetypeName="Hair_FemHair_I.ARC_Hair_FemHair_I", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=10,ArchetypeName="Hair_FemHair_J.ARC_Hair_FemHair_J", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=11,ArchetypeName="Hair_FemHair_K.ARC_Hair_FemHair_K", Gender=eGender_Female)
;HairPackageInfo=(Id=12,ArchetypeName="Hair_FemHair_L.ARC_Hair_FemHair_L", Gender=eGender_Female, bCanUseOnCivilian=true)
;HairPackageInfo=(Id=13,ArchetypeName="Hair_FemHair_M.ARC_Hair_FemHair_M", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=14,CustomTag="JoeKelly",ArchetypeName="Hair_MaleHair_A.ARC_Hair_MaleHair_A", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=15,CustomTag="Bodyguard1",ArchetypeName="Hair_MaleHair_B.ARC_Hair_MaleHair_B", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=16,CustomTag="Bodyguard",ArchetypeName="Hair_MaleHair_C.ARC_Hair_MaleHair_C", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=17,ArchetypeName="Hair_MaleHair_D.ARC_Hair_MaleHair_D", Gender=eGender_Male)
HairPackageInfo=(Id=18,CustomTag="Scientist",ArchetypeName="Hair_MaleHair_E.ARC_Hair_MaleHair_E", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=19,ArchetypeName="Hair_MaleHair_F.ARC_Hair_MaleHair_F", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=20,CustomTag="Engineer",ArchetypeName="Hair_MaleHair_G.ARC_Hair_MaleHair_G", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=21,CustomTag="General",ArchetypeName="Hair_MaleHair_H.ARC_Hair_MaleHair_H", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=22,CustomTag="MinisterThorne",ArchetypeName="Hair_MaleHair_I.ARC_Hair_MaleHair_I", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=23,CustomTag="MinisterHutch",ArchetypeName="Hair_MaleHair_J.ARC_Hair_MaleHair_J", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=24,ArchetypeName="Hair_MaleHair_K.ARC_Hair_MaleHair_K", Gender=eGender_Male, bCanUseOnCivilian=true)
;HairPackageInfo=(Id=25,ArchetypeName="Hair_MaleHair_L.ARC_Hair_MaleHair_L", Gender=eGender_Male)

 

 

 

for the base-game hair styles. Note that different hairstyles are locked to different genders (in general), and that most of them are apparently configured to be usable to construct civilians for terror missions.

 

Much further down in the same file is the "extra content" section, which configures new hair styles from the DLCs :

 

 

HairPackageInfo=(Id=307,CustomTag="Zhang",ArchetypeName="Hair_MaleHair_N.ARC_Hair_MaleHair_N", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=325,CustomTag=,ArchetypeName="Hair_MaleHair_O.ARC_Hair_MaleHair_O", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=326,CustomTag=,ArchetypeName="Hair_FemHair_O.ARC_Hair_FemHair_O", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=327,CustomTag=,ArchetypeName="Hair_FemHair_N.ARC_Hair_FemHair_N", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=410,Gender=eGender_Male,ArchetypeName="Hair_MaleHair_DLC0.MaleHair_DLC0")
HairPackageInfo=(Id=422,CustomTag="Annette",ArchetypeName="Hair_FemHair_DLC0.FemHair_DLC0",Gender=eGender_Female)
HairPackageInfo=(Id=412,Gender=eGender_Male,ArchetypeName="Hair_MaleHair_DLC1.MaleHair_DLC1")
HairPackageInfo=(Id=413,Gender=eGender_Female,ArchetypeName="Hair_FemHair_DLC1.FemHair_DLC1")
HairPackageInfo=(Id=203,ArchetypeName="Hair_OriginalSoldier.ARC_Hair_OriginalSoldier",Gender=eGender_Male)
HairPackageInfo=(Id=204,ArchetypeName="Hair_OriginalSoldier.ARC_Hair_OriginalSoldierFem",Gender=eGender_Female)

 

 

 

as well as the helmets :

 

 

HairPackageInfo=(Id=200,ArchetypeName="Helmet_Kevlar0.KevlarHelmet0",bIsHelmet=true)
HairPackageInfo=(Id=303,ArchetypeName="Helmet_Kevlar1.KevlarHelmet1",bIsHelmet=true)
HairPackageInfo=(Id=403,ArchetypeName="Helmet_Kevlar2.KevlarHelmet2",bIsHelmet=true)
HairPackageInfo=(Id=305,ArchetypeName="Helmet_Kevlar3.KevlarHelmet3",bIsHelmet=true)
HairPackageInfo=(Id=308,ArchetypeName="Helmet_Kevlar8.KevlarHelmet8",bIsHelmet=true)
HairPackageInfo=(Id=310,ArchetypeName="Helmet_Kevlar5.KevlarHelmet5",bIsHelmet=true)
HairPackageInfo=(Id=425,ArchetypeName="Helmet_Kevlar9.KevlarHelmet9",bIsHelmet=true)
HairPackageInfo=(Id=426,ArchetypeName="Helmet_Kevlar10.KevlarHelmet10",bIsHelmet=true)
HairPackageInfo=(Id=427,ArchetypeName="Helmet_Kevlar11.KevlarHelmet11",bIsHelmet=true)
HairPackageInfo=(Id=428,ArchetypeName="Helmet_Kevlar12.KevlarHelmet12",bIsHelmet=true)
HairPackageInfo=(Id=202,ArchetypeName="Helmet_Carapace0.CarapaceHelmet0",bIsHelmet=true)
HairPackageInfo=(Id=401,ArchetypeName="Helmet_Carapace2.CarapaceHelmet2",bIsHelmet=true)
HairPackageInfo=(Id=320,ArchetypeName="Helmet_Carapace3.CarapaceHelmet3",bIsHelmet=true)
HairPackageInfo=(Id=322,ArchetypeName="Helmet_Carapace5.CarapaceHelmet5",bIsHelmet=true)
HairPackageInfo=(Id=302,ArchetypeName="Helmet_Ghost0.GhostHelmet0",bIsHelmet=true)
HairPackageInfo=(Id=402,ArchetypeName="Helmet_Ghost1.GhostHelmet1",bIsHelmet=true)
HairPackageInfo=(Id=315,ArchetypeName="Helmet_Ghost4.GhostHelmet4",bIsHelmet=true)
HairPackageInfo=(Id=313,ArchetypeName="Helmet_Ghost2.GhostHelmet2",bIsHelmet=true)
HairPackageInfo=(Id=304,ArchetypeName="Helmet_Psi0.PsiHelmet0",bIsHelmet=true)
HairPackageInfo=(Id=404,ArchetypeName="Helmet_Psi1.PsiHelmet1",bIsHelmet=true)
HairPackageInfo=(Id=201,ArchetypeName="Helmet_Skeleton0.SkeletonHelmet0",bIsHelmet=true)
HairPackageInfo=(Id=405,ArchetypeName="Helmet_Skeleton2.SkeletonHelmet2",bIsHelmet=true)
HairPackageInfo=(Id=430,ArchetypeName="Helmet_Skeleton3.SkeletonHelmet3",bIsHelmet=true)
HairPackageInfo=(Id=460,ArchetypeName="Helmet_CarterHat.CarterHat",bIsHelmet=true)

 

 

 

Back with Enemy Unknown, DLC content was made available in separate DLC versions of the *Content.ini files, both named XComContent.ini but placed within their DLC subfolders.

 

-----------

 

All of this hair/helmet/hat configuration data appears to be read in via the native function XComContentManager.GetContentInfo_Hair :

native final function bool GetContentInfo_Hair(int Id, out XComHairPackageInfo HairInfo);

which apparently retrieves a particular XComHairPackageInfo based on supplied Id.

 

This is called (along with similar other functions for other types of content) in XGBattleDesc.BuildSoldierContent :

        if(m_kAppearance.iHaircut != -1)
        {
            kRequest.eCategory = 6;
            kRequest.iID = m_kAppearance.iHaircut;
            if(!AreHelmetsAllowed())
            {
                if(XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentInfo_Hair(m_kAppearance.iHaircut, kHairInfo))
                {
                    if(kHairInfo.bIsHelmet)
                    {
                        if(IsFemale())
                        {
                            kRequest.iID = 2;
                        }
                        else
                        {
                            kRequest.iID = 18;
                        }
                    }
                }
            }
            PawnContentRequests.AddItem(kRequest);
        }

The setting up of the hair spinner for the Customize UI in the strategy game appears to use a XComUnitPawn.PossibleHairs which is configured in XComHumanPawn.state'InHQ'.GetPossibleCustomParts :

    XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForRace(6, byte(m_kAppearance.iRace), RaceHairs,, true);
    XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForGender(6, byte(m_kAppearance.iGender), GenderHairs,, true);
    XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForCharacter(6, byte(inCharacter.iType), CharacterHairs,, true);
    XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForPawn(6, PawnType, PawnHairs, true);
    XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).IntersectContentIds(RaceHairs, GenderHairs, RaceGenderHairs);
    XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).IntersectContentIds(CharacterHairs, PawnHairs, CharacterPawnHairs);
    XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).IntersectContentIds(RaceGenderHairs, CharacterPawnHairs, PossibleHairs);

It first generates lists based on the current soldiers' race, gender, type and pawn. These are then intersected to form the final possible list.

 

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

 

So, to insert a new hair/helmet option, it should be as simple as adding a new line of the format :

HairPackageInfo=(Id=<NewID>,ArchetypeName="<PackageName>.<AssetName>", Gender=<Gender>, bCanUseOnCivilian=<true/false>)

The ID= and ArchetypeName= fields are required, but other fields are optional:

  • Gender=<eGender_Male/eGender_Female> -- defaults to eGender_None, so could be used by either
  • bCanUseOnCivilian=<true/false> - defaults to false, not usable by civilians
  • bIsHelmet=<true/false> - defaults to false
  • Race=<eRace_Caucasian/eRace_African/eRace_Asian/eRace_Hispanic> -- unused by vanilla, so unsure if/how this works
  • Character=<???> -- unsure how this could be used
  • Pawn=<???> -- could in theory be used to restrict certain types of hair/helmets to certain armors (based on pawn), but isn't used in vanilla

The Character here isn't referring to something like special characters or easter egg characters -- ECharacter differentiates civilians from soldiers from various alien types (e.g. eChar_Soldier, eChar_Sectoid, eChar_eChar_MutonBerserker). However this is all located inside XComHumanPawn, so none of it can be applied to aliens.

 

Of course the tricky part is creating the new upk asset file :smile:

 

For some initial testing I opened up Helmet_Kevlar0_SF.upk in umodel. It's a pretty straightforward object -- no bones, no animations, no attachment sockets. There are four different versions (2 male and 2 female) with from 1630-1690 verts each.

Edited by Amineri
Link to comment
Share on other sites

  • Recently Browsing   0 members

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