2022-06-27 13:02:31 +08:00

426 lines
14 KiB
C#

#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage;
using Rage.Native;
using Control = Rage.GameControl;
#elif SHVDN3
using GTA;
using GTA.Native;
#endif
using System;
using System.Collections.Generic;
namespace LemonUI.Scaleform
{
/// <summary>
/// The Background of the BruteForce Hack Minigame.
/// </summary>
public enum BruteForceBackground
{
/// <summary>
/// A simple Black background.
/// </summary>
Black = 0,
/// <summary>
/// A simple Purple background.
/// </summary>
Purple = 1,
/// <summary>
/// A simple Gray background.
/// </summary>
Gray = 2,
/// <summary>
/// A simple Light Blue background.
/// </summary>
LightBlue = 3,
/// <summary>
/// A Light Blue Wallpaper.
/// </summary>
Wallpaper1 = 4,
/// <summary>
/// A Fade from Gray in the center to Black in the corners.
/// </summary>
DarkFade = 5,
}
/// <summary>
/// The status of the BruteForce Hack after finishing.
/// </summary>
public enum BruteForceStatus
{
/// <summary>
/// The user completed the hack successfully.
/// </summary>
Completed = 0,
/// <summary>
/// The user ran out of time.
/// </summary>
OutOfTime = 1,
/// <summary>
/// The player ran out of lives.
/// </summary>
OutOfLives = 2,
}
/// <summary>
/// Event information after an the BruteForce hack has finished.
/// </summary>
public class BruteForceFinishedEventArgs
{
/// <summary>
/// The final status of the Hack.
/// </summary>
public BruteForceStatus Status { get; }
internal BruteForceFinishedEventArgs(BruteForceStatus status)
{
Status = status;
}
}
/// <summary>
/// Represents the method that is called when the end user finishes the BruteForce hack.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="BruteForceFinishedEventArgs"/> with the hack status.</param>
public delegate void BruteForceFinishedEventHandler(object sender, BruteForceFinishedEventArgs e);
/// <summary>
/// The BruteForce Hacking Minigame shown in multiple missions.
/// </summary>
public class BruteForce : BaseScaleform
{
#region Fields
private static readonly Random random = new Random();
private static readonly Sound soundRowSwitch = new Sound(string.Empty, "HACKING_MOVE_CURSOR");
private static readonly Sound soundRowCompleted = new Sound(string.Empty, "HACKING_CLICK");
private static readonly Sound soundRowFailed = new Sound(string.Empty, "HACKING_CLICK_BAD");
private static readonly Sound soundSuccess = new Sound(string.Empty, "HACKING_SUCCESS");
private int hideTime = -1;
private int output = 0;
private bool firstRun = true;
private bool inProgress = false;
private BruteForceBackground background = BruteForceBackground.Black;
private string word = "LEMONADE";
private int livesTotal = 5;
private int livesCurrent = 5;
private int closeAfter = -1;
private TimeSpan end = TimeSpan.Zero;
private TimeSpan countdown = TimeSpan.Zero;
private bool showLives = true;
#endregion
#region Properties
/// <summary>
/// The Word shown to select in the menu.
/// </summary>
public string Word
{
get => word;
set
{
if (value.Length != 8)
{
throw new ArgumentOutOfRangeException("The word needs to be exactly 8 characters long.", nameof(value));
}
word = value;
CallFunction("SET_ROULETTE_WORD", value);
}
}
/// <summary>
/// The background of the Hacking minigame.
/// </summary>
public BruteForceBackground Background
{
get => background;
set
{
background = value;
CallFunction("SET_BACKGROUND", (int)value);
}
}
/// <summary>
/// The number of Lives of the minigame.
/// </summary>
public int TotalLives
{
get => livesTotal;
set
{
livesTotal = value;
if (livesCurrent > value)
{
livesCurrent = value;
}
CallFunction("SET_LIVES", livesCurrent, value);
}
}
/// <summary>
/// The current number of lives that the player has.
/// </summary>
public int CurrentLives => livesCurrent;
/// <summary>
/// The messages that might appear on success.
/// </summary>
public List<string> SuccessMessages { get; } = new List<string>();
/// <summary>
/// The messages that will appear when the player fails.
/// </summary>
public List<string> FailMessages { get; } = new List<string>();
/// <summary>
/// The time in milliseconds to wait before closing the Hack window automatically.
/// </summary>
/// <remarks>
/// This can be set to -1 to keep the Hack window open.
/// </remarks>
public int CloseAfter
{
get => closeAfter;
set
{
if (value < -1)
{
throw new ArgumentOutOfRangeException("The Closure time can't be under -1.", nameof(value));
}
closeAfter = value;
}
}
/// <summary>
/// If the player can retry the hack after failing.
/// </summary>
public bool CanRetry { get; set; } = false;
/// <summary>
/// The countdown of the Hack minigame.
/// </summary>
public TimeSpan Countdown
{
get => countdown;
set => countdown = value;
}
/// <summary>
/// If the lives of the player should be shown on the top right.
/// </summary>
public bool ShowLives
{
get => showLives;
set
{
showLives = value;
CallFunction("SHOW_LIVES", value);
}
}
/// <summary>
/// If all of the rows should be restarted after the player fails one.
/// </summary>
public bool ResetOnRowFail { get; set; } = true;
#endregion
#region Events
/// <summary>
/// Event triggered when the player finishes a hack.
/// </summary>
public event BruteForceFinishedEventHandler HackFinished;
#endregion
#region Constructors
/// <summary>
/// Creates a new Hacking Scaleform.
/// </summary>
public BruteForce() : base("HACKING_PC")
{
Visible = false;
for (int i = 0; i < 8; i++)
{
SetColumnSpeed(i, 100);
}
}
#endregion
#region Functions
/// <summary>
/// Resets the entire Hacking minigame.
/// </summary>
public void Reset()
{
inProgress = true;
Background = background;
RunProgram(4);
RunProgram(83);
TotalLives = livesTotal;
Word = word;
ShowLives = showLives;
#if RAGEMP
int time = Misc.GetGameTimer();
#elif RPH
uint time = Game.GameTime;
#else
int time = Game.GameTime;
#endif
end = TimeSpan.FromMilliseconds(time) + countdown;
}
/// <summary>
/// Sets the speed of one of the 8 columns.
/// </summary>
/// <param name="index">The index of the column.</param>
/// <param name="speed">The speed of the column.</param>
public void SetColumnSpeed(int index, float speed)
{
if (index >= 8 || index < 0)
{
throw new ArgumentOutOfRangeException("The index needs to be between 0 and 7.", nameof(index));
}
CallFunction("SET_COLUMN_SPEED", index, speed);
}
/// <summary>
/// Runs the specified Hacking program.
/// </summary>
/// <param name="program">The program to open.</param>
public void RunProgram(int program)
{
CallFunction("RUN_PROGRAM", program);
}
/// <summary>
/// Updates the information of the Hacking window.
/// </summary>
public override void Update()
{
#if RAGEMP
int time = Misc.GetGameTimer();
#elif RPH
uint time = Game.GameTime;
#else
int time = Game.GameTime;
#endif
// If there is a time set to hide the Hack window
if (hideTime != -1)
{
// If that time has already passed, go ahead and hide the window
if (hideTime <= time)
{
Visible = false;
hideTime = -1;
return;
}
}
// If this is the first run and is not in progress, reset it
if (firstRun && !inProgress)
{
firstRun = false;
Reset();
}
// If the hack minigame is not in progress but the player can retry and he pressed enter, reset it
if (!inProgress && CanRetry && Controls.IsJustPressed(Control.FrontendAccept))
{
Reset();
hideTime = -1;
}
// If the Hack minigame is in progress
if (inProgress)
{
// If there is a countdown set
if (countdown > TimeSpan.Zero)
{
// Calculate the time left
TimeSpan span = countdown - (TimeSpan.FromMilliseconds(time) - end);
// If is lower or equal than zero, the player failed
if (span <= TimeSpan.Zero)
{
CallFunction("SET_COUNTDOWN", 0, 0, 0);
string err = FailMessages.Count == 0 ? string.Empty : FailMessages[random.Next(FailMessages.Count)];
CallFunction("SET_ROULETTE_OUTCOME", false, err);
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
inProgress = false;
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.OutOfTime));
return;
}
// Otherwise, update the visible time
else
{
CallFunction("SET_COUNTDOWN", span.Minutes, span.Seconds, span.Milliseconds);
}
}
// If the user pressed left, go to the left
if (Controls.IsJustPressed(Control.MoveLeftOnly) || Controls.IsJustPressed(Control.FrontendLeft))
{
soundRowSwitch.PlayFrontend();
CallFunction("SET_INPUT_EVENT", 10);
}
// If the user pressed right, go to the right
else if (Controls.IsJustPressed(Control.MoveRightOnly) || Controls.IsJustPressed(Control.FrontendRight))
{
soundRowSwitch.PlayFrontend();
CallFunction("SET_INPUT_EVENT", 11);
}
// If the user pressed accept, send the selection event
else if (Controls.IsJustPressed(Control.FrontendAccept))
{
output = CallFunctionReturn("SET_INPUT_EVENT_SELECT");
}
// If there is some output to receive
if (output != 0)
{
// If the value is ready, go ahead and check it
if (IsValueReady(output))
{
switch (GetValue<int>(output))
{
case 86: // Hack Completed
string ok = SuccessMessages.Count == 0 ? string.Empty : SuccessMessages[random.Next(SuccessMessages.Count)];
CallFunction("SET_ROULETTE_OUTCOME", true, ok);
soundSuccess.PlayFrontend();
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.Completed));
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
inProgress = false;
break;
case 87: // Row Failed (or lives failed)
livesCurrent--;
CallFunction("SET_LIVES", livesCurrent, livesTotal);
soundRowFailed.PlayFrontend();
if (livesCurrent <= 0)
{
string err = FailMessages.Count == 0 ? string.Empty : FailMessages[random.Next(FailMessages.Count)];
CallFunction("SET_ROULETTE_OUTCOME", false, err);
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
inProgress = false;
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.OutOfLives));
}
break;
case 92: // Row Completed
soundRowCompleted.PlayFrontend();
break;
}
output = 0;
}
}
}
}
#endregion
}
}