class Shara_ExpressionComponent extends Hat_ExpressionComponent abstract; /* All functions are written by Shararamosh. Please change Class Name (Shara_ExpressionComponent) and GameMod Class Name (Shara_GameMod) to your ones when using it. This Class should be used as superclass of your expression component. Please don't edit anything besides Class Name unless you know what you're doing. Last edited: 02.04.2023 5:45 GMT+3. */ //Please don't change the const keys below - that way we won't have garbage meta data since all mods will use same meta data keys. const EyesOriginKey = 'Player_EyesOrigin'; const ExpressionKey = 'Player_Expression'; function Init(optional Actor DebugCaller) //We need something to Update OP's expression component and that is our ModInstance. { local Shara_GameMod ModInstance; //MODIFIED HAT_EXPRESSIONCOMPONENT CODE START!!! if (!bAttached) { if (class'Engine'.static.IsEditor()) `Broadcast(string(self)$": component not attached to owner. Owner: "$(DebugCaller != None ? DebugCaller : Owner)); return; } if (GetOwnerMesh() == None) { if (class'Engine'.static.IsEditor()) `Broadcast(string(self)$": Unable to get owner mesh for owner: "$(DebugCaller != None ? DebugCaller : Owner)); return; } if (!IsSimpleFaceExpression()) CreateMaterial_Eyes(GetEyesIndex()); CreateMaterial_Face(GetFaceIndex()); ResetEyesMaterial(); ResetFaceMaterial(); ResetExpression(); //MODIFIED HAT_EXPRESSIONCOMPONENT CODE STOP!!! if (Hat_GhostPartyPlayer(Owner) == None) return; ModInstance = class'Shara_GameMod'.static.GetModInstance(); if (ModInstance != None) ModInstance.AddGhostPartyExpression(self); } function MaterialInstance CreateMaterial_Eyes(int index) //Modified to NOT create unnecessary MaterialInstances. { local MaterialInterface Parent; if (index < 0) return None; Parent = GetMeshFirstMaterial(index); if (Parent == None) { if (class'Engine'.static.IsEditor()) `broadcast(self$": Unable to find eye material at index "$index$" on actor "$Owner$"!"); return None; } Eyes.Material = ConditionalInitMaterialInstanceNoMesh(Parent); return Eyes.Material; } function ResetEyesMaterial(optional MaterialInstance c) //Modified to work with GetEyesIndex. { if (IsSimpleFaceExpression()) { ResetFaceMaterial(c); return; } if (c == None) c = Eyes.Material; SetMaterialAllComponents(c, GetEyesIndex()); } function MaterialInstance CreateMaterial_Face(int index) //Modified to NOT create unnecessary MaterialInstances. { local MaterialInterface Parent; if (index < 0) return None; Parent = GetMeshFirstMaterial(index); if (Parent == None) { `broadcast(self$": Unable to find face material at index "$index$" on actor "$Owner$"!"); return None; } Face.Material = ConditionalInitMaterialInstanceNoMesh(Parent); return Face.Material; } function ResetFaceMaterial(optional MaterialInstance c) //Modified to work with GetFaceIndex(). { if (c == None) c = Face.Material; SetMaterialAllComponents(c, GetFaceIndex()); } function MeshComponent GetOwnerMesh() //Returning OP's SkeletalMeshComponent too. { local MeshComponent comp; local Hat_GhostPartyPlayer gpp; comp = Super.GetOwnerMesh(); if (comp == None) { gpp = Hat_GhostPartyPlayer(Owner); if (gpp != None) return gpp.SkeletalMeshComponent; } return comp; } simulated function int GetEyesIndex() //Returns EyesIndex that fits actual MeshComponent used by Owner. { local SkeletalMeshComponent comp; comp = SkeletalMeshComponent(GetOwnerMesh()); if (comp == None) return EyeIndex; switch(comp.SkeletalMesh) { case SkeletalMesh'HatInTime_Characters_HatKid.models.HatKidHead': return 4; case SkeletalMesh'HatInTime_Characters_Coop.models.bowkid_head_skm': return 0; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.OneMeshHatKid': return 6; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bowkid_all': return 1; case SkeletalMesh'HatInTime_Characters_MuGirl.models.MuGirl': return 0; case SkeletalMesh'HatinTime_Characters_CoPartner.models.CoPartner': return 4; case SkeletalMesh'HatInTime_Costumes2.models.hk64_head': return -1; case SkeletalMesh'HatInTime_Costumes2.models.bow_kid_64_head': return -1; case SkeletalMesh'HatInTime_Costumes2.hk64_single': return -1; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bow_kid_64_all': return -1; case SkeletalMesh'Vanessa_Tag_Cosmetics.Punk.CapEX_HKHead': return 4; case SkeletalMesh'Vanessa_Tag_Cosmetics.Punk.CapEX_BKHead': return 0; case SkeletalMesh'Vanessa_Tag_Cosmetics.hkHEAD': return 4; case SkeletalMesh'HatInTime_WireframeSkin.models.Wireframe_HatKid_Head': return 4; case SkeletalMesh'HatInTime_WireframeSkin.models.Wireframe_BowKid_Head': return 0; default: return EyeIndex; } } function bool IsSimpleFaceExpression() //Modified to work with GetEyesIndex and GetFaceIndex(). { local int EIndex; EIndex = GetEyesIndex(); if (EIndex < 0) return false; if (EIndex == GetFaceIndex()) return true; return false; } simulated function int GetFaceIndex() //Returns FaceIndex that fits actual MeshComponent used by Owner. { local SkeletalMeshComponent comp; comp = SkeletalMeshComponent(GetOwnerMesh()); if (comp == None) return FaceIndex; switch(comp.SkeletalMesh) { case SkeletalMesh'HatInTime_Characters_HatKid.models.HatKidHead': return 0; case SkeletalMesh'HatInTime_Characters_Coop.models.bowkid_head_skm': return 1; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.OneMeshHatKid': return 5; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bowkid_all': return 2; case SkeletalMesh'HatInTime_Characters_MuGirl.models.MuGirl': return 1; case SkeletalMesh'HatinTime_Characters_CoPartner.models.CoPartner': return 3; case SkeletalMesh'HatInTime_Costumes2.models.hk64_head': return -1; case SkeletalMesh'HatInTime_Costumes2.models.bow_kid_64_head': return -1; case SkeletalMesh'HatInTime_Costumes2.hk64_single': return -1; case SkeletalMesh'HatinTime_GhostParty.SkeletalMeshes.bow_kid_64_all': return -1; case SkeletalMesh'Vanessa_Tag_Cosmetics.Punk.CapEX_HKHead': return 0; case SkeletalMesh'Vanessa_Tag_Cosmetics.Punk.CapEX_BKHead': return 1; case SkeletalMesh'Vanessa_Tag_Cosmetics.hkHEAD': return 0; case SkeletalMesh'HatInTime_WireframeSkin.models.Wireframe_HatKid_Head': return 0; case SkeletalMesh'HatInTime_WireframeSkin.models.Wireframe_BowKid_Head': return 1; default: return FaceIndex; } } function Update(float DeltaTime, optional bool Optimize = true) //In case of LP: writing expression info to meta data. In case of OP: reading expression info from meta data and applying it. { local EExpressionType OnlineExpression; if (!SetLocalPlayerExpressionMetaData()) { if (ReadOnlinePlayerExpressionMetaData(OnlineExpression)) { if (CurrentExpression != OnlineExpression) SetExpression(OnlineExpression); } } Super.Update(DeltaTime, Optimize); } function UpdateEyeSeek(float DeltaTime) //Updated and optimized some of the stuff. At the end it changes look direction for OP to the one from meta data. { local Rotator newRot, MyRotation; local float Rate; local Vector vMyOrigin, vOtherOrigin; local MeshComponent comp; local SkeletalMeshComponent skelComp; local StaticMeshComponent statComp; if (Eyes.NextSeek <= 0.0) { Eyes.NextSeek = RandRange(1.0, 2.2); Eyes.SeekObject = GetPointOfInterestTarget(); if (ForcedViewTarget != None) Eyes.SeekObject = ForcedViewTarget; if (Eyes.SeekObject == None) { if (EnableEyeSeek) { Eyes.NextSeekRest--; if (Eyes.NextSeekRest <= 0.0) { newRot = rot(0, 0, 0); Eyes.NextSeekRest = Rand(3)+2; } else { newRot.Pitch = RandRange(-8192, 8192); newRot.Yaw = RandRange(-8192, 8192); // Only deviate 50% away from our current origin newRot = class'Hat_Math'.static.RLerpShortest(Eyes.SeekOrigin, newRot, 0.5); } } Eyes.SeekOrigin = newRot; } } Rate = FMin(Abs(DeltaTime)*15.0, 1.0); if (Owner != None) { if (Eyes.ForcedLookSocket != '' || Eyes.ForcedLookBone != '' || Eyes.ForcedLookVector.X != 0.0 || Eyes.ForcedLookVector.Y != 0.0 || Eyes.ForcedLookVector.Z != 0.0) { vMyOrigin = GetOriginLocation(Owner); MyRotation = GetOriginRotation(Owner); vOtherOrigin = vMyOrigin+TransformVectorByRotation(Owner.Rotation, Normal(Eyes.ForcedLookVector)*200.0); if (Eyes.ForcedLookSocket != '' || Eyes.ForcedLookBone != '') { comp = GetOwnerMesh(); if (comp != None) { skelComp = SkeletalMeshComponent(comp); if (skelComp != None) { if (Eyes.ForcedLookSocket != '') { if (skelComp.GetSocketByName(Eyes.ForcedLookSocket) != None) skelComp.GetSocketWorldLocationAndRotation(Eyes.ForcedLookSocket, vOtherOrigin, newRot); } else if (Eyes.ForcedLookBone != '') { if (skelComp.MatchRefBone(Eyes.ForcedLookBone) != INDEX_NONE) skelComp.GetBoneLocation(Eyes.ForcedLookBone); } } else { statComp = StaticMeshComponent(comp); if (statComp != None && Eyes.ForcedLookSocket != '') { if (statComp.GetSocketByName(Eyes.ForcedLookSocket) != None) statComp.GetSocketWorldLocationAndRotation(Eyes.ForcedLookSocket, vOtherOrigin, newRot); } } } } Eyes.SeekOrigin = Rotator(vOtherOrigin-vMyOrigin)-MyRotation; Rate = DebugDrawEyeLines ? 1.0 : FMin(10.0*Abs(DeltaTime), 1.0); if (!Owner.bHidden && DebugDrawEyeLines) { Owner.DrawDebugLine(vOtherOrigin, vMyOrigin, 255, 25, 25, false); Owner.DrawDebugSphere(vOtherOrigin, 2.0, 32, 255, 25, 25, false); } } else if (Eyes.SeekObject != None) { vMyOrigin = GetOriginLocation(Owner); MyRotation = GetOriginRotation(Owner); vOtherOrigin = GetOriginLocation(Eyes.SeekObject); Rate = FMin(Abs(DeltaTime)*20.0, 1.0); if (Camera(Eyes.SeekObject) != None) Rate = 1.0; if (Eyes.SeekObject == ForcedViewTarget) Rate = 1.0; if (!Owner.bHidden && DebugDrawEyeLines) { Rate = 1.0; Owner.DrawDebugLine(vOtherOrigin, vMyOrigin, 255, 25, 25, false); Owner.DrawDebugSphere(vOtherOrigin, 2.0, 32, 255, 25, 25, false); } Eyes.SeekOrigin = Rotator(vOtherOrigin-vMyOrigin)-MyRotation; } if (!SetLocalPlayerEyesMetaData()) { if (ReadOnlinePlayerEyesMetaData(Eyes.SeekOrigin)) Rate = 1.0; } } if (Eyes.CurrentEyesOrigin.Yaw == Eyes.SeekOrigin.Yaw && Eyes.CurrentEyesOrigin.Pitch == Eyes.SeekOrigin.Pitch) return; Eyes.CurrentEyesOrigin = class'Hat_Math'.static.RLerpShortest(Eyes.CurrentEyesOrigin, Eyes.SeekOrigin, Rate); UpdateEyeOffset(); } static function Vector GetOriginLocation(Actor a) //Merged chunk of code that returns view point of supported Actor. { local Pawn p; local Hat_NPC npc; local Hat_InteractInterface ii; local Camera c; local Vector v; local Rotator r; if (a == None) return vect(0.0, 0.0, 0.0); p = Pawn(a); if (p != None) return p.GetPawnViewLocation(); npc = Hat_NPC(a); if (npc != None) return npc.GetPawnViewLocation(); ii = Hat_InteractInterface(a); if (ii != None) { ii.GetTargetedViewLocation(v); return v; } c = Camera(a); if (c != None) { c.GetCameraViewPoint(v, r); return v; } return a.Location; } static function Rotator GetOriginRotation(Actor a) //Merged chunk of code that returns view rotation of supported Actor. { local Hat_NPC npc; if (a == None) return rot(0, 0, 0); npc = Hat_NPC(a); if (npc != None) return npc.GetPawnViewRotation(); return a.Rotation; } simulated function bool SetLocalPlayerEyesMetaData() //Function that writes look direction into LP's meta data. Won't do anything if this particular ExpressionComponent is not ply.ExpressionComponent. { local Hat_Player ply; local Hat_GhostPartyPlayerStateBase PlayerState; local int PlayerIndex; local string s; ply = Hat_Player(Owner); if (ply == None) return false; if (ply.ExpressionComponent != self) //This ExpressionComponent is not actually used by this Player, so we don't modify meta data. return true; PlayerIndex = GetPawnPlayerIndex(ply); if (PlayerIndex < 0) return true; PlayerState = class'Hat_GhostPartyPlayerStateBase'.static.GetLocalPlayerState(PlayerIndex); if (PlayerState == None) return true; s = string(Eyes.CurrentEyesOrigin); if (PlayerState.GetPlayerStateMeta(EyesOriginKey) ~= s) return true; PlayerState.SetPlayerStateMeta(EyesOriginKey, s); return true; } simulated function ReadOnlinePlayerEyesMetaData(out Rotator EyesOrigin) //Function that reads look direction from OP's meta data. { local Hat_GhostPartyPlayer gpp; local string s; gpp = Hat_GhostPartyPlayer(Owner); if (gpp == None || gpp.PlayerState == None) return false; EyesOrigin = rot(0, 0, 0); s = gpp.PlayerState.GetPlayerStateMeta(EyesOriginKey); if (s == "") return true; EyesOrigin = Rotator(s); return true; } simulated function bool SetLocalPlayerExpressionMetaData() //Function that writes current expression into LP's meta data. { local Hat_Player ply; local Hat_GhostPartyPlayerStateBase PlayerState; local int PlayerIndex; local string s; ply = Hat_Player(Owner); if (ply == None) return false; if (ply.ExpressionComponent != self) return true; PlayerIndex = GetPawnPlayerIndex(ply); if (PlayerIndex < 0) return true; PlayerState = class'Hat_GhostPartyPlayerStateBase'.static.GetLocalPlayerState(PlayerIndex); if (PlayerState == None) return true; s = string(int(CurrentExpression)); if (PlayerState.GetPlayerStateMeta(ExpressionKey) ~= s) return true; PlayerState.SetPlayerStateMeta(ExpressionKey, s); return true; } simulated function bool ReadOnlinePlayerExpressionMetaData(out EExpressionType PlayerExpression) //Function that reads current expression from OP's meta data. { local Hat_GhostPartyPlayer gpp; local string s; local int n; gpp = Hat_GhostPartyPlayer(Owner); if (gpp == None || gpp.PlayerState == None) return false; s = gpp.PlayerState.GetPlayerStateMeta(ExpressionKey); if (s == "") return false; n = int(s); if (n < 0 || n > ExpressionCount-1) return false; PlayerExpression = EExpressionType(n); return true; } //The following functions are copied from Shara_SteamID_Tools Class. 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 (p.PlayerReplicationInfo != None) { pc = PlayerController(p.PlayerReplicationInfo.Owner); if (pc != None) return pc; } if (IteratedPawns.Find(p) == INDEX_NONE) 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); } static function int GetPawnPlayerIndex(Pawn p) //Returns player index of Pawn by first finding PlayerController of it. { return GetPlayerIndex(GetPawnPlayerController(p)); } static function int GetPlayerIndex(PlayerController pc) //Returns player index of PlayerController by either casting to Hat_PlayerController_Base or GamePlayerController or by checking Engine.GamePlayers Array. { local GamePlayerController gpc; local Hat_PlayerController_Base hpcb; local Engine e; local int i; if (pc == None) return -1; gpc = GamePlayerController(pc); if (gpc != None) { hpcb = Hat_PlayerController_Base(gpc); if (hpcb != None) return hpcb.GetPlayerIndex(); return gpc.GetUIPlayerIndex(); } e = class'Engine'.static.GetEngine(); if (e == None) return -1; for (i = 0; i < e.GamePlayers.Length; i++) { if (e.GamePlayers[i] == None) continue; if (e.GamePlayers[i].Actor == pc) return i; } return -1; } 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. From Shara_SkinColors_Tools. { local MaterialInstance inst; if (mat == None) return None; if (MaterialInstance(mat) != None) { if (MaterialInstance(mat).IsInMapOrTransientPackage()) return MaterialInstance(mat); if (MaterialInstanceConstant(mat) != None) { inst = new(None) class'MaterialInstanceConstant'; inst.SetParent(mat); return inst; } else if (MaterialInstanceTimeVarying(mat) != None) { inst = new(None) class'MaterialInstanceTimeVarying'; inst.SetParent(mat); return inst; } } else { inst = new(None) class'MaterialInstanceTimeVarying'; inst.SetParent(mat); return inst; } return None; }