using Sandbox; using Sandbox.Utility; using Sandbox.Diagnostics; using System.Linq; using System.Threading.Tasks; namespace SimpleInteractions { /// /// Simple interaction component /// [Icon( "touch_app" )] [Title( "Simple Interaction" )] public class SimpleInteraction : Component { [Property] public bool InteractionEnabled {get; set;} = true; [Property, Title("Interaction Name")] public string InteractionString {get; set;} = "Interact"; [Property] public float InteractionDistance {get; set;} = 120f; [Property, ToggleGroup("InteractionHold")] public bool InteractionHold {get; set;} = false; [Property, Group("InteractionHold")] public float InteractionHoldDuration {get; set;} = 0.5f; /// /// If not set, will try to find a collider on the same GameObject. /// [Property, Title("Override collider")] public Collider Collider { get; set; } private GameObject CurrentPanel = null; private TimeSince HoldTime = 0; private bool Holding = false; private bool HoldingInteractionHappened = false; static protected GameObject InteractionPanelPrefab ; protected override void OnStart() { InteractionPanelPrefab = GameObject.GetPrefab("InteractionsPanel.prefab"); Assert.True(InteractionPanelPrefab.IsValid(), $"No InteractionPanel prefab found for {this.GameObject.Name}!"); if (!Collider.IsValid()) { Collider = this.GameObject.GetComponent(); Assert.True(Collider.IsValid(), $"No collider found for {this.GameObject.Name}!"); } this.GameObject.Tags.Add("Interact"); } protected override void OnUpdate() { if (!InteractionEnabled) { // Reset everything just in case Holding = false; HoldingInteractionHappened = false; // Delete the Interaction panel otherwise it would just float there... if (CurrentPanel.IsValid()) { InteractionPanel panel = CurrentPanel.GetComponent(); if (panel.IsValid()) { _ = DeletePanel(); } } return; } Ray ray = Scene.Camera.GameObject.Transform.World.ForwardRay; var traces = Scene.Trace.Ray(ray, InteractionDistance) .WithoutTags("IgnoreInteract") .HitTriggers() .RunAll(); // Gizmo.Draw.Line(tr.StartPosition, tr.EndPosition); if (traces.Count() <= 0) { _ = DeletePanel(); // Force repressing use in case you looked away while holding down. HoldingInteractionHappened = true; return; } foreach (var tr in traces) { Collider HitCollider = tr.Shape.Collider as Collider; // If it's a trigger and it doesn't have the interact tag, skip it. // We can see through it. if (HitCollider.IsTrigger && !HitCollider.GameObject.Tags.Has("Interact")) { continue; } else if (!HitCollider.IsTrigger && !HitCollider.GameObject.Tags.Has("Interact")) { // Something is blocking the interaction. _ = DeletePanel(); // Force repressing use in case you looked away while holding down. HoldingInteractionHappened = true; break; } Vector3 offset = Vector3.Zero; if (HitCollider is BoxCollider) { offset = (HitCollider as BoxCollider).Center; } else if (HitCollider is SphereCollider) { offset = new Vector3((HitCollider as SphereCollider).Center); } if (HitCollider == Collider) { Vector3 pos = new Vector3(offset.x, offset.y, - offset.z); OnHover(HitCollider.GameObject.WorldPosition - pos); break; } else { _ = DeletePanel(); // Force repressing use in case you looked away while holding down. HoldingInteractionHappened = true; } } } private void OnHover(Vector3 pos) { if (!CurrentPanel.IsValid()) { CurrentPanel = InteractionPanelPrefab.Clone(); } CurrentPanel.WorldPosition = pos; // Flip the panel to face the camera Rotation camRotation = Scene.Camera.WorldRotation; Angles ang = camRotation.Angles(); ang.roll += 180; ang.pitch += 180; Rotation rot = ang.ToRotation(); CurrentPanel.WorldRotation = rot; InteractionPanel panel = CurrentPanel.GetComponent(); panel.InteractionString = InteractionString; panel.IsHoldInteraction = InteractionHold; panel.ProgressionHold = 0; if (!InteractionHold) { if (Input.Pressed("use")) { _ = panel.TriggerInteractAnimation(); OnInteract(); } return; } if (!Input.Down("use")) { Holding = false; HoldingInteractionHappened = false; return; } // Interaction already happened. Player needs to release and press again. if (HoldingInteractionHappened) { return; } if (Holding) { panel.ProgressionHold = Easing.QuadraticInOut(HoldTime / InteractionHoldDuration); if (HoldTime >= InteractionHoldDuration) { HoldingInteractionHappened = true; OnInteract(); } } else { // Started holding. Holding = true; HoldTime = 0; _ = panel.TriggerInteractAnimation(); } } async private Task DeletePanel() { if(!CurrentPanel.IsValid()) return; CurrentPanel.GetComponent().Panel.Delete(); await Task.DelaySeconds( 0.1f ); CurrentPanel.Destroy(); } [Rpc.Broadcast] protected virtual void OnInteract() { Log.Error($"Interaction not implemented for {this.GameObject.Name}!"); } } }