class Shara_SkinColors_Tools extends Object abstract; /* Thought this will be a good idea to put all of the final static functions I use for dyeability in one file. If you wanna use it, you can straight copy the whole file, but don't forget to change the file name. This is a base file. Please add suffix at the end of class name to differentiate classes and remove this line and maybe add your own (describing what mod it's used in). All functions are written by Shararamosh. Last edited: 15.03.2024 2:39 GMT+3. This is an original file. Note to myself: add new function to this and this file only and only then update the mod ones. */ final static function LinearColor GetLinearColorFromColor(Color c) //Returns gamma-corrected LinearColor. { return MakeLinearColor((float(c.R)/255.0)**2.2, (float(c.G)/255.0)**2.2, (float(c.B)/255.0)**2.2, float(c.A)/255.0); } final static function Color GetColorFromLinearColor(LinearColor lc) //Considers that LinearColor is gamma-corrected. { return MakeColor(byte((lc.R**(1.0/2.2))*255.0), byte((lc.G**(1.0/2.2))*255.0), byte((lc.B**(1.0/2.2))*255.0), byte(lc.A*255.0)); } final static function UpdateColorsTextures(Object o, class SkinClass) //Applies colors defined in Skin to object. Object can be either Actor (will use GetAllMeshComponentsByActor to get meshes), MeshComponent or MaterialInstance. { local int i, j; local SkinColors iSkinColor; local LinearColor lc; local Color c; local Texture2D Tex; local Array TextureSlots; local Array comps; local MaterialInstance inst; if (Actor(o) != None) comps = GetAllMeshComponentsByActor(Actor(o)); else if (MeshComponent(o) != None) comps.AddItem(MeshComponent(o)); else inst = MaterialInstance(o); if (comps.Length < 1 && (inst == None || !inst.IsInMapOrTransientPackage())) return; if (SkinClass == None) SkinClass = class'Hat_Collectible_Skin'; SkinClass.static.GetTextureSlotList(TextureSlots); if (inst != None) { SetMaterialInstanceTextureSlots(inst, TextureSlots); for (i = 0; i < SkinClass.const.SkinColorNum; i++) { iSkinColor = SkinColors(i); c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; if (SkinClass.static.IsSlotEmpty(c, Tex) && iSkinColor == SkinColor_HatAlt) { iSkinColor = SkinColor_Hat; if (SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) iSkinColor = SkinColor_Dress; if (!SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) { c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; c.R *= 0.6; c.G *= 0.6; c.B *= 0.6; } } if (SkinClass.static.IsSlotEmpty(c, Tex) && iSkinColor == SkinColor_Hat) { iSkinColor = SkinColor_Dress; if (!SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) { c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; } } if (SkinClass.static.IsSlotEmpty(c, Tex) && iSkinColor == SkinColor_HatBand) { iSkinColor = SkinColor_Cape; if (!SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) { c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; } } if (SkinClass.static.IsSlotEmpty(c, Tex)) ClearMaterialInstanceVectorValue(inst, Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(iSkinColor))); else { if (Tex != None) { lc.R = SkinClass.default.SkinTextureInfo[iSkinColor].UVScale * (SkinClass.default.SkinTextureInfo[iSkinColor].DisableNormalMap ? -1.0 : 1.0); lc.G = SkinClass.default.SkinTextureInfo[iSkinColor].Angle; lc.B = 0.0; lc.A = TextureSlots.Find(Tex); } else { lc.R = (float(c.R)/255.0)**2.2; lc.G = (float(c.G)/255.0)**2.2; lc.B = (float(c.B)/255.0)**2.2; lc.A = -1.0; } SetMaterialInstanceVectorValue(inst, Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(iSkinColor)), lc); } } return; } for (j = 0; j < comps.Length; j++) { if (comps[j] == None) continue; SetTextureSlots(comps[j], TextureSlots); for (i = 0; i < SkinClass.const.SkinColorNum; i++) { iSkinColor = SkinColors(i); c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; if (SkinClass.static.IsSlotEmpty(c, Tex) && iSkinColor == SkinColor_HatAlt) { iSkinColor = SkinColor_Hat; if (SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) iSkinColor = SkinColor_Dress; if (!SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) { c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; c.R *= 0.6; c.G *= 0.6; c.B *= 0.6; } } if (SkinClass.static.IsSlotEmpty(c, Tex) && iSkinColor == SkinColor_Hat) { iSkinColor = SkinColor_Dress; if (!SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) { c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; } } if (SkinClass.static.IsSlotEmpty(c, Tex) && iSkinColor == SkinColor_HatBand) { iSkinColor = SkinColor_Cape; if (!SkinClass.static.IsSlotEmptyByIndex(iSkinColor)) { c = SkinClass.default.SkinColor[iSkinColor]; Tex = SkinClass.default.SkinTextureInfo[iSkinColor].Texture; } } if (SkinClass.static.IsSlotEmpty(c, Tex)) ClearMaterialVectorValueMesh(comps[j], Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(iSkinColor))); else { if (Tex != None) { lc.R = SkinClass.default.SkinTextureInfo[iSkinColor].UVScale * (SkinClass.default.SkinTextureInfo[iSkinColor].DisableNormalMap ? -1.0 : 1.0); lc.G = SkinClass.default.SkinTextureInfo[iSkinColor].Angle; lc.B = 0.0; lc.A = TextureSlots.Find(Tex); } else { lc.R = (float(c.R)/255.0)**2.2; lc.G = (float(c.G)/255.0)**2.2; lc.B = (float(c.B)/255.0)**2.2; lc.A = -1.0; } SetMaterialVectorValueMesh(comps[j], Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(iSkinColor)), lc); } } } } final static function ApplyColorsObject(Object o, Array AppliedColors, Array AppliedTextures) //Applies specific set of colors and textures to Object. Object can be either Actor (will use GetAllMeshComponentsByActor to get MeshComponents), MeshComponent or MaterialInstance. { local int i, j, n; local LinearColor lc; local Array TextureSlots; local bool ShouldClean; local Array comps; local MaterialInstance inst; if (Actor(o) != None) comps = GetAllMeshComponentsByActor(Actor(o)); else if (MeshComponent(o) != None) comps.AddItem(MeshComponent(o)); else inst = MaterialInstance(o); if (ApplyColorsToMaterialInstance(inst, AppliedColors, AppliedTextures)) return; if (comps.Length < 1) return; AppliedColors.Length = Min(AppliedColors.Length, class'Hat_Collectible_Skin'.const.SkinColorNum); AppliedTextures.Length = Min(AppliedTextures.Length, class'Hat_Collectible_Skin'.const.SkinColorNum); for (i = 0; i < AppliedTextures.Length; i++) { if (AppliedTextures[i].Texture == None) continue; if (TextureSlots.Find(AppliedTextures[i].Texture) > -1) continue; if (TextureSlots.Length > class'Hat_MaterialExpressionDynamicLUT'.const.SupportedTextureSlots) continue; TextureSlots.AddItem(AppliedTextures[i].Texture); } n = Min(AppliedColors.Length, AppliedTextures.Length); for (i = 0; i < comps.Length; i++) { if (comps[i] == None) continue; SetTextureSlots(comps[i], TextureSlots); for (j = 0; j < n; j++) { ShouldClean = true; if (AppliedTextures[j].Texture != None) { ShouldClean = false; lc.R = AppliedTextures[j].UVScale*(AppliedTextures[j].DisableNormalMap ? -1.0 : 1.0); lc.G = AppliedTextures[j].Angle; lc.B = 0.0; lc.A = float(TextureSlots.Find(AppliedTextures[j].Texture)); } else { lc = AppliedColors[j]; if (lc.R != 0.0 || lc.G != 0.0 || lc.B != 0.0) ShouldClean = false; } if (ShouldClean) ClearMaterialVectorValueMesh(comps[i], Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(SkinColors(j)))); else SetMaterialVectorValueMesh(comps[i], Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(SkinColors(j))), lc); } } } final static function bool ApplyColorsToMaterialInstance(MaterialInstance inst, Array AppliedColors, Array AppliedTextures) //Special case - applying colors and textures to a single MaterialInstance. { local int i; local LinearColor lc; local Array TextureSlots; local bool ShouldClean; if (inst == None) return false; if (!inst.IsInMapOrTransientPackage()) return true; for (i = 0; i < AppliedTextures.Length; i++) { if (AppliedTextures[i].Texture == None) continue; if (TextureSlots.Find(AppliedTextures[i].Texture) > -1) continue; if (TextureSlots.Length > class'Hat_MaterialExpressionDynamicLUT'.const.SupportedTextureSlots) continue; TextureSlots.AddItem(AppliedTextures[i].Texture); } SetMaterialInstanceTextureSlots(inst, TextureSlots); for (i = 0; i < Min(AppliedColors.Length, AppliedTextures.Length); i++) { ShouldClean = true; if (AppliedTextures[i].Texture != None) { ShouldClean = false; lc.R = AppliedTextures[i].UVScale*(AppliedTextures[i].DisableNormalMap ? -1.0 : 1.0); lc.G = AppliedTextures[i].Angle; lc.B = 0.0; lc.A = float(TextureSlots.Find(AppliedTextures[i].Texture)); } else { lc = AppliedColors[i]; if (lc.R != 0.0 || lc.G != 0.0 || lc.B != 0.0) ShouldClean = false; } if (ShouldClean) ClearMaterialInstanceVectorValue(inst, Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(SkinColors(i)))); else SetMaterialInstanceVectorValue(inst, Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(SkinColors(i))), lc); } return true; } final static function FillUpPlayerAppliedColorsFromSkin(out Array AppliedColors, out Array AppliedTextures, class SkinClass) //Fills up missing values of AppliedColors and AppliedTextures with values defined in the SkinClass's defaultproperties. { local int i; local SkinColors iSkinColor; if (SkinClass == None) return; AppliedColors.Length = class'Hat_Collectible_Skin'.const.SkinColorNum; AppliedTextures.Length = class'Hat_Collectible_Skin'.const.SkinColorNum; for (i = 0; i < class'Hat_Collectible_Skin'.const.SkinColorNum; i++) { iSkinColor = SkinColors(i); AppliedColors[i].A = -1.0; if (AppliedTextures[i].Texture == None && SkinClass.default.SkinTextureInfo[iSkinColor].Texture != None) { AppliedTextures[i] = SkinClass.default.SkinTextureInfo[iSkinColor]; AppliedColors[i].R = 0.0; AppliedColors[i].G = 0.0; AppliedColors[i].B = 0.0; continue; } if (AppliedColors[i].R == 0.0 && AppliedColors[i].G == 0.0 && AppliedColors[i].B == 0.0) { AppliedColors[i].R = (float(SkinClass.default.SkinColor[iSkinColor].R)/255.0)**2.2; AppliedColors[i].G = (float(SkinClass.default.SkinColor[iSkinColor].G)/255.0)**2.2; AppliedColors[i].B = (float(SkinClass.default.SkinColor[iSkinColor].B)/255.0)**2.2; } } } final static function GetPlayerAppliedColors(Actor a, out Array AppliedColors, out Array AppliedTextures) //Gets array of colors and textures applied to Player. Will keep (0, 0, 0) values in case SkinColor was not found. Use it with FillUpPlayerAppliedColorsFromSkin to get the full list of colors. { local Texture tex; local LinearColor lc; local MaterialInstance inst; local Array comps; local FSkinTextureInfo TextureInfo; local class PlayerClass; local Array TextureInfoTexs; local int i, j, k, n, EyesIndex, FaceIndex; local SkeletalMeshComponent comp, MainMesh; local Array UndefinedIndexes, TextureNums; AppliedColors.Length = class'Hat_Collectible_Skin'.const.SkinColorNum; AppliedTextures.Length = class'Hat_Collectible_Skin'.const.SkinColorNum; for (k = 0; k < class'Hat_Collectible_Skin'.const.SkinColorNum; k++) //Resetting all colors and cleanig up texture info. { lc.R = 0.0; lc.G = 0.0; lc.B = 0.0; lc.A = -1.0; AppliedColors[i] = lc; TextureInfo.Texture = None; TextureInfo.UVScale = 1.0; TextureInfo.Angle = 0.0; TextureInfo.DisableNormalMap = false; AppliedTextures[i] = TextureInfo; UndefinedIndexes.AddItem(k); TextureNums.AddItem(-1); } TextureInfoTexs.Length = class'Hat_MaterialExpressionDynamicLUT'.const.SupportedTextureSlots; MainMesh = GetMainMeshByPlayerClass(a); PlayerClass = GetPlayerClassByActor(a); comps = GetAllMeshComponentsByActor(a); for (i = 0; i < comps.Length; i++) { if (comps[i] == None) continue; comp = SkeletalMeshComponent(comps[i]); if (PlayerClass != None && comp == MainMesh) { EyesIndex = PlayerClass.default.MaterialIndexEyes; FaceIndex = PlayerClass.default.MaterialIndexFace; } else { EyesIndex = -1; FaceIndex = -1; } if (comp != None) GetEyesAndFaceMaterialIndexesFromSkeletalMesh(comp.SkeletalMesh, EyesIndex, FaceIndex); for (j = 0; j < comps[i].GetNumElements(); j++) { if (j == EyesIndex || j == FaceIndex) continue; inst = MaterialInstance(comps[i].GetMaterial(j)); if (inst == None || !inst.IsInMapOrTransientPackage()) continue; for (k = 0; k < class'Hat_MaterialExpressionDynamicLUT'.const.SupportedTextureSlots; k++) { if (TextureInfoTexs[k] != None) continue; if (!inst.GetTextureParameterValue(Name(class'Hat_Collectible_Skin'.static.GetTextureSlotName(k)), tex)) continue; if (tex == Texture2D'EngineMaterials.DefaultDiffuse') continue; TextureInfoTexs[k] = Texture2D(tex); } if (UndefinedIndexes.Length > 0) { for (k = 0; k < class'Hat_Collectible_Skin'.const.SkinColorNum; k++) { if (!inst.GetVectorParameterValue(Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(SkinColors(k))), lc)) continue; n = int(lc.A); if (n >= 0 && float(n) == lc.A && n < class'Hat_MaterialExpressionDynamicLUT'.const.SupportedTextureSlots) //Is A value an actual integer and can be TextureInfoTexs Array index? { AppliedTextures[k].Angle = lc.G; AppliedTextures[k].DisableNormalMap = (lc.R < 0.0); AppliedTextures[k].UVScale = Abs(lc.R); TextureNums[k] = n; } else { lc.A = -1.0; AppliedColors[k] = lc; } UndefinedIndexes.RemoveItem(k); if (UndefinedIndexes.Length < 1) break; } } } } for (k = 0; k < class'Hat_Collectible_Skin'.const.SkinColorNum; k++) { AppliedColors[k].A = -1.0; if (TextureNums[k] < 0 || TextureNums[k] >= TextureInfoTexs.Length) AppliedTextures[k].Texture = None; else AppliedTextures[k].Texture = TextureInfoTexs[TextureNums[k]]; if (AppliedTextures[k].Texture != None) { AppliedColors[k].R = 0.0; AppliedColors[k].G = 0.0; AppliedColors[k].B = 0.0; } else { AppliedColors[k].R = FClamp(AppliedColors[k].R, 0.0, 1.0); AppliedColors[k].G = FClamp(AppliedColors[k].G, 0.0, 1.0); AppliedColors[k].B = FClamp(AppliedColors[k].B, 0.0, 1.0); } } } final static function bool SetMaterialInstanceVectorValue(MaterialInstance inst, Name n, LinearColor lc) //Optimized, includes IsInMapOrTransientPackage check to not accidentally change value on all child MaterialInstances. { if (inst == None || !inst.IsInMapOrTransientPackage()) return false; inst.SetVectorParameterValue(n, lc); return true; } final static function SetMaterialVectorValueMesh(MeshComponent comp, Name n, LinearColor lc) //Calls SetMaterialInstanceVectorValue function for each MaterialInstance. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) SetMaterialInstanceVectorValue(MaterialInstance(comp.GetMaterial(i)), n, lc); } final static function bool ClearMaterialInstanceVectorValue(MaterialInstance inst, Name n) //Optimized, includes IsInMapOrTransientPackage check to not accidentally change value on all child MaterialInstances. { if (inst == None || !inst.IsInMapOrTransientPackage()) return false; inst.ClearVectorParameterValue(n); return true; } final static function ClearMaterialVectorValueMesh(MeshComponent comp, Name n) //Calls ClearMaterialInstanceVectorValue function for each MaterialInstance. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) ClearMaterialInstanceVectorValue(MaterialInstance(comp.GetMaterial(i)), n); } final static function bool SetMaterialInstanceScalarValue(MaterialInstance inst, Name n, float f) //Optimized, includes IsInMapOrTransientPackage check to not accidentally change value on all child MaterialInstances. { if (inst == None || !inst.IsInMapOrTransientPackage()) return false; inst.SetScalarParameterValue(n, f); return true; } final static function SetMaterialScalarValueMesh(MeshComponent comp, Name n, float f) //Calls SetMaterialInstanceScalarValue function for each MaterialInstance. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) SetMaterialInstanceScalarValue(MaterialInstance(comp.GetMaterial(i)), n, f); } final static function bool SetMaterialInstanceScalarCurveValue(MaterialInstance inst, Name n, InterpCurveFloat ncf) //Optimized, includes IsInMapOrTransientPackage check to not accidentally change value on all child MaterialInstances. { if (inst == None || !inst.IsInMapOrTransientPackage()) return false; inst.SetScalarCurveParameterValue(n, ncf); return true; } final static function SetMaterialScalarCurveValueMesh(MeshComponent comp, Name n, InterpCurveFloat ncf) //Calls SetMaterialInstanceScalarCurveValue function for each MaterialInstance. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) SetMaterialInstanceScalarCurveValue(MaterialInstance(comp.GetMaterial(i)), n, ncf); } final static function bool SetMaterialInstanceTextureValue(MaterialInstance inst, Name n, Texture t) //Optimized, includes IsInMapOrTransientPackage check to not accidentally change value on all child MaterialInstances. { if (inst == None || !inst.IsInMapOrTransientPackage()) return false; inst.SetTextureParameterValue(n, t); return true; } final static function SetMaterialTextureValueMesh(MeshComponent comp, Name n, Texture t) //Calls SetMaterialInstanceTextureValue function for each MaterialInstance. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) SetMaterialInstanceTextureValue(MaterialInstance(comp.GetMaterial(i)), n, t); } final static function bool ClearMaterialInstanceTextureValue(MaterialInstance inst, Name n) //Optimized, includes IsInMapOrTransientPackage check to not accidentally change value on all child MaterialInstances. { if (inst == None || !inst.IsInMapOrTransientPackage()) return false; inst.ClearTextureParameterValue(n); return true; } final static function ClearMaterialTextureValueMesh(MeshComponent comp, Name n) //Calls ClearMaterialInstanceTextureValue function for each MaterialInstance. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) ClearMaterialInstanceTextureValue(MaterialInstance(comp.GetMaterial(i)), n); } final static function bool SetMaterialInstanceTransitionEffect(MaterialInstance inst, Name n, InterpCurveFloat icf) //Optimized, includes IsInMapOrTransientPackage check to not accidentally change value on all child MaterialInstances. { local MaterialInstanceTimeVarying MITV; if (!SetMaterialInstanceScalarCurveValue(inst, n, icf)) return false; MITV = MaterialInstanceTimeVarying(inst); if (MITV != None) MITV.SetScalarStartTime(n, 0.0); return true; } final static function SetTransitionEffectMesh(MeshComponent comp, Name n, float start, float end, float time, optional float mid = -1.0, optional float midtime = -1.0) //Calls SetMaterialInstanceTransitionEffect function for each MaterialInstance. { local int i; local InterpCurveFloat icf; if (comp == None) return; if (mid >= 0.0 && midtime >= 0.0) icf = class'Hat_Math'.static.GenerateCurveFloat2(start, mid, end, midtime, time); else icf = class'Hat_Math'.static.GenerateCurveFloat(start, end, time); for (i = 0; i < comp.GetNumElements(); i++) SetMaterialInstanceTransitionEffect(MaterialInstance(comp.GetMaterial(i)), n, icf); } final static function bool ClearMaterialInstanceTextureSlots(MaterialInstance inst) //Modified to apply values to MaterialInstance. { local int i; if (inst == None) return false; for (i = 0; i < class'Hat_MaterialExpressionDynamicLUT'.const.SupportedTextureSlots; i++) ClearMaterialInstanceTextureValue(inst, Name(class'Hat_Collectible_Skin'.static.GetTextureSlotName(i))); return true; } final static function ClearTextureSlots(MeshComponent comp) //Calls ClearMaterialInstanceTextureSlots function for each MaterialInstance. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) ClearMaterialInstanceTextureSlots(MaterialInstance(comp.GetMaterial(i))); } final static function bool SetMaterialInstanceTextureSlots(MaterialInstance inst, Array TextureSlots) //Modified to apply values to MaterialInstance. { local int i; if (!ClearMaterialInstanceTextureSlots(inst)) return false; for (i = 0; i < Min(TextureSlots.Length, class'Hat_MaterialExpressionDynamicLUT'.const.SupportedTextureSlots); i++) SetMaterialInstanceTextureValue(inst, Name(class'Hat_Collectible_Skin'.static.GetTextureSlotName(i)), TextureSlots[i]); return true; } final static function SetTextureSlots(MeshComponent comp, Array TextureSlots) //Calls SetMaterialInstanceTextureSlots function for each MaterialInstance. { local int i; if (comp == None) return; ClearTextureSlots(comp); for (i = 0; i < comp.GetNumElements(); i++) SetMaterialInstanceTextureSlots(MaterialInstance(comp.GetMaterial(i)), TextureSlots); } final static function Array GenerateRandomColors() //Originally was in Jaw's Random Color Dye. { local int i; local Array LinearColors; for (i = 0; i < class'Hat_Collectible_Skin'.const.SkinColorNum; i++) LinearColors.AddItem(MakeLinearColor((float(Rand(256))/255.0)**2.2, (float(Rand(256))/255.0)**2.2, (float(Rand(256))/255.0)**2.2, -1.0)); return LinearColors; } final static function RandomizeColors(Object o) //Applies random colors to Object. Object can be either Actor (will use GetAllMeshComponentsByActor to get MeshComponents), MeshComponent or MaterialInstance. { SetObjectColors(o, GenerateRandomColors()); } final static function SetObjectColors(Object o, Array LinearColors) //Function that applies colors (and only colors) to Object. Object can be either Actor (will use GetAllMeshComponentsByActor to get MeshComponents), MeshComponent or MaterialInstance. { local int NumColors; local int i, j; local Array comps; local MaterialInstance inst; NumColors = Min(LinearColors.Length, class'Hat_Collectible_Skin'.const.SkinColorNum); if (NumColors < 1) return; if (Actor(o) != None) comps = GetAllMeshComponentsByActor(Actor(o)); else if (MeshComponent(o) != None) comps.AddItem(MeshComponent(o)); else { inst = MaterialInstance(o); if (inst == None) return; for (i = 0; i < NumColors; i++) { LinearColors[i].A = -1.0; SetMaterialInstanceVectorValue(inst, Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(SkinColors(i))), LinearColors[i]); } return; } for (j = 0; j < comps.Length; j++) { for (i = 0; i < NumColors; i++) { LinearColors[i].A = -1.0; SetMaterialVectorValueMesh(comps[j], Name(class'Hat_Collectible_Skin'.static.GetSkinColorName(SkinColors(i))), LinearColors[i]); } } } final static function ResetMaterials(MeshComponent comp, optional bool UseDefaultMaterials, optional bool KeepTransientInstances, optional bool ClearTransientInstancesParameters) { local int i; local bool IsDefaultMesh; local MaterialInstance inst; local StaticMeshComponent stmComp; local SkeletalMeshComponent skmComp; if (comp == None) return; if (UseDefaultMaterials) { skmComp = SkeletalMeshComponent(comp); stmComp = StaticMeshComponent(comp); if (skmComp != None && skmComp.SkeletalMesh == skmComp.default.SkeletalMesh) IsDefaultMesh = true; else if (stmComp != None && stmComp.StaticMesh == stmComp.default.StaticMesh) IsDefaultMesh = true; } for (i = 0; i < comp.Materials.Length; i++) { inst = MaterialInstance(comp.GetMaterial(i)); if (IsDefaultMesh && i < comp.default.Materials.Length) comp.SetMaterial(i, comp.default.Materials[i]); else comp.SetMaterial(i, None); if (KeepTransientInstances && inst != None && inst.IsInMapOrTransientPackage()) { inst.SetParent(comp.GetMaterial(i)); comp.SetMaterial(i, inst); if (ClearTransientInstancesParameters) inst.ClearParameterValues(false); } } } final static function bool IsSkinRandom(class SkinClass) //Returns true if Skin applies random colors. Currently Jaw's Random Dye and Owls costumes. { if (SkinClass == None || SkinClass == class'Hat_Collectible_Skin') return false; switch(locs(PathName(SkinClass))) { case locs("PlayableOwls.Hat_Collectible_ScienceOwlGroovyCostume"): case locs("PlayableOwls.Hat_Collectible_ExpressOwl1Costume"): case locs("PlayableOwls.Hat_Collectible_ExpressOwl2Costume"): case locs("PlayableOwls.Hat_Collectible_ScienceOwlCostume"): case locs("randye.Jaw_RandomDye"): return true; default: return false; } } final static function ConditionalInitMaterialInstancesMesh(MeshComponent comp) //Inits MaterialInstances on MeshComponent. Optimized: ConditionalInitMaterialInstance does not create unnecessary instances. { local int i; if (comp == None) return; for (i = 0; i < comp.GetNumElements(); i++) ConditionalInitMaterialInstance(comp, i); } final static function bool ConditionalInitMaterialInstance(MeshComponent comp, int i, optional out MaterialInstance CreatedInstance) //Inits one MaterialInstance on comp's Material with index i. Returns true in case it creates or finds instance. Also returns this instance as optional out variable. Force removes Invisible and OccludedMaterial instances. { local MaterialInterface mat; CreatedInstance = None; if (i < 0) return false; if (comp == None) return false; mat = comp.GetMaterial(i); if (mat == None) return false; CreatedInstance = MaterialInstance(mat); if (CreatedInstance != None && CreatedInstance.IsInMapOrTransientPackage()) //The target Material is already a transient instance. { switch(mat.GetMaterial()) //Not creating instances of Invisible and OccludedMaterial. { case Material'HatInTime_Characters.Materials.Invisible': case Material'HatInTime_Characters.Materials.OccludedMaterial': comp.SetMaterial(i, GetActualMaterial(mat)); CreatedInstance = None; return false; default: return true; } } switch(mat.GetMaterial()) { case Material'HatInTime_Characters.Materials.Invisible': case Material'HatInTime_Characters.Materials.OccludedMaterial': CreatedInstance = None; return false; default: break; } if (MaterialInstanceConstant(mat) != None) CreatedInstance = comp.CreateAndSetMaterialInstanceConstant(i); else CreatedInstance = comp.CreateAndSetMaterialInstanceTimeVarying(i); return (CreatedInstance != None); } final static function MaterialInstance ConditionalInitMaterialInstanceNoMesh(MaterialInterface mat) //Creates MaterialInstance (or returns detected one) and sets mat as its Parent. Does not set this Instance as Material to MeshComponent and does not ignore Invisible and OccludedMaterial. { local MaterialInstance inst; if (mat == None) return None; inst = MaterialInstance(mat); if (inst == None) { inst = new(None) class'MaterialInstanceTimeVarying'; if (inst != None) inst.SetParent(mat); return inst; } if (inst.IsInMapOrTransientPackage()) return inst; if (MaterialInstanceConstant(inst) != None) { inst = new(None) class'MaterialInstanceConstant'; if (inst != None) inst.SetParent(mat); return inst; } else if (MaterialInstanceTimeVarying(inst) != None) { inst = new(None) class'MaterialInstanceTimeVarying'; if (inst != None) inst.SetParent(mat); return inst; } return None; } final static function MaterialInterface GetActualMaterial(MaterialInterface mat) //Returns actual (Editor-created) MaterialInterface. { local MaterialInstance inst; inst = MaterialInstance(mat); if (inst == None) return mat; if (inst.IsInMapOrTransientPackage()) return GetActualMaterial(inst.Parent); return inst; } final static function bool IsAppliedSkinFullbodyMaterial(Actor a, class SkinClass, optional bool IgnoreSkinMesh, optional bool IgnoreMainMesh, optional out MaterialInterface BodyMat, optional out MaterialInterface EyesMat, optional out MaterialInterface FaceMat) //A very cursed function that is able to detect if Player has full-body Material applied. Has several parameters that control what Meshes it will check. { local SkeletalMeshComponent comp; local Array comps; local class PlayerClass; local int i, n, EyesIndex, FaceIndex; local MaterialInterface mat, matCompare; if (SkinClass == None || SkinClass == class'Hat_Collectible_Skin') return false; BodyMat = GetFullbodyMaterialFromSkin(SkinClass, true, EyesMat, FaceMat); if (BodyMat != None) return true; if (a == None) return false; PlayerClass = GetPlayerClassByActor(a); if (PlayerClass == None) return false; comps = GetAllMeshComponentsByActor(a); comp = GetMainMeshByPlayerClass(a); if (comp != None && comps.Find(comp) > -1) { n = comp.GetNumElements(); EyesIndex = -1; FaceIndex = -1; if (PlayerClass.default.MaterialIndexEyes > -1 && PlayerClass.default.MaterialIndexEyes < n) EyesIndex = PlayerClass.default.MaterialIndexEyes; if (PlayerClass.default.MaterialIndexFace > -1 && PlayerClass.default.MaterialIndexFace < n) FaceIndex = PlayerClass.default.MaterialIndexFace; GetEyesAndFaceMaterialIndexesFromSkeletalMesh(comp.SkeletalMesh, EyesIndex, FaceIndex); if (EyesIndex > -1) EyesMat = GetActualMaterial(comp.GetMaterial(EyesIndex)); if (FaceIndex > -1) FaceMat = GetActualMaterial(comp.GetMaterial(FaceIndex)); if (!IgnoreMainMesh) { for (i = 0; i < comp.GetNumElements(); i++) { if (i == EyesIndex || i == FaceIndex) continue; matCompare = GetActualMaterial(comp.GetMaterial(i)); if (matCompare == Material'HatInTime_Characters.Materials.Invisible') continue; if (mat == None) mat = matCompare; mat = AreMaterialsSame(mat, matCompare); if (mat == None) return false; } } comps.RemoveItem(comp); } for (i = 0; i < comps.Length; i++) { if (comps[i] == None) continue; if (IgnoreSkinMesh) { comp = SkeletalMeshComponent(comps[i]); if (comp != None && comp.SkeletalMesh != None) { if (comp.SkeletalMesh == SkinClass.default.SkinBodyMesh) continue; if (comp.SkeletalMesh == SkinClass.default.SkinBodyMeshBowKid) continue; if (comp.SkeletalMesh == SkinClass.default.SkinLegsMesh) continue; if (comp.SkeletalMesh == SkinClass.default.SkinLegsMeshBowKid) continue; } } if (!IsMeshFullbodyMaterial(comps[i], matCompare)) return false; if (mat == None) mat = matCompare; mat = AreMaterialsSame(mat, matCompare); if (mat == None) return false; } BodyMat = mat; return true; } final static function MaterialInterface AreMaterialsSame(MaterialInterface FirstMat, MaterialInterface SecondMat) //If true, returns the resulting Material. Shadow Puppet's Materials are considered same. { if (FirstMat == None) return None; if (SecondMat == None) return None; FirstMat = GetActualMaterial(FirstMat); SecondMat = GetActualMaterial(SecondMat); switch(FirstMat) { case Material'HatInTime_Costumes2.Materials.shadow_skin_mat': case Material'HatInTime_Costumes2.Materials.shadow_body_mat': case Material'HatInTime_Costumes2.Materials.shadow_hair_mat': FirstMat = Material'HatInTime_Costumes2.Materials.shadow_skin_mat'; break; default: break; } switch(SecondMat) { case Material'HatInTime_Costumes2.Materials.shadow_skin_mat': case Material'HatInTime_Costumes2.Materials.shadow_body_mat': case Material'HatInTime_Costumes2.Materials.shadow_hair_mat': SecondMat = Material'HatInTime_Costumes2.Materials.shadow_skin_mat'; break; default: break; } if (FirstMat != SecondMat) return None; return FirstMat; } final static function bool IsMeshFullbodyMaterial(MeshComponent comp, optional out MaterialInterface ConsecutiveMaterial, optional out int ConsecutiveAmount) //Checks if MeshComponent is using same Materials. Does not necessarily mean it's full-body Material, so it also returns ConsecutiveAmount which is the number of how many of the same Material are applied to mesh. Invisible Material is completely ignored. { local int i, n; local MaterialInterface mat; ConsecutiveAmount = 0; ConsecutiveMaterial = None; if (comp == None) return false; n = comp.GetNumElements(); if (n < 1) return false; for (i = 0; i < n; i++) { mat = GetActualMaterial(comp.GetMaterial(i)); if (mat == None || mat == Material'HatInTime_Characters.Materials.Invisible') continue; if (ConsecutiveMaterial == None) ConsecutiveMaterial = mat; else { ConsecutiveMaterial = AreMaterialsSame(ConsecutiveMaterial, mat); if (ConsecutiveMaterial == None) return false; } ConsecutiveAmount++; } return true; } final static function ClearFullbodyMaterial(Actor a, optional bool UseDefaultMaterials, optional bool ClearTransientInstancesParameters) //Restores default Materials and default Expression Component to Player. DOES NOT RESET EXPRESSION COMPONENT! ONLY ATTACHES THE ONE THAT WAS BEFORE! { local int i; local Array comps; if (a == None) return; comps = GetAllMeshComponentsByActor(a); for (i = 0; i < comps.Length; i++) ResetMaterials(comps[i], UseDefaultMaterials, true, ClearTransientInstancesParameters); } final static function SetFullbodyMaterialFixed(Actor a, class SkinClass, optional MaterialInterface FullbodyMat, optional MaterialInterface EyesMat, optional MaterialInterface FaceMat, optional bool IgnoreSkinMesh, optional Array IgnoreComponents) //Sets full-body Material to Player. Have some fixes. Can also ignore specific MeshComponents if you put them in IgnoreComponents. { local MaterialInterface mat; local SkeletalMeshComponent comp; local Array comps; local class PlayerClass; local int i, j, EyesIndex, FaceIndex; if (a == None || FullbodyMat == None || FullbodyMat.GetMaterial() == Material'HatInTime_Characters.Materials.Invisible') return; if (FaceMat == None) FaceMat = Material'HatInTime_Characters.Materials.Invisible'; if (EyesMat == None) EyesMat = Material'HatInTime_Characters.Materials.Invisible'; PlayerClass = GetPlayerClassByActor(a); if (PlayerClass == None) return; comps = GetAllMeshComponentsByActor(a); for (i = 0; i < IgnoreComponents.Length; i++) { if (IgnoreComponents[i] != None) comps.RemoveItem(IgnoreComponents[i]); } comp = GetMainMeshByPlayerClass(a); if (comp != None && comps.Find(comp) > -1) //Only applying eyes and face Materials if main mesh is found in comps. { PlayerClass = GetPlayerClassByActor(a); if (PlayerClass != None) { EyesIndex = PlayerClass.default.MaterialIndexEyes; FaceIndex = PlayerClass.default.MaterialIndexFace; } else { EyesIndex = -1; FaceIndex = -1; } GetEyesAndFaceMaterialIndexesFromSkeletalMesh(comp.SkeletalMesh, EyesIndex, FaceIndex); for (j = 0; j < comp.GetNumElements(); j++) { mat = comp.GetMaterial(j); if (j == EyesIndex) SetMaterialParentToInstance(comp, j, EyesMat); else if (j == FaceIndex) SetMaterialParentToInstance(comp, j, FaceMat); else if (mat != None && mat.GetMaterial() != Material'HatInTime_Characters.Materials.Invisible') SetMaterialParentToInstance(comp, j, FullbodyMat); } ConditionalInitMaterialInstancesMesh(comp); comps.RemoveItem(comp); } for (i = 0; i < comps.Length; i++) { EyesIndex = -1; FaceIndex = -1; comp = SkeletalMeshComponent(comps[i]); if (comp != None) { if (IgnoreSkinMesh && comp.SkeletalMesh != None) { if (comp.SkeletalMesh == SkinClass.default.SkinBodyMesh) continue; if (comp.SkeletalMesh == SkinClass.default.SkinLegsMesh) continue; if (comp.SkeletalMesh == SkinClass.default.SkinBodyMeshBowKid) continue; if (comp.SkeletalMesh == SkinClass.default.SkinLegsMeshBowKid) continue; } GetEyesAndFaceMaterialIndexesFromSkeletalMesh(comp.SkeletalMesh, EyesIndex, FaceIndex); } for (j = 0; j < comps[i].GetNumElements(); j++) { mat = comps[i].GetMaterial(j); if (j == EyesIndex) SetMaterialParentToInstance(comps[i], j, EyesMat); else if (j == FaceIndex) SetMaterialParentToInstance(comps[i], j, FaceMat); else if (mat != None && mat.GetMaterial() != Material'HatInTime_Characters.Materials.Invisible') SetMaterialParentToInstance(comps[i], j, FullbodyMat); } ConditionalInitMaterialInstancesMesh(comps[i]); } } final static function SetFullbodyMaterialMesh(MeshComponent comp, MaterialInterface FullbodyMat) //Sets full-body Material to MeshComponent ignoring Invisible Materials. { local int i; local MaterialInterface mat; if (comp == None || FullbodyMat == None || FullbodyMat.GetMaterial() == Material'HatInTime_Characters.Materials.Invisible') return; for (i = 0; i < comp.GetNumElements(); i++) { mat = comp.GetMaterial(i); if (mat == None || mat.GetMaterial() != Material'HatInTime_Characters.Materials.Invisible') SetMaterialParentToInstance(comp, i, FullbodyMat); else comp.SetMaterial(i, GetActualMaterial(mat)); } } final static function bool GetEyesAndFaceMaterialIndexesFromSkeletalMesh(SkeletalMesh sm, out int EyesIndex, out int FaceIndex) //A function that returns eyes and face Material indexes for vanilla SkeletalMeshes. { switch(sm) { case SkeletalMesh'HatInTime_Costumes2.models.hk64_head': case SkeletalMesh'HatInTime_Costumes2.models.bow_kid_64_head': case SkeletalMesh'HatInTime_Costumes2.hk64_single': case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bow_kid_64_all': EyesIndex = -1; FaceIndex = -1; return true; case SkeletalMesh'HatInTime_Characters_HatKid.models.HatKidHead': case SkeletalMesh'HatInTime_WireframeSkin.models.Wireframe_HatKid_Head': case SkeletalMesh'Vanessa_Tag_Cosmetics.Punk.CapEX_HKHead': case SkeletalMesh'Vanessa_Tag_Cosmetics.hkHEAD': EyesIndex = 4; FaceIndex = 0; return true; case SkeletalMesh'HatInTime_Characters_Coop.models.bowkid_head_skm': case SkeletalMesh'HatInTime_WireframeSkin.models.Wireframe_BowKid_Head': case SkeletalMesh'Vanessa_Tag_Cosmetics.Punk.CapEX_BKHead': case SkeletalMesh'HatInTime_Characters_MuGirl.models.MuGirl': EyesIndex = 0; FaceIndex = 1; return true; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.OneMeshHatKid': EyesIndex = 6; FaceIndex = 5; return true; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bowkid_all': EyesIndex = 1; FaceIndex = 2; return true; case SkeletalMesh'HatinTime_Characters_CoPartner.models.CoPartner': EyesIndex = 4; FaceIndex = 3; return true; default: return false; } } final static function GetExpressionMaterialsFromActor(Actor a, optional out MaterialInterface EyesMat, optional out MaterialInterface FaceMat, optional out Hat_ExpressionComponent e) //This function was specially written for Playable Owls, however you might find it useful for your mods too. Returns eyes and face Materials and Expression Component from Player. In case Player does not have Expression Component, it will check Main Mesh for pre-defined ones (Hat Kid's head, Bow Kid's head, OP meshes, etc.) and for base-game expression Materials. Does not use MaterialIndexEyes and MaterialIndexFace. { local SkeletalMeshComponent comp; local MaterialInterface mat; local int i, EyesIndex, FaceIndex; EyesMat = None; FaceMat = None; e = None; if (a == None) return; e = GetExpressionComponentByActor(a); if (e != None) { EyesMat = GetActualMaterial(e.Eyes.Material); FaceMat = GetActualMaterial(e.Face.Material); return; } comp = GetMainMeshByPlayerClass(a); if (comp == None) return; if (GetEyesAndFaceMaterialIndexesFromSkeletalMesh(comp.SkeletalMesh, EyesIndex, FaceIndex)) { if (EyesIndex > -1) EyesMat = GetActualMaterial(comp.GetMaterial(EyesIndex)); if (FaceIndex > -1) FaceMat = GetActualMaterial(comp.GetMaterial(FaceIndex)); return; } for (i = 0; i < comp.GetNumElements(); i++) { mat = comp.GetMaterial(i); if (mat == None) continue; switch(mat.GetMaterial()) { case Material'HatInTime_CharacterShading.Expressions.Eyes_Master': case Material'HatInTime_Global.Materials.Facial_Eyes_Master': case Material'HatInTime_WireframeSkin.Materials.Wireframe_Master_Eyes': if (EyesMat == None) EyesMat = GetActualMaterial(mat); break; case Material'HatInTime_CharacterShading.Expressions.Expression_Master': case Material'HatInTime_Global.Materials.Facial_Expression_Master': case Material'HatInTime_Global.Materials.Facial_Expression_Master_Lit': if (FaceMat == None) FaceMat = GetActualMaterial(mat); break; default: break; } if (EyesMat != None && FaceMat != None) return; } } final static function Hat_ExpressionComponent GetExpressionComponentByActor(Actor a) //Gets Expression Component attached to Player. Also works on Online Players. { local Hat_ExpressionComponent exp; local Hat_Player ply; local Hat_NPC_Player npc; if (a == None) return None; ply = Hat_Player(a); if (ply != None) return ply.ExpressionComponent; npc = Hat_NPC_Player(a); if (npc != None) return npc.Expression; foreach a.ComponentList(class'Hat_ExpressionComponent', exp) { if (exp != None) return exp; } return None; } final static function SetExpressionComponentByActor(Actor a, class ExpressionClass) //Attaches new Expression Component to Player. Works on Online Players. { local int i; local Hat_Player ply; local Hat_NPC_Player npc; local Hat_GhostPartyPlayer gpp; local Hat_ExpressionComponent exp; local Array RemoveList; ply = Hat_Player(a); if (ply != None) { if (ply.ExpressionComponent != None) { ply.ExpressionComponent.DetachFromAny(); ply.ExpressionComponent = None; } if (ExpressionClass == None || ClassIsDeprecated(ExpressionClass)) return; exp = new ExpressionClass; if (exp == None) return; ply.ExpressionComponent = exp; ply.AttachComponent(exp); exp.Init(ply); return; } npc = Hat_NPC_Player(a); if (npc != None) { if (npc.Expression != None) { npc.Expression.DetachFromAny(); npc.Expression = None; } if (ExpressionClass == None || ClassIsDeprecated(ExpressionClass)) return; exp = new ExpressionClass; if (exp == None) return; npc.Expression = exp; npc.AttachComponent(exp); exp.Init(npc); return; } gpp = Hat_GhostPartyPlayer(a); if (gpp == None) return; foreach gpp.AllOwnedComponents(class'Hat_ExpressionComponent', exp) { if (exp != None) RemoveList.AddItem(exp); } for (i = 0; i < RemoveList.Length; i++) { if (RemoveList[i] != None) RemoveList[i].DetachFromAny(); } RemoveList.Length = 0; if (ExpressionClass == None || ClassIsDeprecated(ExpressionClass)) return; exp = new ExpressionClass; if (exp == None) return; gpp.AttachComponent(exp); exp.Init(gpp); } final static function ResetExpressionComponentByActor(Actor a) //Resets Expression Component for Actor. Has support for Altiami's Online Party Expressions (for single mesh Players). { local class PlayerClass; local class ExpressionClass; local SkeletalMeshComponent MainMesh; if (a == None) return; MainMesh = GetMainMeshByPlayerClass(a); if (MainMesh != None) { switch(MainMesh.SkeletalMesh) { case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.OneMeshHatKid': ExpressionClass = class(class'Hat_ClassHelper'.static.GetScriptClass("ExpressiveGhosts.Ysm_ExpressionComponent_HatKid_Ghost_OneMesh")); if (ExpressionClass == None) ExpressionClass = class'Hat_ExpressionComponent'; break; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bowkid_all': ExpressionClass = class(class'Hat_ClassHelper'.static.GetScriptClass("ExpressiveGhosts.Ysm_ExpressionComponent_BowKid_Ghost_OneMesh")); if (ExpressionClass == None) ExpressionClass = class'Hat_ExpressionComponent'; break; case SkeletalMesh'HatInTime_Costumes2.hk64_single': case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bow_kid_64_all': case SkeletalMesh'HatInTime_Costumes2.models.hk64_head': case SkeletalMesh'HatInTime_Costumes2.models.bow_kid_64_head': ExpressionClass = class'Hat_ExpressionComponent'; break; default: break; } } if (ExpressionClass == None) { PlayerClass = GetPlayerClassByActor(a); if (PlayerClass != None && PlayerClass.default.ExpressionComponent != None) ExpressionClass = PlayerClass.default.ExpressionComponent.Class; } if (ExpressionClass == class'Hat_ExpressionComponent' || ClassIsDeprecated(ExpressionClass)) ExpressionClass = None; SetExpressionComponentByActor(a, ExpressionClass); } final static function Array GetAllMeshComponentsByActor(Actor a) //Returns GetMyMaterialMeshComponents array for Player. { local Hat_Player ply; local Hat_NPC_Player npc; local Hat_GhostPartyPlayer gpp; local Array comps; ply = Hat_Player(a); if (ply != None) { comps = ply.GetMyMaterialMeshComponents(); return comps; } npc = Hat_NPC_Player(a); if (npc != None) { comps = npc.GetMyMaterialMeshComponents(); return comps; } gpp = Hat_GhostPartyPlayer(a); if (gpp != None) comps = gpp.GetMyMaterialMeshComponents(); return comps; } final static function class GetCurrentSkin(Actor a) //Returns current SkinClass used by Player. { local Hat_Loadout l; local Hat_GhostPartyPlayer gpp; if (a == None) return None; l = GetLoadout(a); if (l != None) return GetSkinFromLoadout(l); gpp = Hat_GhostPartyPlayer(a); if (gpp == None) return None; return (gpp.CurrentSkin == None ? class'Hat_Collectible_Skin' : gpp.CurrentSkin); } final static function class GetSkinFromLoadout(Hat_Loadout l) //Reads SkinClass from Hat_Loadout. { local class SkinClass; if (l == None) return None; if (l.MyLoadout.Skin == None) return class'Hat_Collectible_Skin'; SkinClass = class(l.MyLoadout.Skin.BackpackClass); return (SkinClass == None ? class'Hat_Collectible_Skin' : SkinClass); } final static function Hat_Loadout GetLoadout(Actor a) //Returns loadout of Player. Input can be Pawn, Hat_PlayerController or Hat_NPC_Player. { local Hat_PlayerController hpc; local Hat_NPC_Player npc; npc = Hat_NPC_Player(a); if (npc != None) return npc.GetLoadout(); if (Pawn(a) != None) hpc = Hat_PlayerController(GetPawnPlayerController(Pawn(a))); else hpc = Hat_PlayerController(PlayerController(a)); if (hpc == None) return None; return hpc.GetLoadout(); } final static function PlayerController GetPawnPlayerController(Pawn p, optional out Array IteratedPawns) //Copied from Shara_SteamID_Tools. Returns PlayerController for Pawn. Will check Pawn itself and DrivenVehicle for PlayerController. IteratedPawns is used to prevent infinite loop when iterating Vehicle's Driver and Pawn's DrivenVehicle. { local PlayerController pc; local Vehicle v; if (p == None) return None; pc = PlayerController(p.Controller); if (pc != None) return pc; if (IteratedPawns.Find(p) < 0) IteratedPawns.AddItem(p); else return None; v = Vehicle(p); if (v != None) { pc = GetPawnPlayerController(v.Driver, IteratedPawns); if (pc != None) return pc; } return GetPawnPlayerController(p.DrivenVehicle, IteratedPawns); } final static function class GetPlayerClassByActor(Actor a) //Returns Player Class. Correctly works for NPCs and Online Players. { local Hat_Player ply; local Hat_NPC_Player npc; local Hat_GhostPartyPlayer gpp; ply = Hat_Player(a); if (ply != None) return ply.Class; gpp = Hat_GhostPartyPlayer(a); if (gpp != None) return (gpp.PlayerVisualClass != None ? gpp.PlayerVisualClass : class'Hat_Player_HatKid'); npc = Hat_NPC_Player(a); if (npc != None) return class(npc.GetPlayerPawnClass()); return None; } final static function SkeletalMeshComponent GetMainMeshByPlayerClass(Actor a) //Returns Player's Mesh (head). { local Hat_Player ply; local Hat_NPC_Player npc; local Hat_GhostPartyPlayer gpp; ply = Hat_Player(a); if (ply != None) return ply.Mesh; npc = Hat_NPC_Player(a); if (npc != None) return npc.SkeletalMeshComponent; gpp = Hat_GhostPartyPlayer(a); if (gpp != None) return gpp.SkeletalMeshComponent; return None; } final static function Hat_CosmeticItem GetInventoryCosmeticItemByActor(Actor a, class CosmeticItemClass, optional bool AllowSubclasses) //Returns cosmetic item from Player's InvManager or NPC's MyInventory array. Only works on Hat_Player and Hat_NPC_Player. { local Hat_CosmeticItem cos; local Hat_Player ply; local Hat_NPC_Player npc; local int i; ply = Hat_Player(a); if (ply != None) { if (ply.InvManager == None) return None; foreach ply.InvManager.InventoryActors(class'Hat_CosmeticItem', cos) { if (cos == None) continue; if (cos.Class == CosmeticItemClass) return cos; if (AllowSubclasses && ClassIsChildOf(cos.Class, CosmeticItemClass)) return cos; } return None; } npc = Hat_NPC_Player(a); if (npc == None) return None; for (i = 0; i < npc.MyInventory.Length; i++) { cos = Hat_CosmeticItem(npc.MyInventory[i]); if (cos == None) continue; if (cos.Class == CosmeticItemClass) return cos; if (AllowSubclasses && ClassIsChildOf(cos.Class, CosmeticItemClass)) return cos; } return None; } final static function bool AreSkinColorsDefault(class SkinClass) //Returns true if SkinClass has no colors in defaultproperties. { local int i; if (SkinClass == None || SkinClass == class'Hat_Collectible_Skin') return true; for (i = 0; i < 11; i++) { if (SkinClass.default.SkinColor[i] != class'Hat_Collectible_Skin'.default.SkinColor[i]) return false; } return true; } final static function bool AreSkinTexturesDefault(class SkinClass) //Returns true if SkinClass has no textures in defaultproperties. { local int i; if (SkinClass == None || SkinClass == class'Hat_Collectible_Skin') return true; for (i = 0; i < 11; i++) { if (SkinClass.default.SkinTextureInfo[i] != class'Hat_Collectible_Skin'.default.SkinTextureInfo[i]) return false; } return true; } final static function bool SetMaterialParentToInstance(MeshComponent comp, int i, MaterialInterface NewParent) //Sets MaterialInstance Parent or SetMaterial+InitMaterialInstance. Returns true if transient MaterialInstance was created. { local MaterialInstance inst; local MaterialInterface mat; if (i < 0 || NewParent == None || comp == None) return false; mat = GetActualMaterial(NewParent); if (mat == None) return false; switch(mat.GetMaterial()) //Force removing transient MaterialInstance of Invisible and OccludedMaterial. { case Material'HatInTime_Characters.Materials.Invisible': case Material'HatInTime_Characters.Materials.OccludedMaterial': if (comp.GetMaterial(i) != mat) comp.SetMaterial(i, mat); return false; default: break; } if (mat != NewParent) //Applying transient MaterialInstance. Need to copy its parameters. Unfortunately, this will require creating one new cloned MaterialInstance. { inst = MaterialInstance(NewParent); inst = new(comp) inst.Class(inst); if (inst != None) //Successfully cloned target MaterialInstance. It already has all overriding parameters, so we set Editor MaterialInterface as its Parent and then call SetMaterial. { inst.SetParent(mat); comp.SetMaterial(i, inst); return true; } } inst = MaterialInstance(comp.GetMaterial(i)); if (inst != None && inst.IsInMapOrTransientPackage()) { if (inst.Parent != mat) inst.SetParent(mat); return false; } if (comp.GetMaterial(i) != mat) comp.SetMaterial(i, mat); if (MaterialInstanceConstant(mat) != None) comp.CreateAndSetMaterialInstanceConstant(i); else comp.CreateAndSetMaterialInstanceTimeVarying(i); return true; } final static function InitAllMaterials(Actor a) //Inits all Material for all MeshComponents of any Actor (Hat_Player, Hat_NPC_Player, Hat_GhostPartyPlayer). CAN BE DESTRUCTIVE TO EXPRESSION COMPONENTS!!! { local int i; local Array comps; comps = GetAllMeshComponentsByActor(a); for (i = 0; i < comps.Length; i++) ConditionalInitMaterialInstancesMesh(comps[i]); } final static function bool IsMeshOcclusion(MeshComponent comp) //Checks if MeshComponent is Occlusion (uses only OccludedMaterial and Invisible Material). { local int i; local MaterialInterface mat; local bool HasAnyMaterial; if (comp == None) return false; for (i = 0; i < comp.GetNumElements(); i++) { mat = comp.GetMaterial(i); if (mat == None) continue; mat = mat.GetMaterial(); if (mat == None) continue; HasAnyMaterial = true; if (mat != Material'HatInTime_Characters.Materials.OccludedMaterial' && mat != Material'HatInTime_Characters.Materials.Invisible') return false; } if (HasAnyMaterial) return true; return false; } final static function bool IsMeshOcclusionClamped(MeshComponent comp, int MinIndex, int MaxIndex) //Checks if MeshComponent is Occlusion but for limited index range. { local int i; local MaterialInterface mat; local bool HasAnyMaterial; if (MaxIndex < 0) return false; if (comp == None) return false; if (MinIndex < 0) MinIndex = 0; for (i = MinIndex; i < Min(comp.GetNumElements(), MaxIndex+1); i++) { mat = comp.GetMaterial(i); if (mat == None) continue; mat = mat.GetMaterial(); if (mat == None) continue; HasAnyMaterial = true; if (mat != Material'HatInTime_Characters.Materials.OccludedMaterial' && mat != Material'HatInTime_Characters.Materials.Invisible') return false; } if (HasAnyMaterial) return true; return false; } final static function SetPlayerOcclusion(Hat_Player ply, optional MaterialInterface OverrideOcclusionMat) { local Hat_CosmeticItem cos; if (ply == None) return; if (ply.OccludedMeshes.Length > 0) SetOcclusionMesh(ply.Mesh, ply.OccludedMeshes[0]); if (ply.InvManager == None) return; foreach ply.InvManager.InventoryActors(class'Hat_CosmeticItem', cos) SetCosmeticsOcclusion(cos, OverrideOcclusionMat); } final static function SetNPCOcclusion(Hat_NPC_Player npc, optional MaterialInterface OverrideOcclusionMat) //NPC Players actually have occlusion cosmetic item meshes, but they're hidden. { local int i; if (npc == None) return; for (i = 0; i < npc.MyInventory.Length; i++) SetCosmeticsOcclusion(Hat_CosmeticItem(npc.MyInventory[i]), OverrideOcclusionMat); } final static function SetCosmeticsOcclusion(Hat_CosmeticItem cos, optional MaterialInterface OverrideOcclusionMat) //Makes occlusion meshes look like actual meshes. { local MeshComponent comp; if (cos == None || cos.OccludedMesh == None) return; comp = MeshComponent(cos.DroppedPickupMesh); if (comp == None || comp.Class != cos.OccludedMesh.Class) return; SetOcclusionMesh(comp, cos.OccludedMesh, OverrideOcclusionMat); } final static function SetOcclusionMesh(MeshComponent OriginalMesh, MeshComponent OcclusionMesh, optional MaterialInterface OverrideOcclusionMat) //Sets occlusion mesh parameters based on original mesh. { local int i; local MaterialInterface mat; if (!CopySpecialMeshProperties(OriginalMesh, OcclusionMesh)) return; if (OverrideOcclusionMat == None || OverrideOcclusionMat.GetMaterial() != Material'HatInTime_Characters.Materials.OccludedMaterial') OverrideOcclusionMat = Material'HatInTime_Characters.Materials.OccludedMaterial'; for (i = 0; i < OriginalMesh.GetNumElements(); i++) { mat = OriginalMesh.GetMaterial(i); if (mat != None && mat.GetMaterial() == Material'HatInTime_Characters.Materials.Invisible') OcclusionMesh.SetMaterial(i, GetActualMaterial(mat)); else OcclusionMesh.SetMaterial(i, OverrideOcclusionMat); } CopySkeletalMeshSections(SkeletalMeshComponent(OriginalMesh), SkeletalMeshComponent(OcclusionMesh)); } final static function string GetFullnameOfObject(Object a) //Small fix for PathName function. Returns None if Object is None or has no PathName. { local string s; if (a != None) { s = PathName(a); if (s == "" || s == ".") return "None"; return s; } return "None"; } final static function bool CopySpecialMeshProperties(MeshComponent CopyFrom, MeshComponent CopyTo) { local SkeletalMeshComponent skmFrom, skmTo; local StaticMeshComponent stmFrom, stmTo; local bool b; local int i; if (CopyFrom == None || CopyTo == None) return false; skmFrom = SkeletalMeshComponent(CopyFrom); if (skmFrom != None) { skmTo = SkeletalMeshComponent(CopyTo); if (skmTo == None) return false; if (skmTo.SkeletalMesh != skmFrom.SkeletalMesh) skmTo.SetSkeletalMesh(skmFrom.SkeletalMesh); if (skmTo.bUseBoundsFromParentAnimComponent) { if (skmTo.PhysicsAsset != None) { skmTo.SetPhysicsAsset(None); if (skmTo.bHasPhysicsAssetInstance) skmTo.SetHasPhysicsAssetInstance(false); } } else if (skmTo.PhysicsAsset != skmFrom.PhysicsAsset) { skmTo.SetPhysicsAsset(skmFrom.PhysicsAsset); if (skmFrom.bHasPhysicsAssetInstance != skmTo.bHasPhysicsAssetInstance) skmTo.SetHasPhysicsAssetInstance(skmFrom.bHasPhysicsAssetInstance); } b = false; for (i = 0; i < skmFrom.MorphSets.Length; i++) //Adding extra MorphSets from skmFrom. { if (skmFrom.MorphSets[i] == None) continue; if (skmTo.MorphSets.Find(skmFrom.MorphSets[i]) < 0) //Only adding MorphSets not already presented on skmTo. { if (i >= skmTo.MorphSets.Length) { skmTo.MorphSets.AddItem(skmFrom.MorphSets[i]); b = true; } else { skmTo.MorphSets.InsertItem(i, skmFrom.MorphSets[i]); b = true; } } } if (b) skmTo.InitMorphTargets(); b = false; for (i = 0; i < skmFrom.AnimSets.Length; i++) //Adding extra AnimSets from skmFrom. { if (skmFrom.AnimSets[i] == None) continue; if (skmTo.AnimSets.Find(skmFrom.AnimSets[i]) < 0) //Only adding AnimSets not already presented on skmTo. { if (i >= skmTo.AnimSets.Length) { skmTo.AnimSets.AddItem(skmFrom.AnimSets[i]); b = true; } else { skmTo.AnimSets.InsertItem(i, skmFrom.AnimSets[i]); b = true; } } } if (b) skmTo.UpdateAnimations(); return true; } stmFrom = StaticMeshComponent(CopyFrom); if (stmFrom == None) return false; stmTo = StaticMeshComponent(CopyTo); if (stmTo == None) return false; if (stmFrom.StaticMesh != stmTo.StaticMesh) stmTo.SetStaticMesh(stmFrom.StaticMesh, true); return true; } final static function CopySkeletalMeshSections(SkeletalMeshComponent skmFrom, SkeletalMeshComponent skmTo) { local int i, j; local Array FromBoneNames, ToBoneNames; if (skmTo == None) return; skmFrom = GetBaseSkeletalMeshComponent(skmFrom); if (skmFrom != None && GetBaseSkeletalMeshComponent(skmTo) == skmTo) //Taking bone names only from Parentless Mesh and hiding bones only if target Mesh is also Parentless. { skmFrom.GetBoneNames(FromBoneNames); skmTo.GetBoneNames(ToBoneNames); for (i = 0; i < FromBoneNames.Length; i++) { j = skmFrom.MatchRefBone(FromBoneNames[i]); if (j > -1 && skmFrom.IsBoneHidden(j)) //The bone should in exist in source SkeletalMeshComponent in order to be hidden in target SkeletalMeshComponent. skmTo.HideBoneByName(FromBoneNames[i], 2); else skmTo.UnHideBoneByName(FromBoneNames[i]); ToBoneNames.RemoveItem(FromBoneNames[i]); } for (i = 0; i < ToBoneNames.Length; i++) //Remaining bones of target SkeletalMeshComponent should be unhidden. skmTo.UnHideBoneByName(ToBoneNames[i]); } for (i = 0; i < skmFrom.LODInfo.Length; i++) { for (j = 0; j < skmFrom.LODInfo[i].HiddenMaterials.Length; j++) skmTo.ShowMaterialSection(j, !skmFrom.LODInfo[i].HiddenMaterials[j], i); } } final static function SkeletalMeshComponent GetBaseSkeletalMeshComponent(SkeletalMeshComponent comp, optional out Array IteratedMeshes) //Returns SkeletalMeshComponent that has no ParentAnimComponent (e.g. the one that actually uses its own Animations and replicates them to Meshes that use it as ParentAnimComponent). { if (comp == None) return None; if (comp.ParentAnimComponent == None) return comp; if (IteratedMeshes.Find(comp) < 0) IteratedMeshes.AddItem(comp); else return None; return GetBaseSkeletalMeshComponent(comp.ParentAnimComponent, IteratedMeshes); } final static function CopyMeshToAnother(MeshComponent CopyFrom, MeshComponent CopyTo) //Function that copies visual parameters of one mesh to another. Used in Owls' ConvertNPCPlayer to make NPC always look absolutely the same as Player. { local int i; if (!CopySpecialMeshProperties(CopyFrom, CopyTo)) return; for (i = 0; i < CopyFrom.Materials.Length; i++) SetMaterialParentToInstance(CopyTo, i, CopyFrom.GetMaterial(i)); CopySkeletalMeshSections(SkeletalMeshComponent(CopyFrom), SkeletalMeshComponent(CopyTo)); } final static function Hat_Ability_Trigger GetPlayerHat(Actor a) //Returns Hat_Ability_Trigger (Hat) Actor of Player. { local int i; local Hat_Ability_Trigger HatActor; local Hat_Player ply; local Hat_InventoryManager him; local Hat_NPC_Player npc; ply = Hat_Player(a); if (ply != None) { him = Hat_InventoryManager(ply.InvManager); if (him != None) return Hat_Ability_Trigger(him.Hat); return None; } npc = Hat_NPC_Player(a); if (npc != None) { for (i = 0; i < npc.MyInventory.Length; i++) { HatActor = Hat_Ability_Trigger(npc.MyInventory[i]); if (HatActor != None) return HatActor; } } return None; } final static function MaterialInterface GetFullbodyMaterialFromSkin(class SkinClass, optional bool ReturnExpressionMats, optional out MaterialInterface EyesMat, optional out MaterialInterface FaceMat) //Returns EyesMat and FaceMat that are applied to either all Players or ones that are not Hat Kid or Bow Kid. { local string SkinPath; if (ReturnExpressionMats) { EyesMat = Material'HatInTime_Characters.Materials.Invisible'; FaceMat = Material'HatInTime_Characters.Materials.Invisible'; } if (SkinClass == None || SkinClass == class'Hat_Collectible_Skin') return None; SkinPath = PathName(SkinClass); switch(locs(SkinPath)) { case locs("HEROSpringPuppet.HEROSpringPup_Skin"): if (ReturnExpressionMats) EyesMat = GetMaterialInterfaceStatic("HEROSpringPuppet_Content.springpupeyes_Mat", class'MaterialInstanceConstant'); return GetMaterialInterfaceStatic("HEROSpringPuppet_Content.springpupbody_Mat", class'Material'); case locs("CosmicKid.Scooter_Collectible_Skin_BodyMaterial_CosmicClone"): if (ReturnExpressionMats) EyesMat = GetMaterialInterfaceStatic("CosmicPaints.Materials.cosmic_eyes", class'MaterialInstanceConstant'); return GetMaterialInterfaceStatic("CosmicPaints.Materials.cosmic", class'Material'); case locs("CosmicKid.Scooter_Collectible_Skin_BodyMaterial_PCosmicClone"): if (ReturnExpressionMats) EyesMat = GetMaterialInterfaceStatic("CosmicPaints.Materials.cosmic_eyes", class'MaterialInstanceConstant'); return GetMaterialInterfaceStatic("CosmicPaints.Materials.cosmic_3dl", class'Material'); case locs("CosmicKid.Hat_Collectible_Skin_BodyMaterial_Cosmic"): if (ReturnExpressionMats) { EyesMat = MaterialInstanceConstant'HatInTime_MaterialSkinsArgle.Eyes_Space'; FaceMat = MaterialInstanceConstant'HatInTime_MaterialSkinsArgle.Expression_Space'; } return GetMaterialInterfaceStatic("CosmicPaints.Materials.CosmicHatKid", class'Material'); case locs("LittleInventor.Star_Collectible_Skin_BodyMaterial_STCopper"): if (ReturnExpressionMats) { EyesMat = GetMaterialInterfaceStatic("ST_Metalhat.Materials.ST_RedCopper_eyes", class'MaterialInstanceConstant'); FaceMat = GetMaterialInterfaceStatic("ST_Metalhat.Materials.Robot_FaceMat", class'MaterialInstanceConstant'); } return GetMaterialInterfaceStatic("ST_Metalhat.Materials.ST_DirtyCopper_Body_Base", class'Material'); case locs("Habi_QueensNightmare.Habi_Collectible_Skin_CorruptedVanessa"): if (ReturnExpressionMats) { EyesMat = GetMaterialInterfaceStatic("Habi_VanessaDye_content.Eyes_VanessaDye", class'MaterialInstanceConstant'); FaceMat = GetMaterialInterfaceStatic("Habi_VanessaDye_content.Expression_VanessaDye", class'MaterialInstanceConstant'); } return GetMaterialInterfaceStatic("Habi_VanessaDye_content.Materials.HabiShadowVanessa", class'Material'); case locs("vanessapuppet.Vanessa_Puppet_Xo"): if (ReturnExpressionMats) EyesMat = GetMaterialInterfaceStatic("vanessapuppetpackage.VPeyes_xo_IAMLOOKING", class'MaterialInstanceConstant'); return GetMaterialInterfaceStatic("vanessapuppetpackage.VPmaterialisucksorry", class'Material'); case locs("Costume_SubconSpirit.Don_Skin_SubconSpirit"): return GetMaterialInterfaceStatic("Don_SubconSpirit_V2_Content.Materials.Don_SubconSpirit_Base_Mat", class'Material'); case locs("Arg_MaterialSkins.Arg_Collectible_Skin_Rainbow"): return GetMaterialInterfaceStatic("Arg_MaterialSkins_Content.Rainbow", class'Material'); case locs("Arg_MaterialSkins.Arg_Collectible_Skin_Static"): return GetMaterialInterfaceStatic("Arg_MaterialSkins_Content.Static", class'Material'); case locs("Arg_MaterialSkins.Arg_Collectible_Skin_Dweller"): if (ReturnExpressionMats) EyesMat = GetMaterialInterfaceStatic("Arg_MaterialSkins_Content.Eyes_Dweller", class'MaterialInstanceConstant'); return Material'hatintime_dwellerobjects.Materials.DwllerObject_Phong'; case locs("PlayableOwls.Hat_Collectible_Skin_BodyMaterial_Molemalgemite"): if (ReturnExpressionMats) EyesMat = GetMaterialInterfaceStatic("PLYOwls_Mats_PC.Materials.evil_Player", class'Material'); return GetMaterialInterfaceStatic("PLYOwls_Mats_PC.Materials.Mole_Ghost_Player", class'Material'); case locs("vanessacursemod.Tag_Collectible_Skin_BodyMaterial_GoldenCrown"): return Material'Vanessa_Tag_Content.Materials.GoldenCrown_Dye'; case locs("vanessacursemod.Tag_Collectible_Skin_BodyMaterial_Film"): if (ReturnExpressionMats) EyesMat = MaterialInstanceTimeVarying'Vanessa_Tag_Cosmetics.Expressions.Filmic_Eyes_timeVarying'; return Material'Vanessa_Tag_Cosmetics.FilmDye.FilmBody'; case locs("rgbshadowpuppet.iai_RGBSP"): if (ReturnExpressionMats) { EyesMat = MaterialInstanceConstant'HatInTime_Costumes2.Materials.shadow_eyes_mat'; FaceMat = MaterialInstanceConstant'HatInTime_Costumes2.Materials.shadow_face_mat'; } return GetMaterialInterfaceStatic("rgbpuppet.shadowmaterial", class'Material'); default: break; } if (ClassIsChildOf(SkinClass, class'Hat_Collectible_Skin_Summer_Fairy') || SkinPath ~= "Dye_Summer.Xara_MaterialDye_Summer") { if (ReturnExpressionMats) { EyesMat = MaterialInstanceConstant'HatInTime_Levels_Event_Summer.Materials.Skin_Dye_Summer_Expressions_Eyes'; FaceMat = MaterialInstanceConstant'HatInTime_Levels_Event_Summer.Materials.Skin_Dye_Summer_Expressions_Mouth'; } return Material'HatInTime_Levels_Event_Summer.Materials.Skin_Dye_Summer_Body'; } if (ClassIsChildOf(SkinClass, class'Hat_Collectible_Skin_BodyMaterial_Gold') || SkinPath ~= "Arg_MaterialSkins.Arg_Collectible_Skin_Gold") return Material'HatInTime_MaterialSkinsArgle.Gold'; if (ClassIsChildOf(SkinClass, class'Hat_Collectible_Skin_BodyMaterial_Metal') || SkinPath ~= "Arg_MaterialSkins.Arg_Collectible_Skin_Metal") return Material'HatInTime_MaterialSkinsArgle.Metal'; if (ClassIsChildOf(SkinClass, class'Hat_Collectible_Skin_BodyMaterial_Shadow') || SkinPath ~= "NyakuzaShadowPuppet.Alp_Collectible_Skin_Hood" || SkinPath ~= "DetecShadow.iai_ShadowDetective" || SkinPath ~= "ShadowParade.iai_ShadowParade" || SkinPath ~= "Arg_MaterialSkins.Arg_Collectible_Skin_Shadow") { if (ReturnExpressionMats) { EyesMat = MaterialInstanceConstant'HatInTime_Costumes2.Materials.shadow_eyes_mat'; FaceMat = MaterialInstanceConstant'HatInTime_Costumes2.Materials.shadow_face_mat'; } return Material'HatInTime_Costumes2.Materials.shadow_skin_mat'; } if (ClassIsChildOf(SkinClass, class'Hat_Collectible_Skin_BodyMaterial_Space') || SkinPath ~= "GalaxyPuppet.iai_GalaxySP" || SkinPath ~= "Arg_MaterialSkins.Arg_Collectible_Skin_Space") { if (ReturnExpressionMats) { EyesMat = MaterialInstanceConstant'HatInTime_MaterialSkinsArgle.Eyes_Space'; FaceMat = MaterialInstanceConstant'HatInTime_MaterialSkinsArgle.Expression_Space'; } return Material'HatInTime_MaterialSkinsArgle.Space'; } return None; } final static function MaterialInterface GetMaterialInterfaceStatic(string MaterialPath, class MaterialClass) //Override this method. Default one uses pretty expensive DynamicLoadObject to load MaterialInterface. I recommend caching your Materials to mod class. { if (MaterialClass == None || ClassIsDeprecated(MaterialClass)) return None; return MaterialInterface(DynamicLoadObject(MaterialPath, MaterialClass, true)); }