Files
zzz_REBIRTH__Utils/Score/LockPicking/Scripts/Cipher.cs
2025-06-04 16:44:53 +09:30

531 lines
18 KiB
C#

using UnityEngine.Events;
/*
* Thank you for using my Lockpicking asset! I hope it works great in your game. You can extend it to suit your game by
* manipulating some of the code below. For instance, if your player can have a various level of "lockpicking" skill,
* you may consider multiplying the value of lockGive by their skill, so that a higher skilled player would find it
* easier to open a lock.
*
* Enjoy!
*/
namespace Lockpicking
{
//[ExecuteInEditMode]
[RequireComponent(typeof(LockEmissive))]
public class Cipher : MonoBehaviour
{
[Header("Speed Settings")]
[Tooltip("Maximum shake distance per shake change.")]
[SerializeField]
private float maxShake = 0.5f;
[Tooltip("Amount of time between shake changes when shaking.")]
[SerializeField]
private float shakeTime = 0.1f;
[Tooltip("Speed of the wheel when turning.")]
public float turnSpeed = 180f;
[Header("Lock Setup")]
[Tooltip("Number of symbols on the wheel. Should be spaced evenly.")]
public int symbolCount = 18;
[Tooltip("How close do we have to be to perfectly aligned for the placement of a wheel to count?")]
public float closeEnough = 3f;
[Tooltip("If true, wheels will \"snap\" into a new random spot on setup. If false, they will rotate to those spots.")]
public bool quickReset;
[Tooltip("If true, the active wheel will be highlighted using the emissive map of the material.")]
public bool highlightActiveWheel = true;
[Tooltip("If true, wheels will move to the closest spot when the player stops input movement.")]
public bool moveToClosestSpot;
[Header("Lock Details")]
[Tooltip("When true, the lock has been opened.")]
public bool isOpen;
[Tooltip("When true, the lock will reset itself on awake. Set this to false if you are choosing a specific combination.")]
public bool resetOnAwake;
[Header("Animation Trigger Strings")] public string openTrigger = "Open";
public string closeTrigger = "Close";
// Audio Settings
[Range(0f, 1f)] public float clickVolumeMin = 0.1f;
[Range(0f, 1f)] public float clickVolumeMax = 0.4f;
[Range(0f, 1f)] public float squeekVolumeMin = 0.1f;
[Range(0f, 1f)] public float squeekVolumeMax = 0.4f;
[Range(0, 100)] public int squeekChance = 50;
[Range(0f, 15f)] public float squeekRate = 3.5f;
public float[] unlockAngle; // All the unlock angles for each wheel
public float[] startAngle; // All the starting angles for each wheel
public float[] turnedDistance; // amount each wheel has been turned
public float[] turnedDistancePrev; // amount each wheel has been turned
public bool[] isMoving; // Records whether the wheels are moving
public bool playAudioOnSetup = true;
[Header("Plumbing")] public GameObject[] wheels;
public LocksetAudio audioClick;
public LocksetAudio audioSqueek;
public LocksetAudio audioJiggle;
public LocksetAudio audioJiggle2;
public LocksetAudio audioJiggle3;
public LocksetAudio audioOpen;
// Events
private readonly UnityEvent lockOpen = new UnityEvent();
// Private variables
private int _activeWheel;
private int _closeTrigger;
private LocksetAudio _locksetAudio;
// Private animation hashes
private int _openTrigger;
private Animator animator;
private float getReadyPostReadyTimer = 0.3f;
private bool isReady; // WHen true, player can interact
private bool isShaking; // When true, the lock is shaking (trying to be opened but in the wrong combination)
private LockEmissive lockEmissive;
private Vector3 preshake; // Saves the pre-shake angles
private float shakeTimer; // Counter for the shake Time
// private float _speed;
private float squeekTimer;
public bool VisualizeSolution { get; set; }
public bool VisualizeStart { get; set; }
public float ActiveWheelStart
{
get => startAngle[ActiveWheel()];
set => startAngle[ActiveWheel()] = value;
}
public float ActiveWheelSolution
{
get => unlockAngle[ActiveWheel()];
set => unlockAngle[ActiveWheel()] = value;
}
private void Awake()
{
squeekTimer = squeekRate;
_locksetAudio = GetComponent<LocksetAudio>();
animator = GetComponent<Animator>();
lockEmissive = GetComponent<LockEmissive>();
if (!highlightActiveWheel)
lockEmissive.highlightRenderer = null;
_openTrigger = Animator.StringToHash(openTrigger);
_closeTrigger = Animator.StringToHash(closeTrigger);
if (resetOnAwake) ResetLock();
}
private void Update()
{
// Do actions to get ready, if we are not ready
if (!isReady)
{
GetReady();
if (playAudioOnSetup)
DoAudio();
return;
}
if (getReadyPostReadyTimer > 0)
getReadyPostReadyTimer -= Time.deltaTime;
if (moveToClosestSpot)
MoveToClosestSpot();
ResetMovement();
if (highlightActiveWheel) lockEmissive.SetHighlightRenderer(wheels[_activeWheel].GetComponent<Renderer>());
DoAudio();
}
private void OnValidate()
{
#if UNITY_EDITOR
if (unlockAngle.Length != wheels.Length )
{
Debug.Log("Updating Lengths");
unlockAngle = new float[wheels.Length];
startAngle = new float[wheels.Length];
turnedDistance = new float[wheels.Length];
isMoving = new bool[wheels.Length];
}
if (_visualizeSolution)
SetRotationToSolution();
else if (_visualizeStart)
SetRotationToStart();
else
SetRotationToZero();
for (int i = 0; i < wheels.Length; i++)
{
unlockAngle[i] = ClosestSpot(unlockAngle[i]);
}
#endif
}
public int ActiveWheel()
{
return _activeWheel;
}
public void EditorOnValidate()
{
#if UNITY_EDITOR
EditorUtility.SetDirty(this);
#endif
OnValidate();
}
private void SetRotationToSolution()
{
for (var i = 0; i < wheels.Length; i++)
{
var eulerAngles = wheels[i].transform.localEulerAngles;
eulerAngles.x = unlockAngle[i];
wheels[i].transform.localEulerAngles = eulerAngles;
}
}
private void SetRotationToStart()
{
for (var i = 0; i < wheels.Length; i++)
{
var eulerAngles = wheels[i].transform.localEulerAngles;
eulerAngles.x = startAngle[i];
wheels[i].transform.localEulerAngles = eulerAngles;
}
}
private void SetRotationToZero()
{
for (var i = 0; i < wheels.Length; i++) wheels[i].transform.localEulerAngles = new Vector3();
}
public float DistanceLeft(int i)
{
if (turnedDistance.Length >= i + 1)
{
var left = unlockAngle[i] - turnedDistance[i];
if (left < -350)
left += 360;
if (left > 350)
left -= 360;
return left;
}
return 9999;
}
private void DoAudio()
{
for (var i = 0; i < turnedDistance.Length; i++)
{
var turnSpeed = 0f;
if (turnedDistance[i] != turnedDistancePrev[i])
{
if (getReadyPostReadyTimer <= 0 || playAudioOnSetup)
{
if (audioSqueek)
// Squeek
if (squeekRate > 0)
{
squeekTimer -= Time.deltaTime;
if (squeekTimer <= 0)
{
if (UnityEngine.Random.Range(0, 100) < squeekChance)
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioSqueek");
audioSqueek.PlayAudioClip(UnityEngine.Random.Range(squeekVolumeMin, squeekVolumeMax));
}
squeekTimer = squeekRate;
}
}
if (audioClick)
{
// Clicks
turnSpeed = turnedDistance[i] - turnedDistancePrev[i];
var turnedMod = turnedDistance[i] % (360 / symbolCount);
var turnedModPrev = turnedDistancePrev[i] % (360 / symbolCount);
if ((turnSpeed > 0 || turnSpeed < -350) && (turnedMod < turnedModPrev || turnedMod > 0 && turnedModPrev < 0))
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioClick 1");
audioClick.PlayAudioClip(UnityEngine.Random.Range(clickVolumeMin, clickVolumeMax));
}
else if ((turnSpeed < 0 || turnSpeed > 350) && (turnedMod > turnedModPrev || turnedMod < 0 && turnedModPrev > 0))
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioClick 2");
audioClick.PlayAudioClip(UnityEngine.Random.Range(clickVolumeMin, clickVolumeMax));
}
else if (turnedMod == 0)
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioClick 3");
audioClick.PlayAudioClip(UnityEngine.Random.Range(clickVolumeMin, clickVolumeMax));
}
}
}
turnedDistancePrev[i] = turnedDistance[i];
}
}
}
public int NextWheelIndex()
{
return _activeWheel + 1 < wheels.Length ? _activeWheel + 1 : 0;
}
public int PrevWheelIndex()
{
return _activeWheel - 1 < 0 ? wheels.Length - 1 : _activeWheel - 1;
}
public void SelectWheel(int value)
{
_activeWheel = value < wheels.Length && value >= 0 ? value : 0;
}
public void ResetMovement()
{
for (var i = 0; i < wheels.Length; i++) isMoving[i] = false;
}
public void MoveToClosestSpot()
{
for (var i = 0; i < wheels.Length; i++)
// Only run this if the wheel is not moving (i.e. being moved by the player)
if (!isMoving[i])
{
if (turnedDistance[i] < 0) turnedDistance[i] += 360;
/*
float smallestDistance = 360f;
float closestRotation = 0f;
for (int d = 0; d < 360 / symbolCount; d++)
{
float diff = d * (360 / symbolCount);
if (Mathf.Abs(Mathf.Abs(turnedDistance[i]) - diff) < smallestDistance)
{
smallestDistance = Mathf.Abs(Mathf.Abs(turnedDistance[i]) - diff);
closestRotation = diff;
}
}
MoveTowards(i, closestRotation);
*/
MoveTowards(i, ClosestSpot(Mathf.Abs(turnedDistance[i])));
}
}
private float ClosestSpot(float value)
{
var smallestDistance = 360f;
var closestRotation = 0f;
for (var d = -360; d < 360 / symbolCount; d++)
{
float diff = d * (360 / symbolCount);
if (Mathf.Abs(value - diff) < smallestDistance)
{
smallestDistance = Mathf.Abs(value - diff);
closestRotation = diff;
}
}
return closestRotation;
}
public void TryOpen()
{
if (!isShaking)
preshake = transform.localEulerAngles;
if (!isOpen)
{
for (var i = 0; i < wheels.Length; i++)
if (Mathf.Abs(DistanceLeft(i)) > closeEnough)
{
Shake();
return;
}
isOpen = true;
animator.SetTrigger(_openTrigger);
//Log.Out("Lockpicking-TryOpen PLAY SOUND: audioPadlockOpen");
audioOpen.PlayOnce();
// Invoke the event for any other scripts that are listening
lockOpen.Invoke();
}
}
public void StopShaking()
{
transform.localEulerAngles = preshake;
isShaking = false;
if (audioJiggle)
{
audioJiggle.StopLoop();
if (audioJiggle2 != null)
audioJiggle2.StopLoop();
if (audioJiggle3 != null)
audioJiggle3.StopLoop();
}
}
private void Shake()
{
if (!isShaking)
isShaking = true;
shakeTimer -= Time.deltaTime;
if (shakeTimer <= 0)
{
// Start with the current values
var newShake = preshake;
// Add some modification
//newShake.z += UnityEngine.Random.Range(-maxShake, maxShake);
newShake.x += UnityEngine.Random.Range(-maxShake, maxShake);
newShake.y += UnityEngine.Random.Range(-maxShake, maxShake);
// Set the value + modification
transform.localEulerAngles = newShake;
// Reset the timer
shakeTimer = shakeTime;
}
if (audioJiggle)
{
audioJiggle.PlayLoop();
if (audioJiggle2 != null)
audioJiggle2.PlayLoop();
if (audioJiggle3 != null)
audioJiggle3.PlayLoop();
}
}
public void GetReady()
{
var readyCount = 0;
for (var i = 0; i < wheels.Length; i++)
{
var speed = 1;
var distanceLeft = startAngle[i] - turnedDistance[i];
if (VisualizeSolution) distanceLeft = unlockAngle[i] - turnedDistance[i];
if (distanceLeft < 0) speed = -1;
var turnAmount = speed * turnSpeed * Time.deltaTime;
if (Mathf.Abs(distanceLeft) < Mathf.Abs(turnAmount))
{
turnAmount = distanceLeft;
readyCount++;
}
if (quickReset || VisualizeSolution)
{
turnAmount = distanceLeft;
readyCount++;
}
SetTurnedDistance(i, turnAmount);
RotateWheel(wheels[i].transform, turnAmount);
}
if (readyCount == wheels.Length)
isReady = true;
}
public void MoveActiveWheel(int speed)
{
MoveWheel(_activeWheel, speed);
}
public void MoveWheel(int i, int speed, float destination = 999f)
{
if (isReady)
{
isMoving[i] = true; // Mark this wheel as currently moving
var turnAmount = speed * turnSpeed * Time.deltaTime;
if (destination != 999)
if (Mathf.Abs(destination - turnedDistance[i]) < Mathf.Abs(turnAmount))
turnAmount = destination - turnedDistance[i];
SetTurnedDistance(i, turnAmount);
RotateWheel(wheels[i].transform, turnAmount);
}
}
private void RotateWheel(Transform transform, float value)
{
transform.Rotate(value, 0.0f, 0.0f, Space.Self);
}
public void MoveTowards(int i, float closestRotation)
{
if (turnedDistance[i] > closestRotation)
MoveWheel(i, -1, closestRotation);
else
MoveWheel(i, 1, closestRotation);
}
public void SetTurnedDistance(int i, float distance)
{
turnedDistance[i] += distance;
if (turnedDistance[i] > 360)
turnedDistance[i] -= 360;
if (turnedDistance[i] < -360)
turnedDistance[i] += 360;
}
public void ResetLock()
{
Debug.Log("ResetLock");
isOpen = false;
isReady = false;
animator.SetTrigger(_closeTrigger);
// Shuffle each wheel
for (var i = 0; i < wheels.Length; i++)
{
unlockAngle[i] = RandomAngle();
startAngle[i] = RandomAngle();
}
}
private float RandomAngle()
{
return UnityEngine.Random.Range(0, symbolCount) * (360 / symbolCount) - 180;
}
}
}