680 lines
20 KiB
C#
680 lines
20 KiB
C#
using System;
|
||
using System.IO;
|
||
using System.Runtime.InteropServices;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using UnityEngine.UI;
|
||
using System.Collections;
|
||
|
||
#if UNITY_EDITOR
|
||
using UnityEditor;
|
||
#endif
|
||
|
||
public enum GameMode { pve, pvp }
|
||
public class GameManager : MonoBehaviour
|
||
{
|
||
|
||
#region DEFENCE
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
private struct PROCESS_INFORMATION
|
||
{
|
||
public IntPtr hProcess;
|
||
public IntPtr hThread;
|
||
public int dwProcessId;
|
||
public int dwThreadId;
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||
private struct STARTUPINFO
|
||
{
|
||
public int cb;
|
||
public string lpReserved;
|
||
public string lpDesktop;
|
||
public string lpTitle;
|
||
public int dwX;
|
||
public int dwY;
|
||
public int dwXSize;
|
||
public int dwYSize;
|
||
public int dwXCountChars;
|
||
public int dwYCountChars;
|
||
public int dwFillAttribute;
|
||
public int dwFlags;
|
||
public short wShowWindow;
|
||
public short cbReserved2;
|
||
public IntPtr lpReserved2;
|
||
public IntPtr hStdInput;
|
||
public IntPtr hStdOutput;
|
||
public IntPtr hStdError;
|
||
}
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||
private static extern bool CreateProcess(
|
||
string lpApplicationName,
|
||
string lpCommandLine,
|
||
IntPtr lpProcessAttributes,
|
||
IntPtr lpThreadAttributes,
|
||
bool bInheritHandles,
|
||
uint dwCreationFlags,
|
||
IntPtr lpEnvironment,
|
||
string lpCurrentDirectory,
|
||
ref STARTUPINFO lpStartupInfo,
|
||
out PROCESS_INFORMATION lpProcessInformation);
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true)]
|
||
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true)]
|
||
private static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true)]
|
||
private static extern bool CloseHandle(IntPtr hObject);
|
||
|
||
private const uint WAIT_TIMEOUT = 0x00000102;
|
||
private const uint WAIT_OBJECT_0 = 0x00000000;
|
||
|
||
private System.Collections.IEnumerator CheckLicenseCoroutine()
|
||
{
|
||
// Ждём один кадр, чтобы Unity завершил инициализацию
|
||
yield return null;
|
||
|
||
bool licenseValid = false;
|
||
bool checkCompleted = false;
|
||
//bool licenseValid = true;
|
||
//bool checkCompleted = true;
|
||
string errorMessage = "";
|
||
|
||
// Запускаем проверку в отдельном потоке (совместимо с IL2CPP)
|
||
System.Threading.Thread thread = new System.Threading.Thread(() =>
|
||
{
|
||
try
|
||
{
|
||
licenseValid = CheckLicense();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
errorMessage = ex.Message;
|
||
}
|
||
finally
|
||
{
|
||
checkCompleted = true;
|
||
}
|
||
});
|
||
|
||
thread.IsBackground = true;
|
||
thread.Start();
|
||
|
||
float timerest = 30f;
|
||
while (!checkCompleted && timerest > 0)
|
||
{
|
||
timerest -= Time.deltaTime;
|
||
yield return null;
|
||
}
|
||
|
||
if (!checkCompleted) licenseValid = false;
|
||
|
||
if (!licenseValid)
|
||
{
|
||
yield return new WaitForSeconds(3f);
|
||
|
||
#if UNITY_EDITOR
|
||
EditorApplication.isPlaying = false;
|
||
#else
|
||
Application.Quit();
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
//exclusive
|
||
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
|
||
Screen.SetResolution(1920, 1080, true);
|
||
ClickCenter();
|
||
}
|
||
}
|
||
|
||
|
||
private bool CheckLicense()
|
||
{
|
||
try
|
||
{
|
||
string launcherName = "RRLauncher.exe";
|
||
string launcherPath = null;
|
||
|
||
// Поиск лаунчера рядом с исполняемым файлом
|
||
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||
string pathInBase = Path.Combine(baseDirectory, launcherName);
|
||
|
||
if (File.Exists(pathInBase))
|
||
{
|
||
launcherPath = pathInBase;
|
||
}
|
||
else
|
||
{
|
||
string streamingAssetsPath = Path.Combine(Application.streamingAssetsPath, launcherName);
|
||
if (File.Exists(streamingAssetsPath))
|
||
{
|
||
launcherPath = streamingAssetsPath;
|
||
}
|
||
else
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
STARTUPINFO si = new STARTUPINFO();
|
||
si.cb = Marshal.SizeOf(si);
|
||
PROCESS_INFORMATION pi;
|
||
|
||
string workingDir = Path.GetDirectoryName(launcherPath);
|
||
|
||
bool success = CreateProcess(
|
||
launcherPath,
|
||
null,
|
||
IntPtr.Zero,
|
||
IntPtr.Zero,
|
||
false,
|
||
0,
|
||
IntPtr.Zero,
|
||
workingDir,
|
||
ref si,
|
||
out pi);
|
||
|
||
if (!success) return false;
|
||
uint waitResult = WaitForSingleObject(pi.hProcess, 30000);
|
||
|
||
if (waitResult == WAIT_TIMEOUT)
|
||
{
|
||
CloseHandle(pi.hProcess);
|
||
CloseHandle(pi.hThread);
|
||
return false;
|
||
}
|
||
|
||
// Получение кода возврата
|
||
uint exitCode;
|
||
if (!GetExitCodeProcess(pi.hProcess, out exitCode))
|
||
{
|
||
CloseHandle(pi.hProcess);
|
||
CloseHandle(pi.hThread);
|
||
return false;
|
||
}
|
||
|
||
CloseHandle(pi.hProcess);
|
||
CloseHandle(pi.hThread);
|
||
|
||
if (exitCode == 0)
|
||
{
|
||
ForceToFront();
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception outerEx)
|
||
{
|
||
Debug.LogError($"[License] CRITICAL ERROR in CheckLicense: {outerEx.Message}");
|
||
Debug.LogError($"[License] Stack trace: {outerEx.StackTrace}");
|
||
return false;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
#region FORCE TO FRONT
|
||
[DllImport("user32.dll")]
|
||
private static extern bool SetForegroundWindow(IntPtr hwnd);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern IntPtr GetActiveWindow();
|
||
|
||
public void ForceToFront()//Метод который возвращает фокус а окно после открытия защиты
|
||
{
|
||
IntPtr handle = GetActiveWindow();
|
||
SetForegroundWindow(handle);
|
||
}
|
||
#endregion
|
||
|
||
|
||
|
||
#region клик в центр
|
||
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
public struct INPUT
|
||
{
|
||
public uint type;
|
||
public MOUSEINPUT mi;
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
public struct MOUSEINPUT
|
||
{
|
||
public int dx;
|
||
public int dy;
|
||
public uint mouseData;
|
||
public uint dwFlags;
|
||
public uint time;
|
||
public IntPtr dwExtraInfo;
|
||
}
|
||
|
||
private const uint INPUT_MOUSE = 0;
|
||
private const uint MOUSEEVENTF_MOVE = 0x0001;
|
||
private const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
|
||
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
|
||
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
|
||
|
||
[DllImport("user32.dll")]
|
||
static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||
|
||
public static void ClickCenter()
|
||
{
|
||
int screenX = Screen.currentResolution.width / 2;
|
||
int screenY = Screen.currentResolution.height / 2;
|
||
|
||
// WinAPI использует координаты 0–65535
|
||
int absoluteX = (int)(screenX * 65535 / Screen.currentResolution.width);
|
||
int absoluteY = (int)(screenY * 65535 / Screen.currentResolution.height);
|
||
|
||
INPUT[] inputs = new INPUT[3];
|
||
|
||
// движение мыши
|
||
inputs[0] = new INPUT
|
||
{
|
||
type = INPUT_MOUSE,
|
||
mi = new MOUSEINPUT
|
||
{
|
||
dx = absoluteX,
|
||
dy = absoluteY,
|
||
dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
|
||
}
|
||
};
|
||
|
||
// нажатие
|
||
inputs[1] = new INPUT
|
||
{
|
||
type = INPUT_MOUSE,
|
||
mi = new MOUSEINPUT
|
||
{
|
||
dwFlags = MOUSEEVENTF_LEFTDOWN
|
||
}
|
||
};
|
||
|
||
// отпускание
|
||
inputs[2] = new INPUT
|
||
{
|
||
type = INPUT_MOUSE,
|
||
mi = new MOUSEINPUT
|
||
{
|
||
dwFlags = MOUSEEVENTF_LEFTUP
|
||
}
|
||
};
|
||
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
|
||
}
|
||
|
||
|
||
#endregion
|
||
#endregion
|
||
|
||
#region MAIN
|
||
|
||
public PlayerStatsService StatsService;
|
||
public bool dummyBots = false;
|
||
public int botsAmount = 5;
|
||
public DifficultyPreset dummyPreset;
|
||
public DifficultyPreset MistressPreset;
|
||
|
||
[Header("General")]
|
||
public GameStateMachine stateMachine;
|
||
public Transform slotLeftTop;
|
||
public static FieldModel fieldModel;
|
||
public static GameManager instance;
|
||
public GameObject AdminPanel, NumberPanel, SettingsPointer;
|
||
public TimerManager timerManager;
|
||
public VFX_manager effect;
|
||
public RulesController rulesController;
|
||
|
||
[Header("Menu")]
|
||
public bool menuOpened = true;
|
||
public GameMode gameMode;
|
||
public Transform
|
||
label,
|
||
buttonLeft,
|
||
buttonRight,
|
||
coinPanel,
|
||
label2,
|
||
board,
|
||
noPrizeLabel;
|
||
public GameObject mainMenu;
|
||
|
||
public TMP_FontAsset russo_font;
|
||
|
||
[Header("Scores")]
|
||
public int player1score = 0, player2score = 0, vsBotScore = 0;
|
||
|
||
public int vsBotMoves;
|
||
public float vsBotThinkTime;
|
||
|
||
public VictoryScale botScale, Player1Scale, Player2Scale;
|
||
|
||
|
||
[HideInInspector]
|
||
public UnityEvent OnNoMoney = new UnityEvent();
|
||
public CheckResult lastResult;
|
||
|
||
[Header("Gameplay")]
|
||
public GameObject player1Chip;
|
||
public GameObject player2Chip;
|
||
public GameObject botChip;
|
||
[HideInInspector] public GameObject currentChip;
|
||
public Sprite chipPointer, selectedPanel, deselectedPanel;
|
||
|
||
public RectTransform EngamePanel, TimerImage;
|
||
public RectTransform smallPrizesOut;
|
||
public Image ReplayButton, MenuButton;
|
||
public DrawVisual DrawVisual;
|
||
public TextMeshProUGUI RestartButton;
|
||
|
||
public TextMeshProUGUI TimerLabel; //до выхода в меню..
|
||
public TextMeshProUGUI ToMenuLabel; //это же
|
||
|
||
|
||
public WinPanel winPanel;
|
||
|
||
[HideInInspector] public CellState currentPlayer = CellState.Player2;
|
||
|
||
public int currentColumnIndex = 0;
|
||
public float stepX = 1.215f;
|
||
public float stepY = 1.24f;
|
||
public float startY;
|
||
|
||
public bool moveMade = false;
|
||
|
||
|
||
public float chipXSpeed = 10,
|
||
chipYSpeed = 0,
|
||
chipAcceleration = 20;
|
||
|
||
public Transform[,] chipTransforms;
|
||
|
||
// Кешированные компоненты для избежания GetComponent в DrawAnimation
|
||
[Header("Панели ввода")]
|
||
public TextInput nameInput;
|
||
public TextInput ValutaNameInput;
|
||
public TextInput specialPrizeInput;
|
||
public NumberInput pricePveInput;
|
||
public NumberInput pricePvpInput;
|
||
public NumberInput pricePveRevengeInput;
|
||
public NumberInput prizesAvailableInput;
|
||
public NumberInput pulsesToResetDifficultyInput;
|
||
public NumberInput valutaPerPulseInput;
|
||
public Leaderboard leaderboard;
|
||
public GameObject CharactersConfigurator;
|
||
|
||
public GameObject fastCheckCounters;
|
||
public GameObject checkQrPanel;
|
||
public DualAvatarController avatarController;
|
||
|
||
public PvpPointer pvpPointer;
|
||
public MovesAndTimeCounter movesAndTimeCounter;
|
||
|
||
public GameObject setPasswordPanel;
|
||
public EnterPasswordPanel enterPasswordPanel;
|
||
|
||
public SoundManager soundManager;
|
||
|
||
void Awake()
|
||
{
|
||
Cursor.lockState = CursorLockMode.Locked;
|
||
Cursor.visible = false;
|
||
|
||
Instantiate(new GameObject()).AddComponent(typeof(VfxPrefab));
|
||
|
||
if (instance == null)
|
||
{
|
||
Application.targetFrameRate = 60;
|
||
|
||
instance = this;
|
||
fieldModel = new FieldModel(7, 6);
|
||
chipTransforms = new Transform[6, 7];
|
||
}
|
||
|
||
|
||
UnityMainThreadDispatcher.Initialize();
|
||
InitializePrefs();
|
||
|
||
SettingsManager.Initialize();
|
||
GetComponent<Logger>().Initialize();
|
||
|
||
ComWrapper.Initialize();
|
||
|
||
SingleWallet.Initialize();
|
||
|
||
ComWrapper.PaymentReceived.AddListener(ProcessDifficultyCounter);
|
||
SettingsManager.OnSettingsChanged.AddListener(ProcessDifficultyCounter);
|
||
|
||
soundManager.Initialize();
|
||
GetComponent<NotificationSystem>().Initialize();
|
||
|
||
ProcessDifficultyCounter();
|
||
|
||
GetComponent<In>().Initialize();
|
||
GetComponent<PrizeSystem>().Initialize();
|
||
GetComponent<VideoUtility>().Initialize();
|
||
StatsService = new();
|
||
StatsService.Initialize();
|
||
#if !UNITY_EDITOR
|
||
//Билд
|
||
// Проверка лицензии при старте игры
|
||
StartCoroutine(CheckLicenseCoroutine());
|
||
|
||
dummyBots = false;
|
||
#endif
|
||
|
||
StartCoroutine(AddBotRecordEvery2Days());// добавлять каждые 2 дня запись
|
||
|
||
avatarController.GetComponent<AvatarEventBus>().Initialize();
|
||
avatarController.Initialize();
|
||
|
||
}
|
||
|
||
private static void InitializePrefs()
|
||
{
|
||
PlayerPrefs.SetString("Serial", ExternalFilesGetter.GetSerial());
|
||
PlayerPrefs.SetInt("NeedSpecialPrize", 0);
|
||
|
||
if (!PlayerPrefs.HasKey("SpecialPrize")) PlayerPrefs.SetString("SpecialPrize", "5000р");
|
||
if (!PlayerPrefs.HasKey("Number")) PlayerPrefs.SetString("Number", "70000000000");
|
||
if (!PlayerPrefs.HasKey("ValutaPerImpulse")) PlayerPrefs.SetInt("ValutaPerImpulse", 10);
|
||
if (!PlayerPrefs.HasKey("ValutaName")) PlayerPrefs.SetString("ValutaName", "руб.");
|
||
|
||
if (!PlayerPrefs.HasKey("PricePVP")) PlayerPrefs.SetInt("PricePVP", 10);
|
||
if (!PlayerPrefs.HasKey("PricePVE")) PlayerPrefs.SetInt("PricePVE", 10);
|
||
if (!PlayerPrefs.HasKey("PriceRevenge")) PlayerPrefs.SetInt("PriceRevenge", 10);
|
||
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
stateMachine.ChangeState(new MenuState());
|
||
SettingsManager.OnSettingsChanged.AddListener(pullSettings);
|
||
pullSettings();
|
||
Instantiate(new GameObject()).AddComponent(typeof(VfxPrefab));
|
||
}
|
||
|
||
|
||
void pullSettings()
|
||
{
|
||
SetupCoinPanel();
|
||
}
|
||
|
||
|
||
#if UNITY_EDITOR
|
||
private void Update()
|
||
{
|
||
if (Input.GetKeyDown(KeyCode.G))
|
||
{
|
||
PrizeSystem.GivePrize(new Prize(PrizeType.Hopper, 1));
|
||
}
|
||
if (Input.GetKeyDown(KeyCode.O))
|
||
{
|
||
PlayerPrefs.SetInt("PrizeGivenBuffer", 1);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
|
||
public void SetupCoinPanel()
|
||
{
|
||
bool shouldShowCoinCounter = !SettingsManager.GetBool("Freeplay", false);
|
||
coinPanel.gameObject.SetActive(shouldShowCoinCounter);
|
||
}
|
||
public void spawnChip()
|
||
{
|
||
GameObject chipToSpawn;
|
||
if (gameMode == GameMode.pvp)
|
||
chipToSpawn = currentPlayer == CellState.Player1 ? player1Chip : player2Chip;
|
||
else
|
||
chipToSpawn = currentPlayer == CellState.Player1 ? player1Chip : botChip;
|
||
|
||
|
||
currentChip = Instantiate(chipToSpawn, new Vector2(0, 20), Quaternion.identity);
|
||
if (currentPlayer == CellState.Player2)
|
||
currentChip.GetComponentInChildren<SpriteRenderer>().sprite = CharacterController.CurrentChipImage;
|
||
}
|
||
public void setChipTransformToArray(int row, int col, Transform entryTransform)
|
||
{
|
||
chipTransforms[row, col] = entryTransform;
|
||
}
|
||
|
||
public void SwitchPlayer()
|
||
{
|
||
currentPlayer = currentPlayer == CellState.Player1 ? CellState.Player2 : CellState.Player1;
|
||
}
|
||
|
||
public void SetMode(GameMode mode) => gameMode = mode;
|
||
|
||
public void InitVictoryScales()
|
||
{
|
||
//Включить и обнулить нужные шкалы
|
||
player1score = 0;
|
||
player2score = 0;
|
||
vsBotScore = 0;
|
||
switch (gameMode)
|
||
{
|
||
case GameMode.pvp:
|
||
botScale.gameObject.SetActive(false);
|
||
Player1Scale.gameObject.SetActive(true);
|
||
Player2Scale.gameObject.SetActive(true);
|
||
|
||
Player1Scale.SetSlotsCount(5);
|
||
Player2Scale.SetSlotsCount(5);
|
||
|
||
|
||
Player1Scale.ResetScale();
|
||
Player2Scale.ResetScale();
|
||
break;
|
||
case GameMode.pve:
|
||
botScale.gameObject.SetActive(true);
|
||
botScale.SetSlotsCount(botsAmount);
|
||
|
||
Player1Scale.gameObject.SetActive(false);
|
||
Player2Scale.gameObject.SetActive(false);
|
||
|
||
botScale.ResetScale();
|
||
break;
|
||
}
|
||
}
|
||
|
||
public int GetMovesCount()
|
||
{
|
||
if (chipTransforms == null) return 0;
|
||
|
||
int count = 0;
|
||
int rows = chipTransforms.GetLength(0);
|
||
int cols = chipTransforms.GetLength(1);
|
||
|
||
for (int r = 0; r < rows; r++)
|
||
for (int c = 0; c < cols; c++)
|
||
if (chipTransforms[r, c] != null)
|
||
count++;
|
||
|
||
return count;
|
||
}
|
||
|
||
public void ProcessDifficultyCounter()//вызывается каждый раз, когда приходит COIN с платы или обновились настройки
|
||
{
|
||
string previous = PlayerPrefs.GetString("PreviousBaseBotDifficulty", "Обычная");
|
||
string difficultyType = SettingsManager.GetString("BaseBotDifficulty", "?");
|
||
|
||
//Инициализация
|
||
if(!PlayerPrefs.HasKey("PulsesToResetDifficulty"))
|
||
PlayerPrefs.SetInt("PulsesToResetDifficulty", 100);
|
||
|
||
//если переход с повышенной на обычную - сброс счетчика
|
||
if (previous == "Повышенная" && difficultyType != previous)
|
||
{
|
||
PlayerPrefs.SetInt("PulsesCounter", 0);
|
||
}
|
||
PlayerPrefs.SetString("PreviousBaseBotDifficulty", difficultyType);
|
||
|
||
|
||
if (difficultyType == "Повышенная")
|
||
{
|
||
int pulses = PlayerPrefs.GetInt("PulsesCounter", 0);
|
||
int pulsesToResetDifficulty = PlayerPrefs.GetInt("PulsesToResetDifficulty", 10);
|
||
|
||
if (pulses >= pulsesToResetDifficulty)
|
||
{
|
||
PlayerPrefs.SetInt("PulsesCounter", 0);
|
||
Setting difficultySetting = SettingsManager.Container.TryGetSetting("BaseBotDifficulty");
|
||
difficultySetting.StringValue = "Обычная";
|
||
SettingsManager.OnSettingsChanged?.Invoke();
|
||
SettingsManager.SaveContainerToFile();
|
||
AdminPanel.GetComponent<SettingsScreen>().RefreshViews();
|
||
print("Сложность сброшена");
|
||
}
|
||
}
|
||
}
|
||
public void upgradeFeeshkaDifficulty()
|
||
{
|
||
string difficultyType = SettingsManager.GetString("BaseBotDifficulty", "Обычная");
|
||
if (difficultyType == "Обычная")
|
||
{
|
||
PlayerPrefs.SetInt("PulsesCounter", 0);
|
||
Setting difficultySetting = SettingsManager.Container.TryGetSetting("BaseBotDifficulty");
|
||
difficultySetting.StringValue = "Повышенная";
|
||
SettingsManager.OnSettingsChanged?.Invoke();
|
||
SettingsManager.SaveContainerToFile();
|
||
AdminPanel.GetComponent<SettingsScreen>().RefreshViews();
|
||
print("Сложность повышена");
|
||
}
|
||
}
|
||
|
||
|
||
IEnumerator AddBotRecordEvery2Days()
|
||
{
|
||
while (true)
|
||
{
|
||
yield return new WaitForSeconds(3600f); // ✅ Проверка каждый час
|
||
|
||
double lastRecordTime = PlayerPrefs.GetFloat("LastBotRecord", 0);
|
||
double currentTime = GetUnixTimestamp();
|
||
|
||
if (currentTime - lastRecordTime >= 172800f)
|
||
{
|
||
StatsService.AddDailyRecord();
|
||
PlayerPrefs.SetFloat("LastBotRecord", (float)currentTime);
|
||
PlayerPrefs.Save();
|
||
}
|
||
}
|
||
}
|
||
double GetUnixTimestamp() => (DateTime.UtcNow - new System.DateTime(1970, 1, 1)).TotalSeconds;
|
||
#endregion
|
||
} |