Code viewer
Custom Train Class Pack + Dweller Trains! (v0.2.1)
#CustomActor
Just like the name says :)
Remember to use mcu8_TrainPoint instead of Hat_TrainPoint! Special thanks for the Starblaster64 for reimplementing the native stuff into UnrealScript :)
/** * trash by m_cu8 * DON'T ASK ME HOW THAT WORKS BECAUSE I HAVE NO CLUE LMAO * (I even decompiled this garbage because I thought that we have outdated source code... but also I'm too lazy to swap it back with the original class code... peck it, it works, so I won't touch this again) */ class mcu8_Hazard_Train extends Hat_Hazard_Train_Base dependson(CameraModifier_CameraShake) dontsortcategories(OnNPCManagedDistance) autoexpandcategories(Navigation); /** How long the train should be inactive before starting its route (first activation only). */ var(TrainFront) float StartWait<EditCondition=!Following>; /** Train teleports all at once instead of car-by-car. */ var(TrainFront) bool InstantTeleport<EditCondition=!Following>; /** Rotates player camera with train rotation when riding on the train. */ var(TrainFront) bool OrientPlayerCamera<EditCondition=!Following>; var(TrainFront) bool TurnCameraToFaceTrainDirectionOnBase<EditCondition=!Following>; var(TrainFront) bool TurnCameraEvenIfPreviousBaseWasTrain<EditCondition=TurnCameraToFaceTrainDirectionOnBase>; /** If enabled, colliding with the front of the train will trigger fall recovery instead of knocking you off the track. Recommended when the train is in a tunnel. */ var(TrainFront) bool DoJumpFailRecovery; /** Camera shakes when train is nearby */ var(TrainFront) bool UseShakeCamera<EditCondition=!Following>; /** Whether to use the train horn or not */ var(TrainFront) bool UseTrainHorn<EditCondition=!Following>; var(TrainFront) bool IgnoreFrontDamageWhileAirborne; var(TrainFront) bool UseFastTrainVFX; var bool DamageOnAnyTouch; var bool CamShakeDirty; var bool IsFront; var bool IsDamageFront; var bool OptimizeStraightTrack; var transient bool Initialized; var transient bool IsPlayerNearby; var bool FollowFrontDirectly; /** Point that the player returns to if fall recovery is triggered by this train. If not set, it will use the last visited checkpoint. */ var(TrainFront) Actor JumpFailReturnPoint<EditCondition=DoJumpFailRecovery>; /** The camera shake applied near the wheels of the train. */ var(TrainFront) editinline CameraShake CamShake<EditCondition=!Following>; /** The camera shake applied near the tracks in front of an incoming train. */ var(TrainFront) editinline CameraShake WarningCamShake<EditCondition=!Following>; /** How far ahead of the front train the camera shake is centralized at. */ var(TrainFront) float WarningCameraShakeLead<EditCondition=!Following>; /** Distance at which the camera shake fades away. */ var(TrainFront) float CameraShakeFalloff<UIMin=0|EditCondition=!Following>; /** Distance at which the warning camera shake fades away. */ var(TrainFront) float WarningShakeFalloff<UIMin=0|EditCondition=!Following>; /** How far ahead of the front train is the detection area for the train horn. */ var(TrainFront) float TrainHornLead<EditCondition=!Following>; /** The radius of the sphere used for the train horn detection area. */ var(TrainFront) float TrainHornSize<UIMin=0|EditCondition=!Following>; /** The train/car this car is immediately connected to. Setting this ignores all property settings in the Train Front" section. */ var(TrainCar) mcu8_Hazard_Train Following; /** Total length of this car in the train. Assumes car pivots around its center. */ var(TrainCar) float CarLength<UIMin=0>; /** How far the 0,0,0 origin point of the model is from the ground. */ var(TrainCar) float CarHeight<UIMin=0>; /** Sound that plays when you are about to get hit by the train. */ var(Sounds) SoundCue TrainHornSound; /** Sound played by the front of the train while moving. */ var(Sounds) export editinline AudioComponent TrainFrontSound; /** Sound played by the cars of the train while moving. */ var(Sounds) export editinline AudioComponent TrainWheelSound; /** Sound that plays when the train runs into you. */ var(Sounds) SoundCue DamageCollideSound; var export editinline Hat_DrawCatapultComponent StartLineComponent; var export editinline Hat_DrawCatapultComponent FollowLineComponent; var Rotator AngleChangeCamera; var mcu8_Hazard_Train FrontTrain; var float FrontDist; var array<mcu8_Hazard_Train> Train; var ECollisionType InitialCollision; var float VelocityTransitionTime; var float VelocityTransitionPercentage; var transient array<Actor> PlayersBasedOnTrain; var transient float LastHornPlayTime; var transient array<int> CamShakeCache; var transient array<int> WarningCamShakeCache; var(TrainFront) float Speed<UIMin=0|EditCondition=!Following>; var(TrainFront) float SpeedMultiplier<HideInSimpleEditor=|UIMin=1>; var(TrainFront) mcu8_TrainPoint StartPoint<EditCondition=!Following>; var float AdjustedSpeed; var transient float CurrentSpeed; var bool IsDweller; var bool InvertCollisionWhenDeactivated_Internal; var transient bool InitialBlockActors; simulated event PostBeginPlay() { super(InterpActor).PostBeginPlay(); InitialCollision = CollisionType; if((Owner == none) && CreationTime <= float(0)) { InitTrain(); } //return; } // for dweller trains function bool CanActivate(Actor Other) { if (Other.IsA('Hat_SubconPainting')) return true; if (!Other.IsA('Pawn')) return false; if (Pawn(Other).Controller == None) return false; if (!Pawn(Other).Controller.IsA('Hat_PlayerController')) return false; return true; } // Subclass this to perform your own blocking check. Return false if the Actor should block, return true if the Actor should not block event bool ShouldIgnoreBlockingBy(const Actor Other) { if (!isDweller) return Super.ShouldIgnoreBlockingBy(Other); if (Other.IsA('Hat_DwellerBell')) return true; if (InvertCollisionWhenDeactivated_Internal && CanActivate(Other)) { CollisionComponent.CanBeEdgeGrabbed = class'Hat_DwellerMaskComponent'.static.IsLocationBlocked(Other.Location, 1, !InitialBlockActors); // prevent player climb when disabled return !class'Hat_DwellerMaskComponent'.static.IsLocationBlocked(Other.Location, 1, !InitialBlockActors); } return InitialBlockActors; } function InitTrain(optional bool DoInitShake) { DoInitShake = true; if((StartPoint == none) && Following == none) { Destroy(); return; } if(StartPoint != none) { if(((StartPoint.TrackMaster == none) || StartPoint.TrackMaster.EditorTimeTrackCalculationState != 2) || StartPoint.TrackMaster.SubPoints.Length == 0) { ScriptTrace(); Destroy(); return; } } IsFront = Following == none; if(IsFront) { CurrentSubPoint = StartPoint.GetMySubPoint(); if(StartWait > float(0)) { UpdateAdjustedSpeed(); MoveTrain(AdjustedSpeed * -StartWait); } if(StartPoint.TrackMaster != none) { MoveTrain(StartPoint.TrackMaster.SyncStartOffset); } } else { FrontTrain = GetFrontTrain(); FrontTrain.Train.AddItem(self); if(DoInitShake) { UseShakeCamera = FrontTrain.UseShakeCamera; CameraShakeFalloff = FrontTrain.CameraShakeFalloff; CamShake = FrontTrain.CamShake; } InstantTeleport = FrontTrain.InstantTeleport; DoJumpFailRecovery = FrontTrain.DoJumpFailRecovery; JumpFailReturnPoint = FrontTrain.JumpFailReturnPoint; StartPoint = FrontTrain.StartPoint; IgnoreFrontDamageWhileAirborne = FrontTrain.IgnoreFrontDamageWhileAirborne; UseFastTrainVFX = FrontTrain.UseFastTrainVFX; bPlayerShouldStayBasedAfterJumping = FrontTrain.bPlayerShouldStayBasedAfterJumping; if(StaticMeshComponent.HiddenGame || StaticMeshComponent.LightEnvironment == none) { SetTickIsDisabled(true); } LightEnvironment.SetEnabled(false); } UpdateAdjustedSpeed(); if(!UseShakeCamera) { CamShake = none; WarningCamShake = none; } if(!IsFront) { WarningCamShake = none; } OptimizeStraightTrack = StartPoint.TrackMaster.TrackIsStraight; if(OptimizeStraightTrack) { SetRotation(StartPoint.TrackMaster.SubPoints[CurrentSubPoint].Rotation); } Initialized = true; //return; } function bool SetIsPlayerNearby(bool B, bool bEnableDebugCollisionView) { if(IsPlayerNearby == B) { return false; } IsPlayerNearby = B; SetCollision(B && default.bCollideActors, B && default.bBlockActors); SetCollisionType(((B) ? InitialCollision : 1)); StaticMeshComponent.SetHidden(!bBlockActors || !bEnableDebugCollisionView); return true; //return ReturnValue; } event OnTickOptimized() { CleanCamShake(); } simulated event Attach(Actor Other) { local Hat_Player ply; local SetRotationGradually srg; if(FrontTrain == none) { return; } ply = Hat_Player(Other); if(ply == none) { return; } Other.bIgnoreAirBaseDownwardMovement = true; Other.AirBaseWeight = 1.0; if(PlayersBasedOnTrain.Find(Other) == -1) { PlayersBasedOnTrain.AddItem(Other); } if(FrontTrain.PlayersBasedOnTrain.Find(Other) == -1) { FrontTrain.PlayersBasedOnTrain.AddItem(Other); } FrontTrain.UpdateMusicParameter(); if(UseFastTrainVFX) { if(!FrontTrain.IsTimerActive('FadeOutTrainFrontSound') && FrontTrain.TrainFrontSound != none) { FrontTrain.TrainFrontSound.FadeIn(0.50, 1.0); } else { FrontTrain.ClearTimer('FadeOutTrainFrontSound'); } ply.CatTrainMovingFastParticle.SetActive(true); Hat_PlayerCamera(PlayerController(ply.Controller).PlayerCamera).AddCameraModeClass(class'mcu8_CamMode_CatTrainRoof'); if(!ply.IsTimerActive('FadeOutWindSound')) { ply.CatTrainWindSound.FadeIn(0.80, 1.0); } else { ply.ClearTimer('FadeOutWindSound'); } } if(TurnCameraToFaceTrainDirectionOnBase) { if((((ply.Controller != none) && ply.LastFloorActor != self) && ply.LastFloorActor != FrontTrain) && ((ply.LastFloorActor == none) || !ply.LastFloorActor.IsA('mcu8_Hazard_Train')) || TurnCameraEvenIfPreviousBaseWasTrain) { srg.Rotation = Rotation; srg.Rotation.Pitch = -2000; srg.AffectPitch = true; srg.AffectRoll = false; srg.TurnTime = 0.50; Hat_PlayerController(ply.Controller).OnSetCameraRotationS(srg); } } //return; } function ApplyVelocityToActor(Actor Other, float Weight) { local Vector VelocityAdd; VelocityAdd = (GetVelocity()) * Weight; if(VelocityAdd.Z < float(0)) { VelocityAdd *= vect(1.0, 1.0, 0.0); } VelocityAdd /= Other.CustomTimeDilation; if(Other.Physics == 1) { VelocityAdd *= 0.50; } Other.Velocity += VelocityAdd; //return; } simulated event Detach(Actor Other) { if((Pawn(Other) != none) && !bPlayerShouldStayBasedAfterJumping) { ApplyVelocityToActor(Other, 1.0); } if(FrontTrain == none) { return; } if(!Other.IsA('Hat_Player')) { return; } PlayersBasedOnTrain.RemoveItem(Other); FrontTrain.PlayersBasedOnTrain.RemoveItem(Other); FrontTrain.UpdateMusicParameter(); if(UseFastTrainVFX) { if(PlayersBasedOnTrain.Length == 0) { FrontTrain.SetTimer(0.30, false, 'FadeOutTrainFrontSound'); } Other.AirBaseWeight = 1.0; Hat_Player(Other).CatTrainMovingFastParticle.SetActive(false); Hat_PlayerCamera(PlayerController(Hat_Player(Other).Controller).PlayerCamera).RemoveCameraModeClass(class'mcu8_CamMode_CatTrainRoof'); Hat_Player(Other).SetTimer(0.30, false, 'FadeOutWindSound'); } //return; } function UpdateMusicParameter() { if((class'Hat_GameManager'.static.GetStaticMusicManager() != none) && class'Hat_GameManager'.static.GetStaticMusicManager().MusicTreeInstance != none) { class'Hat_GameManager'.static.GetStaticMusicManager().MusicTreeInstance.SetParameterInt('OnMetroTrain', ((PlayersBasedOnTrain.Length > 0) ? 1 : 0)); } //return; } function FollowerTick(float D) { if(!Initialized) { return; } Speed = Following.Speed; SpeedMultiplier = Following.SpeedMultiplier; OrientPlayerCamera = Following.OrientPlayerCamera; TurnCameraToFaceTrainDirectionOnBase = Following.TurnCameraToFaceTrainDirectionOnBase; TurnCameraEvenIfPreviousBaseWasTrain = Following.TurnCameraEvenIfPreviousBaseWasTrain; MovementTick(D); if(UseShakeCamera) { UpdateCamShake(CurrentSpeed / AdjustedSpeed); } //return; } simulated event Tick(float D) { if(!IsFront) { return; } if(!Initialized) { return; } if(MovementTick(D)) { TickFollowers(D); TickAttachedPlayers(D); if(IsA('mcu8_Hazard_CatTrain')) { UpdateVisuals(StartPoint.TrackMaster.SubPoints); } } if(UseShakeCamera) { UpdateCamShake(CurrentSpeed / AdjustedSpeed); } if(UseTrainHorn) { UpdateTrainHorn(); } //return; } function TickFollowers(float D) { local mcu8_Hazard_Train T; foreach Train(T) { if(T != self) { T.FollowerTick(D); } } //return; } function TickAttachedPlayers(float D) { local Actor A, TracedActor; local Vector HitLoc, HitNrm; local Hat_Player ply; local float VelocityDot, PlayerSpeedSq, OldWeight; local bool bShouldDampenAirAttachment, bPlayerIsOverTrain; foreach PlayersBasedOnTrain(A) { ply = Hat_Player(A); ply.CatTrainMovingFastParticle.SetRotation(ply.Base.Rotation); if(((ply != none) && ply.Physics == 2) && bPlayerShouldStayBasedAfterJumping) { bShouldDampenAirAttachment = false; if(ply.AirBaseWeight < 1.0) { bShouldDampenAirAttachment = true; } if(!bShouldDampenAirAttachment) { if(ply.m_iPlatformMove != 0) { bShouldDampenAirAttachment = true; } } if(!bShouldDampenAirAttachment) { PlayerSpeedSq = VSizeSq(ply.Velocity * vect(1.0, 1.0, 0.0)); VelocityDot = Normal(ply.Velocity * vect(1.0, 1.0, 0.0)) Dot Normal(mcu8_Hazard_Train(ply.Base).GetVelocity()); if((PlayerSpeedSq >= (float(200) ** float(2))) && VelocityDot < 0.60) { bShouldDampenAirAttachment = true; } } if(!bShouldDampenAirAttachment) { bPlayerIsOverTrain = false; foreach ply.TraceActors(class'Actor', TracedActor, HitLoc, HitNrm, ply.Location + vect(0.0, 0.0, -750.0),, vect(0.0, 0.0, 0.0)) { if(!TracedActor.bCollideActors || !TracedActor.bBlockActors) { continue; } if(TracedActor.IsA('mcu8_Hazard_Train')) { if(mcu8_Hazard_Train(TracedActor).FrontTrain == self) { if(ply.Base != TracedActor) { ply.SetBase(TracedActor); } ply.AirBaseWeight = 1.0; ply.bIgnoreAirBaseDownwardMovement = true; bPlayerIsOverTrain = true; break; } continue; } if(TracedActor.bStatic && !TracedActor.IsA('Hat_Hazard_RoadBlock')) { bShouldDampenAirAttachment = true; break; } } if(!bPlayerIsOverTrain) { bShouldDampenAirAttachment = true; } } if(bShouldDampenAirAttachment) { OldWeight = ply.AirBaseWeight; ply.AirBaseWeight -= (D / VelocityTransitionTime); mcu8_Hazard_Train(ply.Base).ApplyVelocityToActor(ply, (OldWeight - ply.AirBaseWeight) * VelocityTransitionPercentage); if(ply.AirBaseWeight <= 0.0) { ply.SetBase(none); ply.AirBaseWeight = 1.0; } } } } //return; } function TurnBasedPlayerCamera(Rotator Offset) { local Player ply; local array<Player> GamePlayers; Offset.Pitch = 0; Offset.Roll = 0; GamePlayers = class'Engine'.static.GetEngine().GamePlayers; foreach GamePlayers(ply) { if((((ply.Actor != none) && ply.Actor.Pawn != none) && ply.Actor.Pawn.Base == self) && ply.Actor.Pawn.Physics == 1) { ply.Actor.SetRotation(ply.Actor.Rotation + Offset); } } //return; } function mcu8_Hazard_Train GetFrontTrain() { local mcu8_Hazard_Train R; R = self; FrontDist = 0.0; J0x1A: if(R.Following != none) { FrontDist += ((R.CarLength * 0.50) + (R.Following.CarLength * 0.50)); R = R.Following; if(R.FrontTrain != none) { FrontDist += R.FrontDist; R = R.FrontTrain; // [Explicit Break] goto J0x15F; } if(R == self) { // [Explicit Break] goto J0x15F; } J0x15F: // [Loop Continue] goto J0x1A; } return R; //return ReturnValue; } function UpdateTrainHorn() { local Player ply; local array<Player> GamePlayers; if((TrainHornSound == none) || TrainHornSize == float(0)) { return; } if((LastHornPlayTime > float(0)) && (LastHornPlayTime + float(3)) >= WorldInfo.TimeSeconds) { return; } GamePlayers = class'Engine'.static.GetEngine().GamePlayers; foreach GamePlayers(ply) { if((((ply.Actor == none) || ply.Actor.Pawn == none) || !ply.Actor.Pawn.IsA('Hat_Player')) || ply.Actor.Pawn.Health == 0) { continue; } if((ply.Actor.Pawn.Base != none) && ply.Actor.Pawn.Base.IsA('mcu8_Hazard_CatTrain')) { continue; } if(VSizeSq(ply.Actor.Pawn.Location - Location) > ((((TrainHornSize * 0.50) + TrainHornLead) + (CarLength * 0.50)) ** float(2))) { continue; } if(VSizeSq((((GetSubPointOffsetBase(StartPoint.TrackMaster.SubPoints, TrainHornLead + (CarLength * 0.50), true)) + ((vect(0.0, 0.0, 1.0) * TrainHornSize) * 0.660)) - (ply.Actor.Pawn.Location + vect(0.0, 0.0, 50.0))) * vect(1.0, 1.0, 1.50)) > (TrainHornSize ** float(2))) { continue; } PlaySound(TrainHornSound); LastHornPlayTime = WorldInfo.TimeSeconds; Hat_PawnCombat(ply.Actor.Pawn).ShakeNearbyCameras(100.0, 256.0, 4.50, 2.0, true); Hat_Player(ply.Actor.Pawn).SetExpression(8, 3.0); if(Rand(4) == 1) { Hat_Player(ply.Actor.Pawn).PlayVoice(Hat_Player(ply.Actor.Pawn).VoiceTrainStartled); } return; } //return; } function UpdateCamShake(optional float Intensity) { local PlayerController PC; local array<Player> GamePlayers; local Vector vertweight; local float CamShakeAmount, WarningShakeAmount; local int I; local bool ForceZeroShake; Intensity = 1.0; vertweight = vect(1.0, 1.0, 2.0); GamePlayers = class'Engine'.static.GetEngine().GamePlayers; I = 0; J0x69: if(I < GamePlayers.Length) { if((GamePlayers[I].Actor == none) || Hat_Player(GamePlayers[I].Actor.Pawn) == none) { } else { ForceZeroShake = Hat_PlayerController(GamePlayers[I].Actor).bCinematicMode; PC = GamePlayers[I].Actor; CamShakeAmount = 0.0; WarningShakeAmount = 0.0; if((Intensity > float(0)) && (CamShake != none) || WarningCamShake != none) { if((CameraShakeFalloff > float(0)) && CamShake != none) { if(!ForceZeroShake && VSizeSq(PC.Pawn.Location - Location) <= (CameraShakeFalloff ** float(2))) { CamShakeAmount = (1.0 - FClamp((VSize(((Location - (vect(0.0, 0.0, 1.0) * CarHeight)) - PC.Pawn.Location) * vertweight) / CameraShakeFalloff) * Intensity, 0.0, 1.0)) * FMin(1.0, Intensity); } if((CamShakeAmount > float(0)) || CamShakeDirty) { SetCamShakeScale(PC, CamShake, CamShakeAmount); } } if(((IsFront && WarningShakeFalloff > float(0)) && WarningCamShake != none) && WarningCameraShakeLead != float(0)) { if(!ForceZeroShake && VSizeSq(PC.Pawn.Location - Location) <= ((((WarningShakeFalloff * 0.50) + WarningCameraShakeLead) + (CarLength * 0.50)) ** float(2))) { WarningShakeAmount = (1.0 - FClamp(VSize((((GetSubPointOffsetBase(StartPoint.TrackMaster.SubPoints, WarningCameraShakeLead + (CarLength * 0.50), true)) - (vect(0.0, 0.0, 1.0) * CarHeight)) - PC.Pawn.Location) * vertweight) / WarningShakeFalloff, 0.0, 1.0)) * FMin(1.0, Intensity); } if((WarningShakeAmount > float(0)) || CamShakeDirty) { SetCamShakeScale(PC, WarningCamShake, WarningShakeAmount); } if(WarningShakeAmount > 0.450) { Hat_Player(PC.Pawn).ExpressionComponent.ForcedViewTarget = self; Hat_Player(PC.Pawn).ExpressionComponent.SetDefaultExpression(8); } else { if(Hat_Player(PC.Pawn).ExpressionComponent.ForcedViewTarget == self) { Hat_Player(PC.Pawn).ExpressionComponent.ForcedViewTarget = none; Hat_Player(PC.Pawn).ExpressionComponent.SetDefaultExpression(0); } } } CamShakeDirty = (CamShakeAmount + WarningShakeAmount) > float(0); } } ++ I; // [Loop Continue] goto J0x69; } //return; } function CleanCamShake() { local array<Player> GamePlayers; local Player ply; if(!CamShakeDirty) { return; } GamePlayers = class'Engine'.static.GetEngine().GamePlayers; foreach GamePlayers(ply) { if(CamShake != none) { SetCamShakeScale(ply.Actor, CamShake, 0.0); } if(((WarningCamShake != none) && WarningCameraShakeLead != float(0)) && IsFront) { SetCamShakeScale(ply.Actor, WarningCamShake, 0.0); } } CamShakeDirty = false; //return; } function SetCamShakeScale(PlayerController PC, CameraShake Cam, float Amount) { local int I; if(Amount == float(0)) { PC.ClientStopCameraShake(Cam); return; } I = PC.PlayerCamera.CameraShakeCamMod.ActiveShakes.Find('SourceShakeName', Cam.Name); if(I == -1) { PC.ClientPlayCameraShake(Cam); I = PC.PlayerCamera.CameraShakeCamMod.ActiveShakes.Find('SourceShakeName', Cam.Name); } if(I != -1) { PC.PlayerCamera.CameraShakeCamMod.ActiveShakes[I].Scale = Amount; } //return; } event Bump(Actor Other, PrimitiveComponent OtherComp, Vector HitNormal) { TrainHit(Hat_PawnCombat(Other), HitNormal); super(Actor).Bump(Other, OtherComp, HitNormal); //return; } event Touch(Actor Other, PrimitiveComponent OtherComp, Vector HitLocation, Vector HitNormal) { TrainHit(Hat_PawnCombat(Other), HitNormal); super(Actor).Touch(Other, OtherComp, HitLocation, HitNormal); //return; } function TrainHit(Hat_PawnCombat P, Vector HitNormal) { local float KnockForce, KnockUp, KnockSideways; local Vector V; local bool DealDamage; if(P == none) { return; } // for dweller trains if (ShouldIgnoreBlockingBy(p)) return; if((IgnoreFrontDamageWhileAirborne && CollisionComponent.CanBeStoodOn) && (P.Physics == 2) || P.Physics == 0) { return; } if((IsDamageFront && (vector(Rotation) Dot HitNormal) < 0.50) && !DamageOnAnyTouch) { return; } if((AdjustedSpeed < float(400)) && !DamageOnAnyTouch) { return; } if((P.Base != none) && P.Base.IsA(Class.Name)) { return; } if((P.Base == none) && !IsDamageFront) { return; } DealDamage = P.CanTakeDamage(false, self, 1); if((DamageCollideSound != none) && DealDamage) { PlaySound(DamageCollideSound); } if(IsDamageFront) { KnockForce = 700.0; KnockUp = 1500.0; KnockSideways = 0.550; V = VLerp(Normal(P.Location - Location), HitNormal, 0.50); if(KnockSideways > float(0)) { V = VLerp(V, ((HitNormal Cross vect(0.0, 0.0, 1.0)) * -KnockSideways) * float(((((Normal(P.Location - Location) Cross vect(0.0, 0.0, 1.0)) Dot HitNormal) > float(0)) ? 1 : -1)), KnockSideways); } if(DealDamage) { P.TakeDamage(1, none, P.Location, ((Normal(V) * vect(1.0, 1.0, 0.0)) * KnockForce) + (vect(0.0, 0.0, 1.0) * KnockUp), class'Hat_DamageType_WorldShove',, self); if(DoJumpFailRecovery && P.IsA('Hat_Player')) { Hat_Player(P).DoJumpFailRecovery(0.0, JumpFailReturnPoint,, 0.50); } } else { P.Velocity = (Normal(V) * vect(1.0, 1.0, 0.0)) * KnockForce; } } else { if(DealDamage) { P.TakeDamage(1, none, P.Location, (Normal((P.Location - Location) * vect(1.0, 1.0, 0.0)) * float(700)) + vect(0.0, 0.0, 300.0), class'Hat_DamageType_WorldShove',, self); } } //return; } event editoronly OnEditorPropertyChanged(name PropertyName) { if(PropertyName == 'Speed') { if(StartPoint != none) { StartPoint.PerformEditorTimeTrackCalculation(); } } //return; } event editoronly CheckForErrors(out array<string> ErrorMessages) { super(Actor).CheckForErrors(ErrorMessages); if(StartPoint != none) { if(StartPoint.TrackMaster == none) { ErrorMessages.AddItem("Track has no master point. Move a track point!"); } else { if(StartPoint.TrackMaster.EditorTimeTrackCalculationState != 2) { ErrorMessages.AddItem("Track calculations incomplete. Move a track point!"); } else { if(StartPoint.TrackMaster.SubPoints.Length == 0) { ErrorMessages.AddItem("Track has no subpoints. Move a track point!"); } } } } //return; } function Actor GetAdjustedLastFloorActorForPlayer() { return FrontTrain; //return ReturnValue; } function UpdateAdjustedSpeed() { AdjustedSpeed = (Speed * StartPoint.TrackMaster.SyncSpeedMultiplier) * SpeedMultiplier; //return; } function float GetAdjustedSpeed() { UpdateAdjustedSpeed(); return AdjustedSpeed; //return ReturnValue; } function Vector GetVelocity() { return AdjustedSpeed * vector(Rotation); //return ReturnValue; } static function Vector GetProgressPositionCurved(float Progress, mcu8_TrainPoint CurPoint) { local Vector PP, P, N, nn; if((CurPoint == none) || CurPoint.Next == none) { return vect(0.0, 0.0, 0.0); } if((CurPoint.Prev == none) || !CurPoint.Next.Teleport && CurPoint.Next.Next == none) { return VLerp(CurPoint.Location, CurPoint.Next.Location, Progress); } PP = ((CurPoint.Prev.Teleport) ? CurPoint.Location + (Normal(CurPoint.Location - CurPoint.Next.Location) * float(100)) : CurPoint.Prev.Location); P = CurPoint.Location; N = CurPoint.Next.Location; nn = ((CurPoint.Next.Teleport) ? CurPoint.Next.Location + (Normal(CurPoint.Next.Location - CurPoint.Location) * float(100)) : CurPoint.Next.Next.Location); return class'Hat_TrainPoint_Base'.static.ArgleCubicSpline(PP, P, N, nn, Progress); //return ReturnValue; } function bool MovementTick(float D) { CurrentSpeed = AdjustedSpeed; Teleporting = false; return MoveTrain(CurrentSpeed * D); //return ReturnValue; } function bool MoveTrain(float Dist) { local Vector Offset, Target; local Rotator oldRot, NewRot; if(Dist == float(0)) { return false; } if(IsFront) { AddSubPointDistanceBase(StartPoint.TrackMaster.SubPoints, Dist, InstantTeleport, false); } else { if(FollowFrontDirectly) { CurrentSubPoint = FrontTrain.CurrentSubPoint; NextSubPoint = FrontTrain.NextSubPoint; CurrentProgress = FrontTrain.CurrentProgress; AddSubPointDistanceBase(StartPoint.TrackMaster.SubPoints, -FrontDist, InstantTeleport, true); } else { CurrentSubPoint = Following.CurrentSubPoint; NextSubPoint = FrontTrain.NextSubPoint; CurrentProgress = Following.CurrentProgress; AddSubPointDistanceBase(StartPoint.TrackMaster.SubPoints, ((CarLength * 0.50) + (Following.CarLength * 0.50)) * float(-1), InstantTeleport, true); } } Target = VLerp(StartPoint.TrackMaster.SubPoints[CurrentSubPoint].Location, StartPoint.TrackMaster.SubPoints[NextSubPoint].Location, CurrentProgress); if(!FollowFrontDirectly && !IsFront) { Target = (Following.Location - (vect(0.0, 0.0, 1.0) * Following.CarHeight)) + (Normal(Target - (Following.Location - (vect(0.0, 0.0, 1.0) * Following.CarHeight))) * ((CarLength * 0.50) + (Following.CarLength * 0.50))); } Offset = (Target + (vect(0.0, 0.0, 1.0) * CarHeight)) - Location; if(VSizeSq(Offset) == float(0)) { return false; } Move(Offset); if(!OptimizeStraightTrack) { oldRot = Rotation; NewRot = class'Hat_Math'.static.RLerpShortest(StartPoint.TrackMaster.SubPoints[CurrentSubPoint].Rotation, StartPoint.TrackMaster.SubPoints[NextSubPoint].Rotation, CurrentProgress); if(oldRot != NewRot) { SetRotation(NewRot); if((OrientPlayerCamera && FrontTrain != none) && FrontTrain.PlayersBasedOnTrain.Length > 0) { TurnBasedPlayerCamera(Rotation - oldRot); } } } return true; //return ReturnValue; } defaultproperties { Begin Object Class=Hat_DrawCatapultComponent Name=DrawCatapultComponent0 PropertyName_CatapultTargetActor = "StartPoint" LineWidth = 15 SphereColor=(R=255,G=255,B=0,A=255) bSelectable=false MaxDrawDistance = 4000; End Object Components.Add(DrawCatapultComponent0) StartLineComponent=DrawCatapultComponent0 Begin Object Class=Hat_DrawCatapultComponent Name=DrawCatapultComponent1 PropertyName_CatapultTargetActor = "Following" LineWidth = 30 SphereColor=(R=255,G=0,B=128,A=255) bSelectable=false MaxDrawDistance = 4000; End Object Components.Add(DrawCatapultComponent1) FollowLineComponent=DrawCatapultComponent1 Begin Object Name=MyLightEnvironment bEnabled = true bDynamic = true bIsCharacterLightEnvironment = false bSynthesizeSHLight = false bSynthesizeDirectionalLight = false AmbientGlow = (R=0.2f,G=0.2f,B=0.2f,A=1.0f); End Object Begin Object Name=StaticMeshComponent0 StaticMesh=StaticMesh'HatInTime_Levels_Murder_V.models.LoungeTrolley' CanBlockCamera=true bUsePrecomputedShadows=false LightEnvironment=MyLightEnvironment MotionBlurInstanceScale=0 MaxDrawDistance = 14000 End Object Begin Object Class=CameraShake Name=Shake0 OscillationDuration=-1 RotOscillation={(Pitch=(Amplitude=26.f,Frequency=80.f), Yaw=(Amplitude=12.f,Frequency=60.f), Roll=(Amplitude=26.f,Frequency=120.f))} End Object CamShake=Shake0 Begin Object Class=CameraShake Name=Shake1 OscillationDuration=-1 RotOscillation={(Pitch=(Amplitude=120.f,Frequency=40.f), Yaw=(Amplitude=50.f,Frequency=30.f), Roll=(Amplitude=120.f,Frequency=60.f))} End Object WarningCamShake=Shake1 bNoDelete = false bCollideWorld = false TickOptimize = TickOptimize_View TickOptimize_TimingSensitive = true bNoEncroachCheck = false bEncroachPlayersOnly = true bStopOnEncroach = false Physics = PHYS_Interpolating bBlockActors = true bCollideActors = true bPlayerShouldStayBasedAfterJumping = true; IsPlayerNearby = true // Defaults to true so full logic in SetIsPlayerNearby() will run at least once. Should get turned off on first frame Speed = 450 CarLength = 150 UseShakeCamera = false WarningCameraShakeLead = 350 CameraShakeFalloff = 200 WarningShakeFalloff = 900 TrainHornLead = 400 TrainHornSize = 300 VelocityTransitionTime = 0.75f; VelocityTransitionPercentage = 0.5f; IsActorEnemy=true // This is to freeze trains during cinematics HiddenCameraGroups.Add("Hat_Hazard_Train"); TurnCameraEvenIfPreviousBaseWasTrain = true; ScriptShouldIgnoreBlockingBy = true; NextSubPoint = -1; SpeedMultiplier = 1; }
// modified Hat_TrainPoint to work with the custom Hat_Hazard_Train class mcu8_TrainPoint extends Hat_TrainPoint_Base placeable; // The point the train will move to next after reaching this point. var() mcu8_TrainPoint Next<DisplayName=Next Point>; // Train will increase/decrease to this speed when approaching this point, starting from the previous point. Set this to 0 if you want the train to come to a gradual stop. var(Debug) float SpeedMultiplier<UIMin=0.0>; // If enabled, train will teleport directly to the next point on departure from this point. This will be enabled automatically on routes that are dead-ends. var() bool Teleport; // Train will stop at this point and wait until this amount of seconds has elapsed. Leave at 0 for no stop. var() float WaitTime<UIMin=0.0>; enum Hat_TrackColor { Blue, Green, Yellow, Purple, }; var Array<MaterialInterface> HoloMaterials; var() Hat_TrackColor GeneratedTrackColor<DisplayName=Train Color>; // Train will sync up with other trains at this point. var() Array<mcu8_TrainPoint> WaitForSync<DisplayName=Sync Track Points>; // Will attempt to skip this many wait cycles... Just increase this value if trains on this track are sitting idle for too long at a teleport waiting for syncs. var() int SkipSyncWaitCycles; /*var() bool EnableTickOptimize;*/ // Hidden list of points in the opposite direction, populated automatically. var mcu8_TrainPoint Prev; // Editor-only var Array<mcu8_TrainPoint> WholeTrack; var mcu8_TrainPoint TrackMaster; var mcu8_TrainPoint EditorLineNext; var Texture2D TrackMasterIcon; var(Debug) Array<Hat_TrainPoint_SubPoint> SubPoints; var Hat_DrawCatapultComponent NextLineComponent; var Hat_DrawCatapultComponent SyncLineComponent; // Adjusted speed calculated to keep syncs var(Debug) float SyncSpeedMultiplier; // Extra time calculated to keep syncs var(Debug) float SyncTeleportWaitTime; // Position offset at start to line up syncs var(Debug) float SyncStartOffset; var(Debug) TrainPointCalcState EditorTimeTrackCalculationState; // Entire physical track length var(Debug) float WholeTrackLength; // Track "length" including teleport time and sync adjustments var(Debug) float FinalTrackLength; // Physical length of this track segment var(Debug) float SegmentLength; // Whether the track is mostly in a straight line or not var(Debug) bool TrackIsStraight; var(Debug) transient Array<mcu8_TrainPoint> SyncedMasterListCache; enum TrainPointCalcState { TrainPointCalcState_None, TrainPointCalcState_TrackLength, TrainPointCalcState_Complete, }; function float GetWholeTrackLength(optional bool final) { if (TrackMaster != None) return (final && TrackMaster.FinalTrackLength > 0) ? TrackMaster.FinalTrackLength : TrackMaster.WholeTrackLength; return 0; } function DirtyGeneratedTrackMesh() { local mcu8_TrainPoint tp; foreach WholeTrack(tp) tp.GeneratedTrackMeshDirty = true; } event editoronly OnEditorPropertyChanged(Name PropertyName) { local mcu8_TrainPoint tp; if (PropertyName == 'Next') Teleport = false; if (PropertyName == 'Next' || PropertyName == 'Teleport') ConnectTrackPoints(); if (PropertyName == 'Next' || PropertyName == 'Teleport' || PropertyName == 'WaitTime') { PerformEditorTimeTrackCalculation(); DrawEditorGuide(); } if (PropertyName == 'WaitForSync') { UpdateMySyncPoints(true); CalculateSync(); } // Track mesh generation if (PropertyName == 'GenerateTrack') foreach WholeTrack(tp) tp.GenerateTrack = GenerateTrack; if (PropertyName == 'GeneratedTrackColor') { foreach WholeTrack(tp) { tp.GeneratedTrackColor = GeneratedTrackColor; tp.GeneratedTrackMeshHoloMaterial = HoloMaterials[GeneratedTrackColor]; // color all the lines too switch (tp.GeneratedTrackColor) { case Green: tp.NextLineComponent.SphereColor = MakeColor(0,255,0); break; case Yellow: tp.NextLineComponent.SphereColor = MakeColor(255,150,0); break; case Purple: tp.NextLineComponent.SphereColor = MakeColor(150,0,255); break; default: tp.NextLineComponent.SphereColor = MakeColor(0,150,255); break; } tp.ForceUpdateComponents(false, false); } } if (PropertyName == 'GenerateTrack' || PropertyName == 'GenerateSegment' || PropertyName == 'Next' || PropertyName == 'Teleport' || PropertyName == 'GeneratedTrackColor') DirtyGeneratedTrackMesh(); if (PropertyName == 'SkipSyncWaitCycles') { foreach WholeTrack(tp) tp.SkipSyncWaitCycles = SkipSyncWaitCycles; // sync calculations changed, so recalculate them PerformEditorTimeTrackCalculation(); } if (PropertyName == 'GenerateTrack' || PropertyName == 'GenerateSegment') PerformEditorTimeTrackCalculation();// this changes a bool in subpoints, so recalculate } event editoronly OnEditorMoved() { ConnectTrackPoints(); if (Next != None && Prev != None) PerformEditorTimeTrackCalculation(); DrawEditorGuide(); DirtyGeneratedTrackMesh(); } function expensive editoronly ConnectTrackPoints() { local mcu8_TrainPoint tp; local Array<mcu8_TrainPoint> CheckPoints; CheckPoints = GetRelevantPoints(); foreach CheckPoints(tp) tp.Prev = None; foreach CheckPoints(tp) if (tp.Next != None) tp.Next.Prev = tp; foreach CheckPoints(tp) tp.FixDuplicateNext(CheckPoints); foreach CheckPoints(tp) tp.ConnectDeadEnd(); } function editoronly FixAllBrokenPoints() { local mcu8_TrainPoint tp; local int i; foreach AllActors(class'mcu8_TrainPoint', tp) { // wipe any None syncs for (i = 0; i < tp.WaitForSync.Length; i++) { if (tp.WaitForSync[i] == None) { tp.WaitForSync.Remove(i, 1); i--; } } if (tp.TrackMaster != None) tp.TrackMaster.ConditionalPerformEditorTimeTrackCalculation(); else tp.ConditionalPerformEditorTimeTrackCalculation(); } } // get all connected points, or points that should be connected (or shouldn't be connected, but are) function editoronly Array<mcu8_TrainPoint> GetRelevantPoints() { local mcu8_TrainPoint tp; local Array<mcu8_TrainPoint> r; if (Next != None) { Next.UpdateWholeTrackArray(); r = Next.WholeTrack; // add any previous points that might have been missed tp = Next; while (tp != None) { if (r.Find(tp) != INDEX_NONE) break; r.AddItem(tp); tp = tp.Prev; } } tp = self; while (tp != None) { if (r.Find(tp) != INDEX_NONE) break; r.AddItem(tp); tp = tp.Prev; } // anything with the same master too foreach AllActors(class'mcu8_TrainPoint', tp) if (tp.TrackMaster == TrackMaster) if (r.Find(tp) == INDEX_NONE) r.AddItem(tp); return r; } function editoronly UpdateWholeTrackArray() { local mcu8_TrainPoint tp; WholeTrack.Length = 0; tp = self; while (tp.Next != None) { if (WholeTrack.Length > 0 && tp == WholeTrack[0])// looped return; if (WholeTrack.Find(tp) != INDEX_NONE)// improper loop { WholeTrack.Length = 0; return; } WholeTrack.AddItem(tp); tp = tp.Next; } } function editoronly FixDuplicateNext(Array<mcu8_TrainPoint> CheckPoints) { local mcu8_TrainPoint tp; local Array<String> split; local int mynum; if (Next == None || Prev == None) return; split = SplitString(string(Name), "_", true); mynum = int(split[split.Length-1]); foreach CheckPoints(tp) { if (tp.Next == tp) tp.Next = None; if (tp.Next != Next || tp.Next == None || tp == self) continue; split = SplitString(string(tp.Name), "_", true); if (tp.Prev == None || mynum < int(split[split.Length-1])) { Next.Prev = tp; tp.Next = Next; tp.Prev = self; Next = tp; } } } function editoronly ConnectDeadEnd() { local mcu8_TrainPoint tp; local Array<mcu8_TrainPoint> checked; if (Prev != None) return; tp = self; while (tp.Next != None) { tp = tp.Next; if (tp == self) return;// looped! if (checked.Find(tp) != INDEX_NONE) return;// weird loop! checked.AddItem(tp); } if (tp == self) return;// not connected to anything! Prev = tp; tp.Teleport = true; tp.Next = self; } function editoronly UpdateMySyncPoints(bool remove) { local mcu8_TrainPoint tp; local Array<mcu8_TrainPoint> ChangedPoints; local int i; if (TrackMaster != None) ChangedPoints.AddItem(TrackMaster); foreach AllActors(class'mcu8_TrainPoint', tp) { // wipe any None syncs for (i = 0; i < tp.WaitForSync.Length; i++) { if (tp.WaitForSync[i] == None) { tp.WaitForSync.Remove(i, 1); i--; } } if (WaitForSync.Find(tp) == INDEX_NONE) { if (remove) { tp.WaitForSync.RemoveItem(self); tp.ForceUpdateComponents(false, false); if (tp.TrackMaster != None) ChangedPoints.AddItem(tp.TrackMaster); } } else if (tp.WaitForSync.Find(self) == INDEX_NONE) { tp.WaitForSync.AddItem(self); tp.ForceUpdateComponents(false, false); if (tp.TrackMaster != None) ChangedPoints.AddItem(tp.TrackMaster); } } foreach ChangedPoints(tp) tp.BuildAIPathsHook(); } event editoronly name GetCustomTickOptimizationGroup() { local Array<mcu8_TrainPoint> TrackMasters; local name BestName; local int i, BestNameNumber, Number; if (TrackMaster == None) return ''; if (TrackMaster != self) return TrackMaster.GetCustomTickOptimizationGroup(); GetSyncedTrackMasterList(TrackMasters); // Pick the lowest of them. for (i = 0; i < TrackMasters.Length; i++) { Number = int(GetRightMost(String(TrackMasters[i].Name))); if (i == 0 || Number < BestNameNumber) { BestName = TrackMasters[i].Name; BestNameNumber = Number; } } return BestName; } event editoronly CheckForErrors(out Array<string> ErrorMessages) { Super.CheckForErrors(ErrorMessages); if (!Teleport && WaitTime > 0) ErrorMessages.AddItem("WaitTime only supported for teleport."); if (TrackMaster != None && TrackMaster == self) { if (EditorTimeTrackCalculationState != TrainPointCalcState_Complete) ErrorMessages.AddItem("Track calculations incomplete. Move a track point!"); else if (SubPoints.Length == 0) ErrorMessages.AddItem("Track has no subpoints. Move a track point!"); } } function float GetCombinedWaitTime() { return WaitTime + SyncTeleportWaitTime; } event editoronly GetCustomEditorCommands(out Array<ActorCustomEditorCommand> Result) { local ActorCustomEditorCommand Item; if (Next != None && Prev != None) { Item.Text = "Reverse Track"; Item.Command = 'ReverseTrack'; Result.AddItem(Item); } if (Next != None && Prev != None) { Item.Text = "Split Track"; Item.Command = 'SplitTrack'; Result.AddItem(Item); } Item.Text = "Recalculate All Tracks"; Item.Command = 'RecalculateAll'; Item.bOnlyCallOnFirstSelected = true; Result.AddItem(Item); Item.Text = "Update All Optimization Groups"; Item.Command = 'UpdateOptimization'; Item.bOnlyCallOnFirstSelected = true; Result.AddItem(Item); Item.Text = "Generate Meshes"; Item.Command = 'GenerateMeshes'; Result.AddItem(Item); Item.Text = "Generate All Track Meshes"; Item.Command = 'GenerateAllTrackMeshes'; Result.AddItem(Item); } event editoronly OnEditorCustomCommand(Name Command, int Index) { if (Command == 'ReverseTrack') ReverseWholeTrack(); if (Command == 'SplitTrack') SplitTrackFromPoint(); if (Command == 'RecalculateAll') ForceRecalculateAll(); if (Command == 'UpdateOptimization') class'Hat_NPCManager'.static.ComputeTickOptimizationGroups(); if (Command == 'GenerateMeshes') GenerateMeshes(); if (Command == 'GenerateAllTrackMeshes') GenerateAllTrackMeshes(true); } function editoronly ReverseWholeTrack() { local mcu8_TrainPoint tp, curnext; local bool FirstTeleport; UpdateWholeTrackArray(); if (WholeTrack.Length <= 0) { `broadcast("Whole track not calculated"); ScriptTrace(); return; } foreach WholeTrack(tp) { curnext = tp.Next; tp.Next = tp.Prev; tp.Prev = curnext; } tp = Next; FirstTeleport = tp.Teleport; while (tp != self) { tp.Teleport = tp.Next == self ? FirstTeleport : tp.Next.Teleport; tp = tp.Next; } PerformEditorTimeTrackCalculation(); DrawEditorGuide(); } function editoronly SplitTrackFromPoint() { local mcu8_TrainPoint tp, NewPoint, FirstTeleport, FirstTeleportDest; UpdateWholeTrackArray(); if (WholeTrack.Length <= 0) { `broadcast("Whole track not calculated"); ScriptTrace(); return; } // find first teleport tp = Next; while (tp != self) { if (tp.Teleport) { FirstTeleport = tp; FirstTeleportDest = tp.Next; break; } tp = tp.Next; } if (FirstTeleport == None) { // no teleport, so just make one here FirstTeleport = Prev; FirstTeleportDest = self; } // copy this point NewPoint = Spawn(class,,, Location, Rotation); NewPoint.Prev = Prev; NewPoint.Prev.Next = NewPoint; Prev = FirstTeleport; Prev.Next = self; NewPoint.Next = FirstTeleportDest; NewPoint.Next.Prev = NewPoint; NewPoint.Teleport = true; NewPoint.GeneratedTrackColor = GeneratedTrackColor; // update track UpdateWholeTrackArray(); NewPoint.UpdateWholeTrackArray(); PerformEditorTimeTrackCalculation(); NewPoint.PerformEditorTimeTrackCalculation(); DrawEditorGuide(); } function editoronly GenerateMeshes() { local mcu8_TrainPoint tp; foreach WholeTrack(tp) tp.GenerateTrackMesh(true); } event editoronly Array<Hat_TrainPoint_Base> GetWholeTrack() { return WholeTrack; } event editoronly Vector CalcTrackCurve(float Alpha) { return class'mcu8_Hazard_Train'.static.GetProgressPositionCurved(Alpha, self); } event editoronly bool IsTeleport() { return Teleport; } event editoronly bool ShouldHaveTrackMesh() { return TrackMaster == self; } function expensive int GetMySubPoint(optional Array<Hat_TrainPoint_SubPoint> SubPointList) { local int i; if (SubPointList.Length == 0 && TrackMaster != None && TrackMaster.SubPoints.Length != 0) SubPointList = TrackMaster.SubPoints; for (i = 0; i < SubPointList.Length; i++) if (VSizeSq(SubPointList[i].Location - Location) < 1) return i; return 0; } function editoronly Hat_TrainPoint_SubPoint GetSubPoint(int i, Array<Hat_TrainPoint_SubPoint> SubPointList) { return SubPointList[(i + SubPointList.Length) % SubPointList.Length]; } function editoronly int GetSubPointIndex(int i, Array<Hat_TrainPoint_SubPoint> SubPointList) { return (i + SubPointList.Length) % SubPointList.Length; } function editoronly ClearEditorTimeCalculations() { SyncSpeedMultiplier = default.SyncSpeedMultiplier; SyncTeleportWaitTime = default.SyncTeleportWaitTime; EditorTimeTrackCalculationState = TrainPointCalcState_None; SubPoints.Length = 0; WholeTrackLength = 0; FinalTrackLength = 0; SyncStartOffset = 0; SyncTeleportWaitTime = 0; SyncedMasterListCache.Length = 0; } function editoronly ConditionalPerformEditorTimeTrackCalculation() { if (EditorTimeTrackCalculationState == TrainPointCalcState_Complete) return; PerformEditorTimeTrackCalculation(); } function editoronly ForceRecalculateAll() { local mcu8_TrainPoint tp; // note: sync points must be updated in a separate pass that occurs before track calculations foreach AllActors(class'mcu8_TrainPoint', tp) tp.UpdateMySyncPoints(false); RunBuildAIPathsHooks(); } event editoronly BuildAIPathsHook() { //UpdateMySyncPoints(false); if (self == TrackMaster) PerformEditorTimeTrackCalculation(); } // Always does a full recalculation. If you only want to do a calculation if none exists, use ConditionalPerformEditorTimeTrackCalculation function editoronly PerformEditorTimeTrackCalculation(optional bool IncludePassB = true) { ClearEditorTimeCalculations(); // Let's determine the track master so we can call on him instead UpdateWholeTrackArray(); // Needed for DetermineTrackMasterForWholeTrack DetermineTrackMasterForWholeTrack(); if (TrackMaster == None) return; if (TrackMaster != self) { ClearEditorTimeCalculations(); // Clear our data since we're not the trackmaster TrackMaster.PerformEditorTimeTrackCalculation(IncludePassB); return; } PerformEditorTimeTrackCalculation_PassA(); // Calculate the track length if (IncludePassB) PerformEditorTimeTrackCalculation_PassB(); // Place the actual subpoints based on track length (& synced track's length) } function editoronly PerformEditorTimeTrackCalculation_PassA() { local mcu8_TrainPoint tp; if (TrackMaster != self) { // We're not the track master, so we shouldn't be performing the calculations. ScriptTrace(); return; } if (EditorTimeTrackCalculationState != TrainPointCalcState_None) return; UpdateWholeTrackArray(); // Clean slate foreach WholeTrack(tp) { if (tp == self) continue; tp.ClearEditorTimeCalculations(); } TrackIsStraight = IsTrackStraight(); // Calculate the whole track length CalculateWholeTrackLength(); EditorTimeTrackCalculationState = TrainPointCalcState_TrackLength; } function editoronly PerformEditorTimeTrackCalculation_PassB() { if (TrackMaster != self) { // We're not the track master, so we shouldn't be performing the calculations. ScriptTrace(); return; } // Requires PassA to have been completed if (EditorTimeTrackCalculationState != TrainPointCalcState_TrackLength) return; // Make adjustments to track for train syncing CalculateSync(); // Generate new subpoints based on the sync info PopulateSubPoints(SubPoints, true); // Get final track length (used for placing train duplicates) FinalTrackLength = CalculateSubPointTotalLength(SubPoints); EditorTimeTrackCalculationState = TrainPointCalcState_Complete; } function editoronly CalculateWholeTrackLength() { local mcu8_TrainPoint tp; local Array<Hat_TrainPoint_SubPoint> SubPointList; PopulateSubPoints(SubPointList, false); WholeTrackLength = CalculateSubPointTotalLength(SubPointList); foreach WholeTrack(tp) tp.CalculateSegmentDistance(SubPointList); } function editoronly float CalculateSubPointTotalLength(Array<Hat_TrainPoint_SubPoint> SubPointList) { local int i; local float Result; Result = 0; for (i = 0; i < SubPointList.Length; i++) Result += FMax(0, SubPointList[i].Distance); return Result; } function editoronly CalculateSegmentDistance(Array<Hat_TrainPoint_SubPoint> SubPointList) { local int i, n; if (SubPointList.Length == 0 && TrackMaster != None) SubPointList = TrackMaster.SubPoints; if (SubPointList.Length == 0) return; i = GetMySubPoint(SubPointList); n = Next.GetMySubPoint(SubPointList); SegmentLength = 0; while (i != n) { SegmentLength += FMax(0, SubPointList[i].Distance); i = GetSubPointIndex(i+1,SubPointList); } } function editoronly float GetSegmentDistance() { if (SegmentLength == 0) CalculateSegmentDistance(TrackMaster.SubPoints); return SegmentLength; } // We do 2 passes. First pass is just to get total track length, 2nd pass is the final subpoints for train movement function editoronly PopulateSubPoints(out Array<Hat_TrainPoint_SubPoint> SubPointList, optional bool FinalPass) { local int i; local mcu8_TrainPoint tp; local float density; local Vector midpoint; if (TrackMaster == None || TrackMaster != self) { // We're not the track master, so we shouldn't be performing the calculations. ScriptTrace(); return; } if (WholeTrack.Length <= 0) { // Whole track should be calculated at this point ScriptTrace(); return; } foreach WholeTrack(tp) { if (tp.Teleport) { AddSubPoint(SubPointList, tp.Location, FinalPass ? tp.GetCombinedWaitTime() : tp.WaitTime, true, tp.GenerateTrack && tp.GenerateSegment, FinalPass); } else if (TrackIsStraight) { AddSubPoint(SubPointList, tp.Location, tp.WaitTime, false, tp.GenerateTrack && tp.GenerateSegment, FinalPass); } else { midpoint = class'mcu8_Hazard_Train'.static.GetProgressPositionCurved(0.5, tp); density = VSize(Normal(midpoint-tp.Location) - Normal(tp.Next.Location-midpoint))*1.5; density *= VSize(tp.Location - tp.Next.Location)/GetConnectedTrainSpeed(false)*2; density = FCeil(Lerp(1,30,FClamp(density,0,1))); for (i = 0; i < density; i++) AddSubPoint(SubPointList, class'mcu8_Hazard_Train'.static.GetProgressPositionCurved(i/density, tp), i == 0 ? tp.WaitTime : 0.0, false, tp.GenerateTrack && tp.GenerateSegment, FinalPass); } } UpdateSubPointInfo(SubPointList); } function editoronly bool IsTrackStraight() { local mcu8_TrainPoint tp; local Vector midpoint; if (TrackMaster != None && TrackMaster != self) return TrackMaster.IsTrackStraight(); // only two points, so it's straight if (Next.Next == self) return true; // check if track has any curves in it foreach WholeTrack(tp) { if (tp.Teleport) continue; midpoint = class'mcu8_Hazard_Train'.static.GetProgressPositionCurved(0.5, tp); if (VSize(Normal(midpoint-tp.Location) - Normal(tp.Next.Location-midpoint)) >= 0.012) return false; } return true; } // Calculates distances and rotation, it was implemented this way to support wrap around for [length-1] function editoronly UpdateSubPointInfo(out Array<Hat_TrainPoint_SubPoint> SubPointList) { local int i; local Vector v; local bool PrevWasWaitTime; for (i = 0; i < SubPointList.Length; i++) { if (SubPointList[i].Distance == 0) SubPointList[i].Distance = VSize(GetSubPoint(i+1,SubPointList).Location - SubPointList[i].Location); if (SubPointList[i].bIsWaitTime) continue;// skip over wait time when averaging rotations, since no movement occurs PrevWasWaitTime = i > 0 && SubPointList[i-1].bIsWaitTime; v = vect(0,0,0); if (SubPointList[i].Distance != -1) v += Normal(GetSubPoint(i+1,SubPointList).Location - SubPointList[i].Location); if (GetSubPoint(PrevWasWaitTime ? i-2 : i-1,SubPointList).Distance != -1) v += Normal(SubPointList[i].Location - GetSubPoint(PrevWasWaitTime ? i-2 : i-1,SubPointList).Location); SubPointList[i].Rotation = Rotator(v); if (PrevWasWaitTime) SubPointList[i-1].Rotation = Rotator(v); } for (i = 0; i < SubPointList.Length; i++) if (SubPointList[i].bIsWaitTime) SubPointList[i].Rotation = GetSubPoint(i+1,SubPointList).Rotation; } function editoronly AddSubPoint(out Array<Hat_TrainPoint_SubPoint> SubPointList, vector loc, float wait, bool IsTeleport, bool GenTrack, bool FinalPass) { local Hat_TrainPoint_SubPoint sub; sub.Location = loc; sub.bHasGeneratedTrack = GenTrack; if (wait > 0) { sub.Distance = GetConnectedTrainSpeed(FinalPass)*wait;// train does wait time by traveling to this same location for the duration of the wait sub.bIsWaitTime = true; } else if (IsTeleport) sub.Distance = -1;// note: 0 distance isn't a teleport. -1 tells tracks not to curve and cars not to teleport backwards. SubPointList.AddItem(sub); if (wait > 0) AddSubPoint(SubPointList, loc, 0, IsTeleport, GenTrack, FinalPass); } function editoronly DrawEditorGuide() { local int i; local mcu8_TrainPoint tp; FlushPersistentDebugLines(); foreach WholeTrack(tp) { tp.EditorLineNext = None; tp.EditorLineNext = tp.Teleport ? None : tp.Next; tp.ForceUpdateComponents(false, false); if (tp.GetCombinedWaitTime() > 0) DrawDebugSphere(tp.Location, 70, 2, 0, 255, 50, true); for (i = 0; i < tp.SubPoints.Length; i++) { if (tp.SubPoints[i].Distance == -1) { DrawDebugLine(tp.SubPoints[i+1 < tp.SubPoints.Length ? i+1 : 0].Location, tp.SubPoints[i].Location, 100, 0, 255, true); DrawDebugSphere(tp.SubPoints[i].Location, 80, 2, 150, 0, 255, true); } else { DrawDebugLine(tp.SubPoints[i+1 < tp.SubPoints.Length ? i+1 : 0].Location, tp.SubPoints[i].Location, 255, 160, 0, true); DrawDebugSphere(tp.SubPoints[i].Location, 15, 2, 255, 210, 0, true); } } } } function editoronly mcu8_Hazard_Train GetConnectedTrain() { local mcu8_Hazard_Train ct; foreach DynamicActors(class'mcu8_Hazard_Train', ct) { if (ct.StartPoint == None) continue; if (ct.StartPoint.TrackMaster == None) continue; if (ct.StartPoint.TrackMaster.WholeTrack.Length <= 0) { // Whole track should be calculated at this point. If it isn't, it's not relevant continue; } // This point isn't associated with the train if (ct.StartPoint.TrackMaster.WholeTrack.Find(self) == INDEX_NONE) continue; return ct; } return None; } function editoronly DetermineTrackMasterForWholeTrack() { local mcu8_Hazard_Train ct; local mcu8_TrainPoint tp; ct = GetConnectedTrain(); if (ct != None) { foreach ct.StartPoint.WholeTrack(tp) tp.SetTrackMaster(ct.StartPoint); } else // if there's no train, just use the last accessed point { foreach WholeTrack(tp) tp.SetTrackMaster(self); } } function editoronly float GetConnectedTrainSpeed(bool WithSyncMultiplier) { local mcu8_Hazard_Train train; if (TrackMaster == None) return class'mcu8_Hazard_Train'.default.Speed; train = GetConnectedTrain(); if (train != None) return train.Speed * (WithSyncMultiplier ? TrackMaster.SyncSpeedMultiplier : 1.f); return class'mcu8_Hazard_Train'.default.Speed; } // What's the purpuose of this??? // function editoronly int GetConnectedDuplicates() // { // local mcu8_Hazard_Train train; // if (TrackMaster == None) return 1; // train = GetConnectedTrain(); // return 1; // } function editoronly SetTrackMaster(mcu8_TrainPoint tp) { local SpriteComponent sc; TrackMaster = tp; foreach ComponentList(class'SpriteComponent', sc) { if (sc == None) continue; sc.SetSprite(tp == self ? TrackMasterIcon : sc.default.Sprite); } } function editoronly mcu8_TrainPoint GetTeleportPointInTrack() { local mcu8_TrainPoint tp; if (TrackMaster != self) return TrackMaster.GetTeleportPointInTrack(); if (WholeTrack.Length <= 0) { `broadcast("Whole track not calculated"); ScriptTrace(); return None; } foreach WholeTrack(tp) { if (tp.Teleport) return tp; } return None; } function editoronly GetSyncedTrackMasterList(out Array<mcu8_TrainPoint> TrackMasters) { local mcu8_TrainPoint tp; local int i; if (TrackMaster == None || TrackMaster.EditorTimeTrackCalculationState == TrainPointCalcState_None) PerformEditorTimeTrackCalculation(false); if (TrackMaster != self) { TrackMaster.GetSyncedTrackMasterList(TrackMasters); return; } if (SyncedMasterListCache.Length > 0) { TrackMasters = SyncedMasterListCache; return; } if (TrackMasters.Find(self) != INDEX_NONE) return;// found a sync loop, exiting TrackMasters.AddItem(self); foreach WholeTrack(tp) { for (i = 0; i < tp.WaitForSync.Length; i++) { if (tp.WaitForSync[i] == None) continue; tp.WaitForSync[i].GetSyncedTrackMasterList(TrackMasters); } } } function editoronly CalculateSync() { local Array<mcu8_TrainPoint> TrackMasters; local mcu8_TrainPoint tp, TeleportPoint, OffsetMaster; local int i; local float LongestTrackTime, TrainSpeed, MyTrackTime, HighestStartWait, SyncOffsetOffset; local mcu8_Hazard_Train train; // Get all track masters in our connection chain GetSyncedTrackMasterList(TrackMasters); SyncedMasterListCache = TrackMasters; for (i = 0; i < TrackMasters.Length; i++) { // verify track is computed properly if (TrackMasters[i].EditorTimeTrackCalculationState == TrainPointCalcState_None) TrackMasters[i].PerformEditorTimeTrackCalculation_PassA(); if (TrackMasters[i].WholeTrackLength <= 0) { // Whole track length should be calculated at this point ScriptTrace(); continue; } // get longest track time TrainSpeed = TrackMasters[i].GetConnectedTrainSpeed(false); MyTrackTime = TrackMasters[i].WholeTrackLength / TrainSpeed; LongestTrackTime = FMax(LongestTrackTime, MyTrackTime); // wipe sync offsets TrackMasters[i].SyncStartOffset = 0; } for (i = 0; i < TrackMasters.Length; i++) { TrainSpeed = TrackMasters[i].GetConnectedTrainSpeed(false); MyTrackTime = TrackMasters[i].WholeTrackLength / TrainSpeed; TeleportPoint = TrackMasters[i].GetTeleportPointInTrack(); // Reset existing calculations foreach TrackMasters[i].WholeTrack(tp) { tp.SyncSpeedMultiplier = 1; tp.SyncTeleportWaitTime = 0; } if (TeleportPoint != None) { TeleportPoint.SyncTeleportWaitTime = LongestTrackTime - MyTrackTime; if (TrackMasters[i].SkipSyncWaitCycles > 0 && LongestTrackTime >= MyTrackTime*(TrackMasters[i].SkipSyncWaitCycles+1)) TeleportPoint.SyncTeleportWaitTime = LongestTrackTime/float(TrackMasters[i].SkipSyncWaitCycles+1) - MyTrackTime; } else TrackMasters[i].SyncSpeedMultiplier = MyTrackTime / LongestTrackTime; CalculateSyncStartOffset(TrackMasters[i]); } // pick an offset to offset all the offsets by (so at least one offset is zero), so the trains don't have to move so much at the start of the game OffsetMaster = self; for (i = 0; i < TrackMasters.Length; i++) { train = TrackMasters[i].GetConnectedTrain(); // get the train with the smallest sync offset if (HighestStartWait == 0 && TrackMasters[i].SyncStartOffset < OffsetMaster.SyncStartOffset) OffsetMaster = TrackMasters[i]; // use train with highest start wait, since the timing there is probably important if (train.GetAdjustedSpeed()*train.StartWait > HighestStartWait) { OffsetMaster = TrackMasters[i]; HighestStartWait = train.GetAdjustedSpeed()*train.StartWait; } } // offset all trains by highest start offset (relative to the speed of the OffsetMaster train) SyncOffsetOffset = OffsetMaster.SyncStartOffset; for (i = 0; i < TrackMasters.Length; i++) TrackMasters[i].SyncStartOffset -= SyncOffsetOffset * (TrackMasters[i].GetConnectedTrainSpeed(true)/OffsetMaster.GetConnectedTrainSpeed(true)); } function editoronly CalculateSyncStartOffset(mcu8_TrainPoint master) { local mcu8_TrainPoint tp, tpp; local float dist, FirstSyncDist; local mcu8_Hazard_Train train; local Array<mcu8_TrainPoint> checked; train = master.GetConnectedTrain(); tp = train.StartPoint;// doesn't matter which train FirstSyncDist = -1; while (tp.Next != None) { if (tp.WaitForSync.Length > 0)// we hit a sync point { if (FirstSyncDist == -1) { // first sync point, move the train to it master.SyncStartOffset += dist; FirstSyncDist = dist; } else { // any sync point past the first, offset all other connected trains backwards recursively to line up checked.Length = 0; checked.AddItem(master); foreach tp.WaitForSync(tpp) RecursiveSyncOffset(tpp.TrackMaster, FirstSyncDist - dist, checked); } } dist += tp.GetSegmentDistance(); tp = tp.Next; if (tp == train.StartPoint) break; } } function editoronly RecursiveSyncOffset(mcu8_TrainPoint master, float dist, out Array<mcu8_TrainPoint> checked) { local mcu8_TrainPoint tp, tpp; master.SyncStartOffset += dist; checked.AddItem(master); foreach master.WholeTrack(tp) foreach tp.WaitForSync(tpp) if (checked.Find(tpp.TrackMaster) == INDEX_NONE) RecursiveSyncOffset(tpp.TrackMaster, dist, checked); } defaultproperties { Begin Object Name=Sprite Sprite=Texture2D'HatInTime_Editor.Textures.traintrack2' Scale=0.2 HiddenGame=true AlwaysLoadOnClient=false AlwaysLoadOnServer=false SpriteCategoryName="TargetPoint" MaxDrawDistance = 6000; End Object Begin Object Class=Hat_DrawCatapultComponent Name=DrawCatapultComponent0 PropertyName_CatapultTargetActor = "EditorLineNext" LineWidth = 30 SphereColor=(R=0,G=255,B=255,A=255) bSelectable=false HiddenGame=true MaxDrawDistance = 4000; End Object Components.Add(DrawCatapultComponent0) NextLineComponent=DrawCatapultComponent0 Begin Object Class=Hat_DrawCatapultComponent Name=DrawCatapultComponent1 PropertyName_CatapultTargetActor = "WaitForSync" LineWidth = 15 SphereColor=(R=255,G=0,B=255,A=255) bSelectable=false HiddenGame=true MaxDrawDistance = 4000; End Object Components.Add(DrawCatapultComponent1) SyncLineComponent=DrawCatapultComponent1 bStatic = true bNoDelete = true bMovable = false bHidden = false SpeedMultiplier = 1 TrackMasterIcon = Texture2D'HatInTime_Editor.Textures.TrainTrackicon' /*EnableTickOptimize = true*/ // note: train points don't actually tick, but shares an optimization group with trains to keep them running when out of sight TickOptimize = TickOptimize_View TickOptimize_TimingSensitive = true SingleTrackMesh = StaticMesh'HatinTime_Metro_Generation.models.metro_hologram_rail' TrackOverlap = 18.4 HoloMaterials[0] = Material'HatInTime_Levels_Metro_J.Materials.tracks_holo' HoloMaterials[1] = MaterialInstanceConstant'HatInTime_Levels_Metro_J.Materials.tracks_holo_green' HoloMaterials[2] = MaterialInstanceConstant'HatInTime_Levels_Metro_J.Materials.tracks_holo_yellow' HoloMaterials[3] = MaterialInstanceConstant'HatInTime_Levels_Metro_J.Materials.tracks_holo_purple' GeneratedTrackMeshHoloMaterial = Material'HatInTime_Levels_Metro_J.Materials.tracks_holo' SyncSpeedMultiplier = 1; }
/** * Jank by Starblaster64 and m_cu8 * This code is just a big trainwreck... (haha, get it? :hueh:) */ class sb64_Hazard_CatTrain extends mcu8_Hazard_Train; // train cat running v fast var(CatTrainNative) vector2d SpeedNodeMinMaxRate; var(CatTrainNative) Name AimNodeTrainBoneNameA; var(CatTrainNative) Name AimNodeTrainBoneNameB; // In Degrees!!! var(CatTrainNative) vector2d AimNodeMinMaxPitch; // In Degrees!!! var(CatTrainNative) vector2d AimNodeMinMaxYaw; // Name of TrainMesh bone that CatMesh aligns with. var(CatTrainNative) Name CatMeshTrainBoneName; var(CatTrainNative) vector CatMeshOffset; var(CatTrainNative) vector TrainMeshOffset; var(CatTrainNative) vector CatCollisionOffset; var(CatTrainNative) rotator CatCollisionRotationOffset; // Name of TrainMesh bone that TrainFront collision mesh aligns with. var(CatTrainNative) Name TrainFrontHitBoneName; var(CatTrainNative) vector TrainFrontHitOffset; var(CatTrainNative) rotator TrainFrontHitRotationOffset; // MCU8-ADD: from original Hat_Hazard_CatTrain const HeadlightDistanceThreshold = 2500.f; const HeadlightFadeTime = 0.7f; const DebugNeverOptimize = true; const DebugAlwaysShowCollisionBoxes = false; // Spawns additional cat trains with the same settings on the same track var(CatTrain) int SpawnDuplicates<UIMin=0>; // The skeletal mesh attached to this train. var(CatTrain) SkeletalMeshComponent TrainMesh; // Skeletal mesh of the cat that leads the train. var(CatTrain) SkeletalMeshComponent CatMesh; // If enabled, non-player objects may fall through trains, but performance will improve. var(CatTrain) bool OptimizeTrainCollisions; // Collision geometry of the cat itself. var StaticMeshComponent CatCollision; // Collision geometry of the front of the train. var StaticMeshComponent TrainFrontHit; // How many cars in this train. var int CarsInTrain<UIMin=0>; // How many cars (lengthwise) each "car" is made up of, allowing cars to "bend". Decrease this value to improve performance for trains that barely turn, or trains where you don't touch the sides or top. var int CarCollisionDensity<UIMin=1|UIMax=3>; // Size of the collision box for each car. Don't change this unless you change the mesh! var Vector CarCollisionSize; // Whether to render the collision boxes. var(Debug) bool DebugRenderCollisionBoxes<EditCondition=TrainMesh>; // How many "cars" long the cat is. This area is intangible aside from the cat hitbox. var float FrontClip; // How many "cars" should be intangible at the end of the train. var float RearClip; // The names of the single-bone skeletal controls in the mesh's AnimTree. var Array<Name> AnimTreeControls<EditCondition=TrainMesh>; // Distance between bones. Should all be equal. var float BoneDensity<EditCondition=TrainMesh>; // How much the bones tuck inwards when going around corners. Use this if the bone weights don't line up with the collision boxes when cornering. var float AverageCorners<UIMin=0.0|UIMax=1.0|EditCondition=TrainMesh>; // The spotlight attached to the front of the train. // Aruki note: This should be removed, I'm only leaving it to preserve the designer-set bEnabled value for now var(CatTrain) SpotLightComponent Headlight; // The spotlight actor attached to the front of the train. var(CatTrain) bool bEnableDynamicHeadlight; var transient Hat_TrainHeadlight HeadlightActor; var float CatAnimRunSpeed; var Array<SkelControlSingleBone> TrainBones; var AnimNodeAimOffset AimNode; var AnimNodeScalePlayRate SpeedNode; var(CatTrain) SpriteComponent SpriteEyes[2]; var(CatTrain) SpriteComponent SpriteHeadlights[2]; var transient Vector LastBoneUpdatePos; var transient bool CatMidTeleport; var transient bool DebugDontUpdateViewOptimize; var transient float HeadlightOpacity; var(Sounds) SoundCue FootStepSound; var(Sounds) SoundCue HoloStepSound; var(Sounds) SoundCue MeowVoiceClip; var(Sounds) Vector2D VoiceInterval<Tooltip=Min and Max seconds to randomly play the voice clip>; // MCU8-ADD-END simulated event PostBeginPlay() { Super.PostBeginPlay(); SetTimer(0.001, false, NameOf(PostPostBeginPlay)); if (!IsDweller) SetTrainColor(); if (SpriteEyes[0] != None && CatMesh != None) { SpriteEyes[0].SetHidden(false); CatMesh.AttachComponentToSocket(SpriteEyes[0], 'EyeL'); } if (SpriteEyes[1] != None && CatMesh != None) { SpriteEyes[1].SetHidden(false); CatMesh.AttachComponentToSocket(SpriteEyes[1], 'EyeR'); } if (SpriteHeadlights[0] != None && TrainMesh != None) { SpriteHeadlights[0].SetHidden(false); TrainMesh.AttachComponentToSocket(SpriteHeadlights[0], 'HeadLight'); } if (SpriteHeadlights[1] != None && TrainMesh != None) { SpriteHeadlights[1].SetHidden(false); TrainMesh.AttachComponentToSocket(SpriteHeadlights[1], 'BackLight'); } if (DebugRenderCollisionBoxes) { CatCollision.SetHidden(false); TrainFrontHit.SetHidden(false); } } function PostPostBeginPlay() { local mcu8_Hazard_Train car; local bool SetFrontDamage; // doesn't work, peck it //`assert(TickOptimizationGroup == StartPoint.TrackMaster.TickOptimizationGroup);// note: if this pops up, recalculate tracks! if (SpawnDuplicates > 0 && InitDuplicates()) return; if (StartPoint.TrackMaster.TrackIsStraight) CarCollisionDensity = 1; CarLength = CarCollisionSize.X/float(CarCollisionDensity); CarHeight = CarCollisionSize.Z/2; CarCollisionSize.X *= 1.3;// some overlap makes covering bends better if (TrainMesh == None || DebugAlwaysShowCollisionBoxes) DebugRenderCollisionBoxes = true; if (DebugRenderCollisionBoxes) { StaticMeshComponent.SetHidden(false); StaticMeshComponent.SetMaterial(0, Material'HatInTime_ScienceOwlAssets.Materials.Laser_outline'); bDebugVisualizePlayerBoundsCheck = true; } while (Train.Length > 0) { if (Train[Train.Length-1] != self) Train[Train.Length-1].Destroy(); Train.Remove(Train.Length-1, 1); } Train.AddItem(self); StaticMeshComponent.SetScale3D(CarCollisionSize*vect(0,1,1)/256.f + CarCollisionSize*vect(1,0,0)/float(CarCollisionDensity)/256.f); while (CarsInTrain > 0 && Train.Length < CarsInTrain*CarCollisionDensity) { car = Spawn(class'mcu8_Hazard_Train', self,, Location, Rotation); car.Following = Train[Train.Length-1]; car.FollowFrontDirectly = true; car.CollisionComponent.CanBeEdgeGrabbed = CollisionComponent.CanBeEdgeGrabbed && CollisionComponent.CanBeStoodOn; car.CollisionComponent.CanBeWallSlid = CollisionComponent.CanBeWallSlid && CollisionComponent.CanBeStoodOn; car.CollisionComponent.CanBeStoodOn = CollisionComponent.CanBeStoodOn; if (IsDweller) { car.IsDweller = true; car.InitialBlockActors = sb64_Hazard_CatTrain_Dweller(Self).InitialBlockActors; car.InvertCollisionWhenDeactivated_Internal = sb64_Hazard_CatTrain_Dweller(Self).InvertCollisionWhenDeactivated; } car.CollisionComponent.CanClimbInfinitely = true; car.bPlayerShouldStayBasedAfterJumping = bPlayerShouldStayBasedAfterJumping; car.OrientPlayerCamera = OrientPlayerCamera; car.TurnCameraToFaceTrainDirectionOnBase = TurnCameraToFaceTrainDirectionOnBase; car.TurnCameraEvenIfPreviousBaseWasTrain = TurnCameraEvenIfPreviousBaseWasTrain; if (HiddenVolumeList.Length > 0) car.SetHiddenVolumeList(HiddenVolumeList); if (DebugRenderCollisionBoxes) car.bDebugVisualizePlayerBoundsCheck = true; if (Train.Length <= Round(FrontClip*CarCollisionDensity) || Train.Length >= Round(CarsInTrain - RearClip)*CarCollisionDensity) { car.SetCollision(false, false); car.StaticMeshComponent.SetStaticMesh(None); car.StaticMeshComponent.SetHidden(true); car.DetachComponent(car.StaticMeshComponent); } else { if (!SetFrontDamage && 1 <= Round(FrontClip*CarCollisionDensity)) { car.IsDamageFront = true; SetFrontDamage = true; } car.StaticMeshComponent.SetStaticMesh(StaticMeshComponent.StaticMesh); car.StaticMeshComponent.SetScale3D(StaticMeshComponent.Scale3D); if (StaticMeshComponent.HiddenGame) car.StaticMeshComponent.SetHidden(true); else car.StaticMeshComponent.SetMaterial(0, StaticMeshComponent.Materials[0]); } car.CarLength = CarLength; car.CarHeight = CarHeight; `NPCManager.NPCManager.SetActorTickOptimizationGroup(car, TickOptimizationGroup); car.InitTrain(Train.Length%CarCollisionDensity == 0); } if (1 <= Round(FrontClip*CarCollisionDensity)) { StaticMeshComponent.SetActorCollision(false, false); //DetachComponent(StaticMeshComponent); //StaticMeshComponent = None; StaticMeshComponent.SetHidden(true); } SetTimer(RandRange(VoiceInterval.X, VoiceInterval.Y), false, NameOf(PlayMeowSound)); if (Headlight.bEnabled || bEnableDynamicHeadlight) { Headlight.SetEnabled(false); HeadlightActor = Spawn(class'Hat_TrainHeadlight', self); HeadlightActor.SetHardAttach(true); HeadlightActor.SetBase(self); } } function InitTrain(bool DoInitShake = true) { Super.InitTrain(DoInitShake); if (TrainWheelSound != None) { TrainWheelSound.FadeIn(4,1); TrainWheelSound.PitchMultiplier = ((AdjustedSpeed / default.Speed) / 2) + 0.5f; if (TrainMesh != None) { if (TrainMesh.GetSocketByName('WheelsFront') != None) TrainWheelSound.AudioVectorsWS.Add(1); if (TrainMesh.GetSocketByName('WheelsMiddle') != None) TrainWheelSound.AudioVectorsWS.Add(1); if (TrainMesh.GetSocketByName('WheelsBack') != None) TrainWheelSound.AudioVectorsWS.Add(1); } } } function FadeOutTrainFrontSound() { if (TrainFrontSound != None) TrainFrontSound.FadeOut(0.3,0); } simulated event Tick(float d) { UpdateOptimization(); UpdateWheelAudioComponent(); // FUCK THIS // EDIT: LMAO THAT WORK... UpdateVisuals2(); Super.Tick(d); } function UpdateOptimization() { local bool NearTrainAtAll, AllowCollision, PlayerStandingOnTrain; local int i, BoneIndexThreshold, FirstTrainIndexP1, FirstTrainIndexP2, LastTrainIndexP1, LastTrainIndexP2; local float TrainBoneRatio; if (Train.Length == 0 || !Initialized) return; if (DebugNeverOptimize) return; if (!OptimizeTrainCollisions) { if (!IsPlayerNearby) for (i = 0; i < Train.Length; i++) Train[i].SetIsPlayerNearby(true, DebugRenderCollisionBoxes); return; } NearTrainAtAll = DetermineIsPlayerNearby(TrainMesh); TrainBoneRatio = 1.0/float(AnimTreeControls.Length-1)*(Train.Length); BoneIndexThreshold = (StartPoint.TrackMaster.TrackIsStraight ? 1 : 3); if (ClosestBoneIndices[0] >= 0) { FirstTrainIndexP1 = int(ClosestBoneIndices[0]*TrainBoneRatio); LastTrainIndexP1 = FirstTrainIndexP1 + BoneIndexThreshold; } else { FirstTrainIndexP1 = -1; LastTrainIndexP1 = -1; } if (ClosestBoneIndices[1] >= 0) { FirstTrainIndexP2 = int(ClosestBoneIndices[1]*TrainBoneRatio); LastTrainIndexP2 = FirstTrainIndexP2 + BoneIndexThreshold; } else { FirstTrainIndexP2 = -1; LastTrainIndexP2 = -1; } for (i = 0; i < Train.Length; i++) { // We want to always enable the train if it, or an adjacent train, has players standing on it PlayerStandingOnTrain = Train[i].PlayersBasedOnTrain.length > 0 || (i > 0 && Train[i-1].PlayersBasedOnTrain.length > 0) || (i < Train.length-1 && Train[i+1].PlayersBasedOnTrain.length > 0); if (PlayerStandingOnTrain) { Train[i].SetIsPlayerNearby(true, DebugRenderCollisionBoxes); } else if (Train[i] == self || !NearTrainAtAll) { Train[i].SetIsPlayerNearby(NearTrainAtAll, DebugRenderCollisionBoxes); } else { // only tick train cars nearest to hatkid and bowkid AllowCollision = ( i >= FirstTrainIndexP1 && i <= LastTrainIndexP1 ) || ( i >= FirstTrainIndexP2 && i <= LastTrainIndexP2 ) ; Train[i].SetIsPlayerNearby(AllowCollision && Train[i].StaticMeshComponent.StaticMesh != None, DebugRenderCollisionBoxes); } } } simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp) { local Name n; local SkelControlSingleBone skel; Super.PostInitAnimTree(SkelComp); if (SkelComp == CatMesh) { AimNode = AnimNodeAimOffset(SkelComp.FindAnimNode('Aim')); SpeedNode = AnimNodeScalePlayRate(SkelComp.FindAnimNode('PlayRate')); } if (SkelComp == TrainMesh) { TrainBones.Length = 0; foreach AnimTreeControls(n) { skel = SkelControlSingleBone(TrainMesh.FindSkelControl(n)); skel.ControlStrength = 1; TrainBones.AddItem(skel); } } } simulated event PlayFootStepSound(int FootDown) { if (StartPoint != None && StartPoint.TrackMaster.SubPoints[CurrentSubPoint].bHasGeneratedTrack) { if (HoloStepSound != None) PlaySound(HoloStepSound); } else if (FootStepSound != None) PlaySound(FootStepSound); } function UpdateWheelAudioComponent() { local int i; local Vector loc; local Rotator rot; if (TrainWheelSound == None) return; if (TrainMesh == None) return; if (bHidden && TrainWheelSound.IsPlaying()) { TrainWheelSound.Stop(); return; } if (!TrainWheelSound.IsPlaying()) { TrainWheelSound.FadeIn(4,1); } if (!TrainWheelSound.IsPlaying()) return; if (TrainMesh.GetSocketByName('WheelsFront') != None) { if (TrainMesh.GetSocketWorldLocationAndRotation('WheelsFront', loc, rot, 0)) { TrainWheelSound.AudioVectorsWS[i] = loc; i++; } } if (TrainMesh.GetSocketByName('WheelsMiddle') != None) { if (TrainMesh.GetSocketWorldLocationAndRotation('WheelsMiddle', loc, rot, 0)) { TrainWheelSound.AudioVectorsWS[i] = loc; i++; } } if (TrainMesh.GetSocketByName('WheelsBack') != None) { if (TrainMesh.GetSocketWorldLocationAndRotation('WheelsBack', loc, rot, 0)) TrainWheelSound.AudioVectorsWS[i] = loc; } } function SetTrainColor() { if (StartPoint == None || TrainMesh == None) return; switch (StartPoint.GeneratedTrackColor) { case Green: TrainMesh.SetMaterial(0, MaterialInstanceConstant'HatInTime_Levels_Metro_K.Materials.train_metro_green'); break; case Yellow: TrainMesh.SetMaterial(0, MaterialInstanceConstant'HatInTime_Levels_Metro_K.Materials.train_metro_yellow'); break; case Purple: TrainMesh.SetMaterial(0, MaterialInstanceConstant'HatInTime_Levels_Metro_K.Materials.train_metro_purple'); break; default: if (TrainMesh.Materials.Length > 0) TrainMesh.SetMaterial(0, None); break; } } simulated event Destroyed() { // Anim node references must be cleared when destroying Actors for the garbage collector to catch them. // CatTrains are destroyed when initializing duplicates, so this is required. // Presumebly this is normally handled by native code. AimNode = None; SpeedNode = None; TrainBones.Length = 0; while (Train.Length > 0) { if (Train[Train.Length-1] != self) Train[Train.Length-1].Destroy(); Train.Remove(Train.Length-1, 1); } Super.Destroyed(); } function TickFollowers(float d) { local mcu8_Hazard_Train t; // don't tick followers if entire train isn't nearby if (!IsPlayerNearby) return; foreach Train(t) if (t != self) if (t.IsPlayerNearby || t.PlayersBasedOnTrain.length > 0) t.FollowerTick(d); } function bool InitDuplicates() { local int i; local sb64_Hazard_CatTrain dupe; local float tracksize, dist; if (SpawnDuplicates <= 0) return false; if (StartPoint.TrackMaster == None || StartPoint.TrackMaster.EditorTimeTrackCalculationState != TrainPointCalcState_Complete || StartPoint.TrackMaster.SubPoints.Length == 0) { `broadcast(self@"is on a track that isn't calculated! Move one of the track points connected to"@StartPoint); ScriptTrace(); return false; } tracksize = StartPoint.GetWholeTrackLength(true); if (tracksize <= 0) { SpawnDuplicates = 0; return false; } for (i = 0; i <= SpawnDuplicates; i++) { dupe = Spawn(class,,, Location, Rotation, self, true); if (IsDweller) { dupe.IsDweller = true; dupe.InitialBlockActors = sb64_Hazard_CatTrain_Dweller(Self).InitialBlockActors; sb64_Hazard_CatTrain_Dweller(dupe).InvertCollisionWhenDeactivated = sb64_Hazard_CatTrain_Dweller(Self).InvertCollisionWhenDeactivated; dupe.InvertCollisionWhenDeactivated_Internal = sb64_Hazard_CatTrain_Dweller(Self).InvertCollisionWhenDeactivated; } dupe.SpawnDuplicates = 0;// lmao 2 cat // yes I laugh 2 dupe.Initialized = false; dupe.Train.Length = 0; dupe.InitTrain(true); if (dupe.CatMesh != None && default.CatMesh != None) dupe.CatMesh.SetAnimTreeTemplate(default.CatMesh.AnimTreeTemplate);// randomize animations dist = tracksize*(i/float(SpawnDuplicates+1)); while (dist > 10) { dupe.AddSubPointDistanceBase(StartPoint.TrackMaster.SubPoints, FMin(100,dist), InstantTeleport); dist -= FMin(100,dist); } `if(`notdefined(FINAL_RELEASE)) if (dupe.HiddenVolumeList.Length != HiddenVolumeList.Length) { `broadcast("Error, dupe has incorrect HiddenVolumeList"); } if (dupe.TickOptimize != TickOptimize) { `broadcast("Error, dupe has incorrect TickOptimize"); } `endif } Destroy(); return true; } event Touch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal) { // don't get hit by the cow catcher while jumping unless in a tunnel if (IgnoreFrontDamageWhileAirborne && !IsNearerToCatThanTrainFront(Other) && CollisionComponent.CanBeStoodOn == true && Other.Physics != PHYS_Walking) return; Super.Touch(Other, OtherComp, HitLocation, HitNormal); } function bool IsNearerToCatThanTrainFront(Actor a) { // the Touch event cant differentiate between these, so just check which is nearest return VSizeSq((CatCollision.Translation >> Rotation) + Location - a.Location) < VSizeSq((TrainFrontHit.Translation >> Rotation) + Location - a.Location); } function TrainHit(Hat_PawnCombat p, Vector HitNormal) { if (Initialized && !Teleporting) Super.TrainHit(p, HitNormal); } event editoronly CheckForErrors(out Array<string> ErrorMessages) { Super.CheckForErrors(ErrorMessages); if (DrawScale3D != vect(1,1,1) || DrawScale != 1) ErrorMessages.AddItem("Doesn't support manual scaling! Use CarCollisionSize instead."); } event editoronly OnEditorPropertyChanged(Name PropertyName) { Super.OnEditorPropertyChanged(PropertyName); if (PropertyName == 'SpawnDuplicates') if (StartPoint != None) StartPoint.PerformEditorTimeTrackCalculation(); } event editoronly name GetCustomTickOptimizationGroup() { if (StartPoint != None) return StartPoint.GetCustomTickOptimizationGroup(); } event editoronly GetCustomEditorCommands(out Array<ActorCustomEditorCommand> Result) { local ActorCustomEditorCommand Item; Item.Text = "Enable Dynamic Headlight (EXPENSIVE)"; Item.Options.Length = 0; Item.Command = 'ToggleDynamicHeadlight'; Item.CurrentValue = bEnableDynamicHeadlight ? 1 : 0; Result.AddItem(Item); } event editoronly OnEditorCustomCommand(Name Command, int Index) { if (Command == 'ToggleDynamicHeadlight') { bEnableDynamicHeadlight = !bEnableDynamicHeadlight; } } function PlayMeowSound(optional bool Stop) { if (!Stop) { PlaySound(MeowVoiceClip); SetTimer(RandRange(VoiceInterval.X, VoiceInterval.Y), false, NameOf(PlayMeowSound)); } } function UpdateVisuals2() { local mcu8_Hazard_Train t; if (TrainMesh == None) return; if (Train.Length == 0 || !Initialized) return;// not initialized yet if (Teleporting) { // don't stretch out the cats on teleport (yes it was very funny looking) TrainMesh.SetHidden(true); if (CatMesh != None) CatMesh.SetHidden(true); foreach Train(t) t.SetLocation(Location); //return; } else { if (TrainMesh != None) TrainMesh.SetHidden(false); if (CatMesh != None) CatMesh.SetHidden(false); } UpdateTrainMesh(); UpdateCatMesh(); UpdateCollisionMeshes(); //Super.UpdateVisuals(SubPoints); } // Mismatch with collision boxes becomes noticable on tight corners and inclines. // TODO: Figure out how to implement 'AverageCorners' variable. function UpdateTrainMesh() { local int i; local vector boneLocation; local rotator boneRotation; if (TrainMesh == None) return; if (StartPoint == None || StartPoint.TrackMaster == None) return; for (i = 0; i < TrainBones.Length; i++) { // Clamping because passing Distance value of exactly 0 to GetSubPointOffsetBase() always returns (0,0,0). boneLocation = GetSubPointOffsetBase(StartPoint.TrackMaster.SubPoints, FMax(0.001, BoneDensity * i) * -1, true, boneRotation); boneLocation += vect(0,0,1) * CarHeight + TrainMeshOffset; // boneLocation += (vect(0,0,1) << boneRotation) * CarHeight + TrainMeshOffset; // results were a bit wonk // Un-invert and adjust rotations boneRotation.Pitch *= -1; boneRotation.Yaw += `HalfRot; boneRotation.Roll += `QuarterRot; TrainBones[i].BoneTranslation = boneLocation; TrainBones[i].BoneRotation = boneRotation; // if (bDebug) `Broadcast(self @ "Bone" @ i $ ":" @ `ShowVar(boneLocation) @ "|" @ `ShowVar(self.Location)); } // AverageCorners // Affects whole train (except first and last bones). // Lower bound is 0, no upper bound. } // Not quite accurate to native's results, but seems to follow corners slightly better. function UpdateCatMesh() { local vector catLocation; local rotator aimRotation, boneRotation, boneRotation2; if (CatMesh == None) return; if (StartPoint == None || StartPoint.TrackMaster == None) return; if (TrainMesh != None && TrainMesh.MatchRefBone(CatMeshTrainBoneName) != INDEX_NONE) { catLocation = TrainMesh.GetBoneLocation(CatMeshTrainBoneName); aimRotation = QuatToRotator(TrainMesh.GetBoneQuaternion(CatMeshTrainBoneName)); aimRotation.Pitch *= -1; aimRotation.Yaw += `HalfRot; aimRotation.Roll -= `QuarterRot; } else { catLocation = GetSubPointOffsetBase(StartPoint.TrackMaster.SubPoints, -BoneDensity, true, aimRotation); } catLocation += CatMeshOffset >> aimRotation; CatMesh.SetTranslation(catLocation); CatMesh.SetRotation(aimRotation); CatMesh.SetAbsolute(true, true); if (AimNode != None) { boneRotation = Rotation; boneRotation2 = CatMesh.Rotation; if (TrainMesh != None) { if (TrainMesh.MatchRefBone(AimNodeTrainBoneNameA) != INDEX_NONE) { boneRotation = QuatToRotator(TrainMesh.GetBoneQuaternion(AimNodeTrainBoneNameA)); boneRotation.Pitch *= -1; boneRotation.Yaw -= `HalfRot; boneRotation.Roll -= `QuarterRot; } if (TrainMesh.MatchRefBone(AimNodeTrainBoneNameB) != INDEX_NONE) { boneRotation2 = QuatToRotator(TrainMesh.GetBoneQuaternion(AimNodeTrainBoneNameB)); boneRotation2.Pitch *= -1; boneRotation2.Yaw -= `HalfRot; boneRotation2.Roll -= `QuarterRot; } } aimRotation = Normalize(boneRotation2 - boneRotation); aimRotation.Pitch = Clamp(aimRotation.Pitch, AimNodeMinMaxPitch.X * DegToUnrRot, AimNodeMinMaxPitch.Y * DegToUnrRot); aimRotation.Yaw = Clamp(aimRotation.Yaw, AimNodeMinMaxYaw.X * DegToUnrRot, AimNodeMinMaxYaw.Y * DegToUnrRot); // Pitch AimNode.Aim.Y = 0.0; if (aimRotation.Pitch != 0) AimNode.Aim.Y = aimRotation.Pitch / ((aimRotation.Pitch < 0 ? AimNodeMinMaxPitch.X : AimNodeMinMaxPitch.Y) * DegToUnrRot); // Yaw AimNode.Aim.X = 0.0; if (aimRotation.Yaw != 0) AimNode.Aim.X = aimRotation.Yaw / ((aimRotation.Yaw < 0 ? AimNodeMinMaxYaw.X : AimNodeMinMaxYaw.Y) * DegToUnrRot); } if (SpeedNode != None) { SpeedNode.ScaleByValue = FClamp(CurrentSpeed / CatAnimRunSpeed, SpeedNodeMinMaxRate.X, SpeedNodeMinMaxRate.Y); } } // This is a slightly different method than what native code seems to do. // Native code seems to always apply Translation/Rotation in local space. // I am applying transformations in world space for convenience, // then reverting to local space when players are not nearby. function UpdateCollisionMeshes() { local bool shouldUpdate; local vector newLocation; local rotator newRotation; shouldUpdate = (!OptimizeTrainCollisions || IsPlayerNearby); if (CatCollision != None && CatMesh != None && (shouldUpdate || CatCollision.CollideActors)) { newLocation = CatCollision.default.Translation; newRotation = CatCollision.default.Rotation; if (shouldUpdate) { newLocation = CatMesh.Translation; newRotation = CatMesh.Rotation; newLocation += CatCollisionOffset >> newRotation; newRotation += CatCollisionRotationOffset; } CatCollision.SetTranslation(newLocation); CatCollision.SetRotation(newRotation); CatCollision.SetAbsolute(shouldUpdate, shouldUpdate); // This isn't working, but only when going back to relative, and only for *this* component. Why?? CatCollision.SetActorCollision(shouldUpdate, CatCollision.BlockActors, true); // This prevents it from causing any issues at least } if (TrainFrontHit != None && TrainMesh != None && (shouldUpdate || TrainFrontHit.CollideActors)) { newLocation = TrainFrontHit.default.Translation; newRotation = TrainFrontHit.default.Rotation; if (shouldUpdate) { newLocation = Location; newRotation = Rotation; if (TrainMesh.MatchRefBone(TrainFrontHitBoneName) != INDEX_NONE) { newLocation = TrainMesh.GetBoneLocation(TrainFrontHitBoneName); newRotation = QuatToRotator(TrainMesh.GetBoneQuaternion(TrainFrontHitBoneName)); } newLocation += TrainFrontHitOffset >> newRotation; newRotation += TrainFrontHitRotationOffset; } TrainFrontHit.SetTranslation(newLocation); TrainFrontHit.SetRotation(newRotation); TrainFrontHit.SetAbsolute(shouldUpdate, shouldUpdate); TrainFrontHit.SetActorCollision(shouldUpdate, TrainFrontHit.BlockActors, true); } } defaultproperties { // MCU8-ADD: from original Hat_Hazard_CatTrain Begin Object Name=StaticMeshComponent0 StaticMesh=StaticMesh'HatinTime_PrimitiveShapes.TexPropCube' Materials(0)=Material'HatInTime_Levels_ScienceTrain.Materials.Objects.floorpanel_lockon' HiddenGame=true HiddenEditor=true LightEnvironment = None bUsePrecomputedShadows=false End Object Begin Object Class=SkeletalMeshComponent Name=Model0 SkeletalMesh=SkeletalMesh'hatintime_levels_metro_h.models.test_train' PhysicsAsset=PhysicsAsset'hatintime_levels_metro_h.test_train_phys' AnimTreeTemplate=AnimTree'hatintime_levels_metro_h.test_train_tree' CanBlockCamera=true bUsePrecomputedShadows=false HiddenGame=true// during initialization HiddenEditor=true LightEnvironment=MyLightEnvironment Translation=(Z=-23) Rotation=(Roll=16384) MotionBlurInstanceScale=0 bNoSelfShadow=true bCastDynamicShadow=false// looks bad more often than not `if(`isdefined(IS_NINTENDO_SWITCH)) MaxDrawDistance = 6500 `else MaxDrawDistance = 14000 `endif End Object TrainMesh=Model0 Components.Add(Model0) Begin Object Class=SkeletalMeshComponent Name=Model1 SkeletalMesh=SkeletalMesh'HatInTime_Characters_CatMetro.models.catmetro_skm' PhysicsAsset = PhysicsAsset'HatInTime_Characters_CatMetro.Physics.catmetro_skm_Physics' AnimSets(0)=AnimSet'HatInTime_Characters_CatMetro.AnimSets.catmetro_skm_Anims' AnimTreeTemplate=AnimTree'HatInTime_Characters_CatMetro.catmetro_skm_AnimTree' CanBlockCamera=true HiddenGame=true// during initialization bUsePrecomputedShadows=false LightEnvironment=MyLightEnvironment MotionBlurInstanceScale=0 bNoSelfShadow=true bCastDynamicShadow=false// looks bad more often than not `if(`isdefined(IS_NINTENDO_SWITCH)) MinLodModel = 1; MaxDrawDistance = 6500 `else MaxDrawDistance = 14000 `endif End Object CatMesh=Model1 Components.Add(Model1) Begin Object Class=SpotLightComponent Name=SpotLightComponent0 LightAffectsClassification=LAC_DYNAMIC_AND_STATIC_AFFECTING bForceDynamicLight=TRUE LightingChannels=(BSP=TRUE,Static=TRUE,Dynamic=TRUE,CompositeDynamic=TRUE) Brightness=5 LightColor=(R=255,G=240,B=120) Radius=2048 FalloffExponent = 3 InnerConeAngle = 22 OuterConeAngle = 44 Rotation = (Pitch=-1820) CullDistance = 4000 DetailMode = DM_Medium bAffectCompositeShadowDirection = false; bCastCompositeShadow = false; bEnabled = false; bEnabledInEditor = false; End Object Headlight=SpotLightComponent0 Components.Add(SpotLightComponent0) Begin Object Class=StaticMeshComponent Name=CatCollision0 StaticMesh=StaticMesh'HatinTime_PrimitiveShapes.TexPropCube' HiddenGame = true HiddenEditor = true CollideActors = true BlockActors = false Scale3D = (X=1.2,Y=0.7,Z=0.8) AlwaysCheckCollision = true End Object CatCollision = CatCollision0 CollisionComponent = CatCollision0 Components.Add(CatCollision0) Begin Object Class=StaticMeshComponent Name=TrainFrontHit0 StaticMesh=StaticMesh'HatinTime_PrimitiveShapes.TexPropCube' HiddenGame = true HiddenEditor = true CollideActors = true BlockActors = false CanBeEdgeGrabbed = false CanBeWallSlid = false Scale3D = (X=0.1,Y=1.5,Z=1.1) AlwaysCheckCollision = true End Object TrainFrontHit = TrainFrontHit0// (the cow catcher) Components.Add(TrainFrontHit0) Begin Object Class=SpriteComponent Name=SpriteLensFlareEye0 Sprite = Material'HatInTime_Levels_Metro_M.Materials.CatEyesLight' SpriteColor = (R=1,G=0.86,B=0.2,A=0.2) UL = 1000 VL = 1000 BlendMode = BLEND_Translucent MaxDrawDistance = 7000 HiddenGame = true // set in initialization HiddenEditor = true End Object Components.Add(SpriteLensFlareEye0) SpriteEyes(0) = SpriteLensFlareEye0 Begin Object Class=SpriteComponent Name=SpriteLensFlareEye1 Sprite = Material'HatInTime_Levels_Metro_M.Materials.CatEyesLight' SpriteColor = (R=1,G=0.86,B=0.2,A=0.2) UL = 1000 VL = 1000 BlendMode = BLEND_Translucent MaxDrawDistance = 7000 HiddenGame = true // set in initialization HiddenEditor = true End Object Components.Add(SpriteLensFlareEye1) SpriteEyes(1) = SpriteLensFlareEye1 Begin Object Class=SpriteComponent Name=SpriteLensflareHeadlight0 Sprite = Material'HatInTime_Levels_Metro_M.Materials.CatEyesLight' SpriteColor = (R=0.000023,G=1.0,B=0.625345,A=0.5) UL = 1000 VL = 1000 BlendMode = BLEND_Translucent MaxDrawDistance = 7000 HiddenGame = true // set in initialization HiddenEditor = true End Object Components.Add(SpriteLensflareHeadlight0); SpriteHeadlights(0) = SpriteLensflareHeadlight0 Begin Object Class=SpriteComponent Name=SpriteLensflareHeadlight1 Sprite = Material'HatInTime_Levels_Metro_M.Materials.CatEyesLight' SpriteColor = (R=0.000023,G=1.0,B=0.625345,A=0.5) UL = 1000 VL = 1000 BlendMode = BLEND_Translucent MaxDrawDistance = 7000 HiddenGame = true // set in initialization HiddenEditor = true End Object Components.Add(SpriteLensflareHeadlight1); SpriteHeadlights(1) = SpriteLensflareHeadlight1 Begin Object Class=AudioComponent Name=AmbientSoundComponent0 SoundCue=SoundCue'HatinTime_SFX_Metro.SoundCues.CatTrains_Purring_Loop' bAutoPlay=false bStopWhenOwnerDestroyed=true End Object TrainFrontSound=AmbientSoundComponent0 Components.Add(AmbientSoundComponent0) Begin Object Class=AudioComponent Name=AmbientSoundComponent1 SoundCue=SoundCue'HatinTime_SFX_Metro.SoundCues.CatTrains_Movement_Driving_Loop_Standart' bAutoPlay=false bStopWhenOwnerDestroyed=true End Object TrainWheelSound=AmbientSoundComponent1 Components.Add(AmbientSoundComponent1) Speed = 900 CatAnimRunSpeed = 880 OrientPlayerCamera = true UseShakeCamera = true InstantTeleport = true IgnoreFrontDamageWhileAirborne = true UseFastTrainVFX = true IsDamageFront = true DamageOnAnyTouch = true OptimizeTrainCollisions = true WarningCameraShakeLead = 100 CameraShakeFalloff = 900 UseTrainHorn = true TrainHornLead = 500 TrainHornSize = 200 FrontClip = 2.48 RearClip = 0.76 bBlockActors = false CarCollisionSize = (X=320,Y=320,Z=360) CarsInTrain = 16 CarCollisionDensity = 2 BoneDensity = 320 AnimTreeControls = ("wagon1","wagon2","wagon3","wagon4","wagon5","wagon6","wagon7","wagon8","wagon9","wagon10","wagon11","wagon12","wagon13","wagon14","wagon15","wagon16","wagon_end") AverageCorners = 0.5 TrainHornSound = SoundCue'HatinTime_SFX_Metro.SoundCues.metrotrain_warninghorn' FootStepSound = SoundCue'HatinTime_SFX_Metro.SoundCues.CatTrains_Movement_Paw_Footstep' HoloStepSound = SoundCue'HatinTime_SFX_Metro.SoundCues.holorail_footstep' MeowVoiceClip = SoundCue'HatinTime_SFX_Metro.SoundCues.CatTrains_Vocal' VoiceInterval = (X=15,Y=40) EditorConvertMenuName = "Length" EditorConvertActorList.Add((Text="Long", ClassName="sb64_Hazard_CatTrain")) EditorConvertActorList.Add((Text="Short", ClassName="sb64_Hazard_CatTrain_Short")) EditorConvertActorList.Add((Text="Dweller", ClassName="sb64_Hazard_CatTrain_Dweller")) // MCU8-ADD-END SpeedNodeMinMaxRate = (X=0.5,Y=2.0) AimNodeTrainBoneNameA = "wagon2" AimNodeTrainBoneNameB = "wagon3" AimNodeMinMaxPitch = (X=-90.0,Y=90.0) AimNodeMinMaxYaw = (X=-90.0,Y=90.0) CatMeshTrainBoneName = "wagon2" CatMeshOffset = (X=10.0,Z=-165.0) TrainMeshOffset = (Z=-24.0) CatCollisionOffset = (Z=90.0) CatCollisionRotationOffset = (Pitch=0,Yaw=0,Roll=0) TrainFrontHitBoneName = "wagon3" TrainFrontHitOffset = (X=100.0) TrainFrontHitRotationOffset = (Pitch=-5461,Yaw=0,Roll=16384) DebugRenderCollisionBoxes = false; }
/* * Garbage code by m_cu8 */ class sb64_Hazard_CatTrain_Dweller extends sb64_Hazard_CatTrain; var(Collisions) bool InvertCollisionWhenDeactivated; event editoronly GetCustomEditorCommands(out Array<ActorCustomEditorCommand> Result) { local ActorCustomEditorCommand Item; Item.Text = "Inverted Dweller Mesh"; Item.Command = 'InvertDwellerMesh'; Item.CurrentValue = bBlockActors ? 0 : 1; Result.AddItem(Item); } event editoronly OnEditorCustomCommand(Name Command, int Index) { if (Command == 'InvertDwellerMesh') { SetTrainColor(); } } function SetTrainColor() { if (bBlockActors) { SetCollision(false,false); TrainMesh.SetMaterial(0, MaterialInstanceConstant'hatintime_dwellerobjects.Materials.DwllerObject_Phong_Inverse'); } else { SetCollision(true,true); TrainMesh.SetMaterial(0, Material'hatintime_dwellerobjects.Materials.DwllerObject_Phong'); } } simulated event PostBeginPlay() { isDweller = true; InvertCollisionWhenDeactivated_Internal = InvertCollisionWhenDeactivated; SetTrainColor(); Super.PostBeginPlay(); InitialBlockActors = bBlockActors; // if (InvertCollisionWhenDeactivated) // SetCollisionType(COLLIDE_BlockAll); InitMaterialInstancesMesh(TrainMesh); SetMaterialFloatMesh(TrainMesh, 'Editor', 0); } /* MATERIALS */ function InitMaterialInstancesMesh(Meshcomponent m) { local int i; local MaterialInstanceConstant Mat; for (i = 0; i < m.GetNumElements(); i++) { if (m.GetMaterial(i) == None) continue; Mat = m.CreateAndSetMaterialInstanceConstant(i); m.SetMaterial(i, Mat); } } /* MATERIAL FUNCTIONS */ function SetMaterialVectorMesh(MeshComponent m, Name n, Vector c) { local int i; local MaterialInstance Mat; local LinearColor v; v.R = c.X; v.G = c.Y; v.B = c.Z; v.A = 1; if (m == None) return; for (i = 0; i < m.GetNumElements(); i++) { Mat = MaterialInstance(m.GetMaterial(i)); if (Mat != None) { Mat.SetVectorParameterValue(n, v); } } } function SetMaterialFloatMesh(MeshComponent m, Name n, float f) { local int i; local MaterialInstance Mat; if (m == None) return; for (i = 0; i < m.GetNumElements(); i++) { Mat = MaterialInstance(m.GetMaterial(i)); if (Mat != None) { Mat.SetScalarParameterValue(n, f); } } } function SetTransitionEffectVectorMesh(MeshComponent m, Name n, Vector start, Vector end, float time) { local int i; local InterpCurveVector Curve; local MaterialInstanceTimeVarying Mat; Curve = GenerateCurveVector(start, end, time); for (i = 0; i < m.GetNumElements(); i++) { Mat = MaterialInstanceTimeVarying(m.GetMaterial(i)); if (Mat != None) { Mat.SetVectorCurveParameterValue(n, Curve); Mat.SetVectorStartTime(n, 0.0); } } } function SetTransitionEffectFloatMesh(MeshComponent m, Name n, float start, float end, float time) { local int i; local InterpCurveFloat Curve; local MaterialInstanceTimeVarying Mat; Curve = GenerateCurveFloat(start, end, time); for (i = 0; i < m.GetNumElements(); i++) { Mat = MaterialInstanceTimeVarying(m.GetMaterial(i)); if (Mat != None) { Mat.SetScalarCurveParameterValue(n, Curve); Mat.SetScalarStartTime(n, 0.0); } } } function InterpCurveFloat GenerateCurveFloat(float start, float end, float time) { local InterpCurveFloat Curve; local InterpCurvePointFloat Point; Point.InVal = 0.0; Point.OutVal = start; Curve.Points.AddItem(Point); Point.InVal = time; Point.OutVal = end; Curve.Points.AddItem(Point); return Curve; } function InterpCurveVector GenerateCurveVector(Vector start, Vector end, float time) { local InterpCurveVector Curve; local InterpCurvePointVector Point; Point.InVal = 0.0; Point.OutVal = start; Curve.Points.AddItem(Point); Point.InVal = time; Point.OutVal = end; Curve.Points.AddItem(Point); return Curve; } defaultproperties { Begin Object Name=Model1 Materials(0)=Material'HatInTime_Levels_Metro_A.GhostTrain' Materials(1)=Material'HatInTime_Characters.Materials.Invisible' End Object InvertCollisionWhenDeactivated = true; isDweller = true; }
// a copy of the original Hat_Hazard_CatTrain_Short... just in case :) class sb64_Hazard_CatTrain_Short extends sb64_Hazard_CatTrain; defaultproperties { Begin Object Name=Model0 SkeletalMesh=SkeletalMesh'HatInTime_Characters_CatMetro.metro_short_skm' End Object Begin Object Name=CatCollision0 CanBeStoodOn=false End Object CarsInTrain = 7 AnimTreeControls = ("wagon1","wagon2","wagon3","wagon4","wagon5","wagon6","wagon7","wagon8") }
/** * just a copy of the Hat_CamMode_CatTrainRoof */ class mcu8_CamMode_CatTrainRoof extends Hat_CamMode_Dynamic; defaultproperties { CameraPriority = 105; IsCamAssist = false; IsDynamic = true; IsAdditive = true; FadeInTime = 0.4f; FadeOutTime = 0.4f; } event Render(Hat_PlayerCamera_Base PlayerCamera, Pawn TPawn, const float DeltaTime, out Vector camEnd, out Rotator camRotation, const Rotator PlayerRotation, out float FoV, out sDynamicCameraInfo DynamicCameraInfo) { FoV *= 1.1; }