core/player/PacViewNode.cs
using Sandbox.UI;
[Icon("videocam")]
public class PacViewNode : Component {
[Property] public string NodeGroup {get; set;}
public struct TargetNode {
public GameObject Node {get; set;}
public string ExtNodeName {get; set;}
[Range(0, 8)] public int EntryAngle {get; set;}
[Range(0, 8)] public int ReverseEntryAngle {get; set;}
public bool DenyReverseEntry {get; set;}
//public Door BlockedByDoor {get; set;}
public bool BlockOnOpen {get; set;}
//public Elevator BlockedByElevator {get; set;}
public string ExtElevatorName {get; set;}
public int ElevatorFloor {get; set;}
public Action OnTravel {get; set;}
public override string ToString() {
return Node.ToString();
}
}
[Button] public void AutoConnectNodes() {
var group = GameObject.Name.Replace("_" + GameObject.Name.Split("_").Last(), "");
var index = int.Parse(GameObject.Name.Split("_").Last());
foreach (var gameobject in Scene.GetAllObjects(true)) {
var camnode = gameobject.Components.Get<PacViewNode>();
if (camnode == null)
continue;
bool alreadyset = false;
foreach (var node in ConnectedNodes)
if (node.Node == gameobject)
alreadyset = true;
if (alreadyset)
continue;
if (group != gameobject.Name.Replace("_" + gameobject.Name.Split("_").Last(), ""))
continue;
if (Math.Abs(int.Parse(gameobject.Name.Split("_").Last()) - index) != 1)
continue;
var refnormal = Vector3.Direction(Transform.World.Position, gameobject.Transform.World.Position);
int entryangle = 0;
for (int i = 0; i < camnode.ViewAngles.Count; i++)
if (gameobject.Transform.World.RotationToWorld(camnode.ViewAngles[i].ToRotation()).Forward.Dot(refnormal)
> gameobject.Transform.World.RotationToWorld(camnode.ViewAngles[entryangle].ToRotation()).Forward.Dot(refnormal))
entryangle = i;
int reverseentryangle = 0;
for (int i = 0; i < camnode.ViewAngles.Count; i++)
if (gameobject.Transform.World.RotationToWorld(camnode.ViewAngles[i].ToRotation()).Forward.Dot(refnormal)
< gameobject.Transform.World.RotationToWorld(camnode.ViewAngles[reverseentryangle].ToRotation()).Forward.Dot(refnormal))
reverseentryangle = i;
ConnectedNodes.Add(new() {
Node = gameobject,
EntryAngle = entryangle,
ReverseEntryAngle = reverseentryangle,
DenyReverseEntry = false,
//BlockedByDoor = null,
});
}
}
[Property] public List<TargetNode> ConnectedNodes {get; set;} = new();
[Property, ReadOnly] public BasePlayer AttachedPlayer {get; set;}
[Property, ReadOnly] public int ViewDirection {get; set;} = 0;
[Button] public void FourViewAngles() {
ViewAngles = new List<Angles>{
Angles.Zero.WithYaw(0f),
Angles.Zero.WithYaw(90f),
Angles.Zero.WithYaw(180f),
Angles.Zero.WithYaw(270f)
};
}
[Button] public void SixViewAngles() {
ViewAngles = new List<Angles>{
Angles.Zero.WithYaw(0f),
Angles.Zero.WithYaw(60f),
Angles.Zero.WithYaw(120f),
Angles.Zero.WithYaw(180f),
Angles.Zero.WithYaw(240f),
Angles.Zero.WithYaw(300f)
};
}
[Property] public List<Angles> ViewAngles {get; set;} = new List<Angles>{
Angles.Zero.WithYaw(0f),
Angles.Zero.WithYaw(60f),
Angles.Zero.WithYaw(120f),
Angles.Zero.WithYaw(180f),
Angles.Zero.WithYaw(240f),
Angles.Zero.WithYaw(300f)
};
[Property] public float Fov {get; set;} = 100f;
[Property] public bool RainOnCamera {get; set;} = false;
[Property] public bool SnowOnCamera {get; set;} = false;
[Property] public List<int> AdditionalSnowOnCameraAngles {get; set;} = new();
[Property] public bool FullScreenFMV {get; set;} = false;
[Property] public bool CantMoveInFullScreenFMV {get; set;} = false;
[Property] public bool UseCustomTextureOverlay {get; set;}
[Property] public Texture CustomTextureOverlay {get; set;}
public struct ViewAngleIndexSet {
[Range(0, 8)] public int A {get; set;}
[Range(0, 8)] public int B {get; set;}
}
[Property] public List<ViewAngleIndexSet> DisconnectedAngles {get; set;} = new();
[Property] public Dictionary<int,int> NodeMoveOverrides {get; set;} = new();
[Property] public Dictionary<int,int> NodeReverseMoveOverrides {get; set;} = new();
[Property] public List<int> DisableMovementNodes {get; set;} = new();
[Property, Range(0,2)] public float FmvGreenScale {get; set;} = 1f;
[Property, Hide] public List<Texture> BaseLayerRenders {get; set;} = new();
[Property] public Action OnEnterNode {get; set;}
[Property] public Action OnExitNode {get; set;}
[Property] public Action OnAttemptInputLeft {get; set;}
[Property] public Action OnAttemptInputRight {get; set;}
protected override void DrawGizmos() {
Gizmo.Draw.IgnoreDepth = Gizmo.IsSelected;
Gizmo.Draw.Color = Color.White;
Gizmo.Draw.LineSphere(0f, 1f);
Gizmo.Hitbox.Sphere(new Sphere(0f, 2f));
foreach (var node in ConnectedNodes) {
if (node.Node == null)
continue;
Gizmo.Draw.IgnoreDepth = false;
if (Gizmo.IsSelected) {
Gizmo.Draw.Color = Color.Green;
Gizmo.Draw.LineThickness = 2;
} else {
Gizmo.Draw.Color = Color.Gray;
Gizmo.Draw.LineThickness = 1;
}
#if false
#endif
Gizmo.Draw.Line(0f, Transform.World.ToLocal(node.Node.Transform.World).Position);
if (Gizmo.IsSelected) {
Gizmo.Draw.IgnoreDepth= true;
Gizmo.Draw.LineThickness = 1;
Gizmo.Draw.Color = Color.White;
var nodecomp = node.Node.Components.Get<PacViewNode>();
if (nodecomp != null) {
Gizmo.Transform = node.Node.Transform.World;
foreach (var angle in nodecomp.ViewAngles) {
if (node.EntryAngle == nodecomp.ViewAngles.IndexOf(angle)) {
Gizmo.Draw.LineThickness = 5f;
Gizmo.Draw.Color = Color.Green;
}
if (node.ReverseEntryAngle == nodecomp.ViewAngles.IndexOf(angle) && !node.DenyReverseEntry) {
Gizmo.Draw.LineThickness = 5f;
Gizmo.Draw.Color = Color.Orange;
}
Gizmo.Draw.Line(0f, angle.Forward * 3f);
Gizmo.Draw.Color = Color.White;
Gizmo.Draw.LineThickness = 1;
Gizmo.Draw.Text(nodecomp.ViewAngles.IndexOf(angle).ToString(), new Transform(angle.Forward * 3.2f));
}
}
Gizmo.Transform = Transform.World;
}
}
if (Gizmo.IsSelected) {
Gizmo.Draw.IgnoreDepth= true;
Gizmo.Draw.LineThickness = 2;
Gizmo.Draw.Color = Color.Red;
foreach (var angle in ViewAngles) {
Gizmo.Draw.Line(0f, angle.Forward * 3f);
Gizmo.Draw.Text(ViewAngles.IndexOf(angle).ToString(), new Transform(angle.Forward * 3.2f));
}
Gizmo.Draw.Color = Color.Gray;
Gizmo.Draw.Line(0f, Vector3.Down * 72f);
Gizmo.Draw.LineCircle(Vector3.Down * 72f, Vector3.Up, 12f);
Gizmo.Draw.IgnoreDepth = false;
Gizmo.Draw.Color = Gizmo.Colors.Red;
Gizmo.Draw.LineThickness = 2.5f;
Gizmo.Draw.Line(0f, Vector3.Down * 72f);
Gizmo.Draw.LineCircle(Vector3.Down * 72f, Vector3.Up, 12f);
}
base.DrawGizmos();
}
private Action DelayedAction = null;
private TimeUntil DelayedActionDelay;
private bool Zoomed = false;
protected override void OnUpdate() {
if (!AttachedPlayer.IsValid())
return;
#pragma warning disable CS0642
if (DelayedAction != null && !AllowInteraction())
DelayedActionDelay = 0.1f;
if (DelayedAction != null && AllowInteraction() && DelayedActionDelay) {
AttachedPlayer.PacCamera.InvalidateView();
AttachedPlayer.PacCamera.Update();
DelayedAction.Invoke();
DelayedAction = null;
} else {
var attack1handled = false;
if (CustomTextureOverlay.IsValid()) {
AttachedPlayer.PacCamera.OverlayTexture = CustomTextureOverlay;
AttachedPlayer.PacCamera.OverlayStrength = AttachedPlayer.PacCamera.OverlayStrength.Approach(UseCustomTextureOverlay ? 1 : 0, Time.Delta);
} else if (UseCustomTextureOverlay) {
AttachedPlayer.PacCamera.OverlayStrength = 0f;
} else {
var mousepos = (Mouse.Position / Screen.Size);
if (mousepos.y > 0.868f || Zoomed) {
attack1handled = true;
mousepos.x -= 0.5f;
mousepos.x *= (float)Screen.Size.x / Screen.Size.y;
mousepos.x /= 640f/480f;
mousepos.x += 0.5f;
if (!Zoomed) {
Zoomed = Input.Pressed("Attack1") && mousepos.x > 0.041f && mousepos.y > 0.888f && mousepos.x < 0.187f && mousepos.y < 0.987f;
} else if (mousepos.x < 0.097f || mousepos.y < 0.206f || mousepos.x > 0.896f || mousepos.y > 0.741f) {
if (Input.Pressed("Attack1"))
Zoomed = false;
}
}
if (Zoomed)
AttachedPlayer.PacCamera.OverlayTexture = Texture.Load("materials/interface/choreo/postcard_zoom.vtex");
else
AttachedPlayer.PacCamera.OverlayTexture = Texture.Load("materials/interface/choreo/postcard_bar.vtex");
AttachedPlayer.PacCamera.OverlayStrength = (FullScreenFMV || AttachedPlayer.PacCamera.FullScreenFMV) ? 0f : 1f;
}
AttachedPlayer.PacCamera.Turn = 0;
if (!attack1handled && InputTurn(false, true)) {
AttachedPlayer.PacCamera.Turn = Math.Sign((Mouse.Position / Screen.Size).x - 0.5f);
}
if (Input.Pressed("Attack1") && AllowInteraction() && !attack1handled && (InputTurn(true, true) || InputInteraction() || InputMove(true, false)));
if (Input.Pressed("Forward") && AllowInteraction() && (InputMove(false, false)));
if (Input.Pressed("Backward") && AllowInteraction() && (InputMove(false, true)));
if (Input.Pressed("Left") && AllowInteraction() && (InputTurn(true, false, true)));
if (Input.Pressed("Right") && AllowInteraction() && (InputTurn(true, false, false)));
}
#pragma warning restore CS0642
if (!AttachedPlayer.IsValid())
return;
if (ViewDirection >= ViewAngles.Count) ViewDirection = 0; else if (ViewDirection < 0) ViewDirection = ViewAngles.Count - 1;
AttachedPlayer.LocalRotation = ViewAngles[ViewDirection];
AttachedPlayer.PacCamera.FullScreenFMV = FullScreenFMV;
AttachedPlayer.PacCamera.RenderCamera.FieldOfView = Fov;
InteractionCooldown = false;
base.OnUpdate();
}
public bool InteractionCooldown = false;
public bool AllowInteraction() {
if (!AttachedPlayer.PacCamera.TransitionTime)
return false;
if (CantMoveInFullScreenFMV && FullScreenFMV)
return false;
if (InteractionCooldown)
return false;
return true;
}
public bool InputTurn(bool real, bool usemouse, bool right = false) {
var mousePosition = Mouse.Position / Screen.Size;
if (real) {
if (usemouse && MathF.Abs(mousePosition.x - 0.5f) > 0.25f) {
if (mousePosition.x < 0.5f)
OnAttemptInputRight?.Invoke();
else
OnAttemptInputLeft?.Invoke();
} else {
if (right)
OnAttemptInputRight?.Invoke();
else
OnAttemptInputLeft?.Invoke();
}
}
if (ViewAngles.Count == 1)
return false;
var originalViewDirection = ViewDirection;
if (usemouse) {
if (MathF.Abs(mousePosition.x - 0.5f) < 0.25f)
return false;
if (mousePosition.x > 0.5f)
ViewDirection--;
else
ViewDirection++;
} else {
if (right)
ViewDirection++;
else
ViewDirection--;
}
foreach (var set in DisconnectedAngles) {
if (set.A == originalViewDirection && set.B == ViewDirection) {
ViewDirection = originalViewDirection;
return false;
}
if (set.B == originalViewDirection && set.A == ViewDirection) {
ViewDirection = originalViewDirection;
return false;
}
}
if (!real) {
ViewDirection = originalViewDirection;
return true;
}
if (AttachedPlayer.PacCamera.DelayMoveAction != null) {
AttachedPlayer.PacCamera.DelayMoveAction.Invoke();
AttachedPlayer.PacCamera.DelayMoveAction = null;
var dir = ViewDirection;
DelayedAction = () => {
if (ViewDirection > originalViewDirection)
AttachedPlayer.PacCamera.DoTransition = PlayerPacCamera.TransitionType.SlideRight;
else
AttachedPlayer.PacCamera.DoTransition = PlayerPacCamera.TransitionType.SlideLeft;
foreach (var animator in Scene.GetAllComponents<NpcAnimator>())
animator.ResetToIdle();
ViewDirection = dir;
};
ViewDirection = originalViewDirection;
return true;
}
if (ViewDirection > originalViewDirection)
AttachedPlayer.PacCamera.DoTransition = PlayerPacCamera.TransitionType.SlideRight;
else
AttachedPlayer.PacCamera.DoTransition = PlayerPacCamera.TransitionType.SlideLeft;
foreach (var animator in Scene.GetAllComponents<NpcAnimator>())
animator.ResetToIdle();
return true;
}
public bool InputInteraction() {
var pos = Mouse.Position / Screen.Size;
pos *= AttachedPlayer.PacCamera.RenderCamera.ScreenRect.Size;
var ray = AttachedPlayer.PacCamera.RenderCamera.ScreenPixelToRay(pos);
foreach (var interactable in Scene.GetAllComponents<IsoInteractable>()) {
if (!interactable.ValidNodes.Contains(this))
continue;
if (interactable.Box.Trace(ray.ToLocal(interactable.WorldTransform), 512, out var dist)) {
interactable.Interact();
return true;
}
}
return false;
}
public bool InputMove(bool usemouse, bool reverse) {
var bestnode = new TargetNode();
var bestdot = 0.76f;
if (usemouse && DisableMovementNodes.Contains(ViewDirection))
return false;
if (!reverse && NodeMoveOverrides.TryGetValue(ViewDirection, out int value)) {
bestnode = ConnectedNodes[value];
} else if (reverse && NodeReverseMoveOverrides.TryGetValue(ViewDirection, out int reverseValue)) {
bestnode = ConnectedNodes[reverseValue];
} else {
foreach (var node in ConnectedNodes) {
if (node.Node.Components.Get<PacViewNode>() == null)
continue;
var normal = BasePlayer.Local.PacCamera.WorldRotation.Forward.WithZ(0f).Normal;
if (usemouse)
normal = (BasePlayer.Local.PacCamera.RenderCamera.ScreenNormalToRay(Mouse.Position/Screen.Size).Project(WorldPosition.Distance(node.Node.WorldPosition)))-WorldPosition.WithZ(0f).Normal;
if (reverse)
normal *= -1f;
var dot = normal.Dot((node.Node.WorldPosition-WorldPosition).WithZ(0f).Normal);
if (node.DenyReverseEntry && reverse)
dot = 0f;
#if false
#endif
if (dot > bestdot) {
bestdot = dot;
bestnode = node;
}
}
}
if (bestnode.Node == null)
return false;
DelayedAction = () => {
OnExitNode?.Invoke();
AttachedPlayer = null;
if (reverse)
bestnode.Node.Components.Get<PacViewNode>().ConnectToNode(bestnode.ReverseEntryAngle, PlayerPacCamera.TransitionType.Fade);
else
bestnode.Node.Components.Get<PacViewNode>().ConnectToNode(bestnode.EntryAngle, PlayerPacCamera.TransitionType.Fade);
bestnode.OnTravel?.Invoke();
foreach (var animator in Scene.GetAllComponents<NpcAnimator>())
animator.ResetToIdle();
};
if (AttachedPlayer.PacCamera.DelayMoveAction != null) {
AttachedPlayer.PacCamera.DelayMoveAction.Invoke();
AttachedPlayer.PacCamera.DelayMoveAction = null;
return true;
}
DelayedAction.Invoke();
DelayedAction = null;
return true;
}
[Button] public void ConnectToNode(int direction = 0, PlayerPacCamera.TransitionType transition = PlayerPacCamera.TransitionType.None) {
if (!BasePlayer.Local.IsValid())
return;
foreach (var node in Scene.GetAllComponents<PacViewNode>())
node.AttachedPlayer = null;
ViewDirection = direction;
AttachedPlayer = BasePlayer.Local;
AttachedPlayer.PacCamera.DoTransition = transition;
AttachedPlayer.GameObject.SetParent(GameObject);
if (!BasePlayer.Local.GameObject.Parent.IsValid())
return;
AttachedPlayer.LocalTransform = global::Transform.Zero;
AttachedPlayer.LocalRotation = ViewAngles[ViewDirection];
AttachedPlayer.PacCamera.FullScreenFMV = FullScreenFMV;
InteractionCooldown = true;
OnEnterNode?.Invoke();
}
public bool DrawSnowOnCamera() {
if (SnowOnCamera)
return true;
if (AdditionalSnowOnCameraAngles.Contains(ViewDirection))
return true;
return false;
}
}