C#:
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using System.Collections.Generic;
using Oxide.Core.Libraries;
using Newtonsoft.Json.Linq;
using System;
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using Oxide.Game.Rust.Cui;
//НИ В КОЕМ СЛУЧАЕ НЕ ТРОГАТЬ ПЛАГИН!!!
//НЕВЕРНЫЕ ИЗМЕНЕНИЯ МОГУТ ПОВЛЕЧЬ ЗА СОБОЙ БЛОКИРОВКУ АККАУНТА НА САЙТЕ,
// А ТАКЖЕ ОШИБОЧНОЕ СПИСАНИЕ СРЕДСТВ
namespace Oxide.Plugins
{
[Info("RewardPlay", "sudo", "0.0.82")]
public class RewardPlay : RustPlugin
{
[PluginReference]
private Plugin MultiFighting, NoSteam, TirifyGamePluginPirate;
private Dictionary<ulong, Timer> playerAfkTimers = new Dictionary<ulong, Timer>();
private Dictionary<ulong, PlayerSession> playerSessions = new Dictionary<ulong, PlayerSession>();
private Dictionary<ulong, PlayerActive> playerActive = new Dictionary<ulong, PlayerActive>();
private const string ApiCheck = "https://rewardplay.org/api/server/check";
private const string ApiImport = "https://rewardplay.org/api/server/import";
private const string ApiPluginInfo = "https://rewardplay.org/api/server/plugin";
private string versionPlugin;
private Timer ImportTimer;
private static string ipAddress;
private static int? portQuery;
private static int? portGame;
private static bool isPirate = false;
private float price;
private string status;
private long LastImportTime = 0;
private bool afkRecord;
private void ApiPost(string api, Dictionary<string, object> postData, Action<int, JObject> callback) {
string jsonData = JsonConvert.SerializeObject(postData, Formatting.None);
webrequest.Enqueue(api, jsonData, (code, response) =>
{
if (code == 200 && response != null) callback(code, JObject.Parse(response));
else PrintError($"Ошибка API: {code}");
}, this, RequestMethod.POST, new Dictionary<string, string> { { "Content-Type", "application/json" }, { "User-Agent", "RewardPlay PLUGIN" }, { "secretKey", config.secretKey } });
}
private void ApiGet(string api, Dictionary<string, object> getData, Action<int, JObject> callback)
{
if (getData.Count != 0){
var queryString = string.Join("&", getData.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value.ToString())}"));
api = $"{api}?{queryString}";
}
webrequest.Enqueue(api , null, (code, response) =>
{
if (code == 200 && response != null) callback(code, JObject.Parse(response));
else PrintError($"Ошибка API: {code}");
}, this, RequestMethod.GET, new Dictionary<string, string> { { "Content-Type", "application/json" } });
}
private void OnServerInitialized()
{
ipAddress = ConVar.Server.ip;
portQuery = ConVar.Server.queryport;
portGame = ConVar.Server.port;
if(!portQuery.HasValue) {
PrintError($"Query port сервера не указан");
return;
}
if(!portGame.HasValue) {
PrintError($"Game port сервера не указан");
return;
}
if (MultiFighting != null || NoSteam != null || TirifyGamePluginPirate != null) isPirate = true;
versionPlugin = GetType().GetCustomAttribute<InfoAttribute>().Version.ToString();
var postData = new Dictionary<string, object> { { "portQuery", portQuery}, { "portGame", portGame }, { "ip", ipAddress }, { "isPirate", isPirate }, { "versionPlugin", versionPlugin} };
ApiPost(ApiCheck, postData, (code, response) =>
{
if (response["success"]?.ToObject<bool>() == true) {
price = response["price"].ToObject<float>();
status = response["status"].ToString();
afkRecord = response["afkRecord"].ToObject<bool>();
Puts("Все проверки пройдены, запускаем отправку информацию о игроках...");
//ВРЕМЯ ОТПРАВКИ НЕ МЕНЯТЬ!!!
ImportTimer = timer.Every(300f, () =>
{
if (config.logging) Puts("Отправляем инфорацию о игроках");
ImportPlayers();
});
LoadData();
cmd.AddChatCommand("reward", this, "OpenUICommand");
cmd.AddChatCommand("rewardplay", this, "OpenUICommand");
cmd.AddChatCommand("rewardClose", this, "CloseUICommand");
foreach(var player in BasePlayer.activePlayerList)
OnPlayerConnected(player, true);
foreach (var target in playerSessions.Keys.ToList()) {
var session = playerSessions[target];
bool online = BasePlayer.activePlayerList.Any(p => p.userID == target);
if (!online && session.Connect > session.Disconnect) {
playerSessions.Remove(target);
}
}
ApiGet(ApiPluginInfo, new Dictionary<string, object> {}, (code, response) =>
{
if(response["success"]?.ToObject<bool>() == true) {
var actualVersion = response["version"].ToString();
if (versionPlugin != actualVersion) PrintError($"Актуальная версия плагина (v {actualVersion}), скорее обновитесь!");
}
});
}
else {
PrintError($"{response["message"].ToString()}");
if (response["recheck"]?.ToObject<bool>() == true) {
timer.Once(5, OnServerInitialized);
}
}
});
}
private void ImportPlayers()
{
var players = new Dictionary<ulong, Dictionary<string, long>>{};
long importTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
foreach(var entry in playerSessions)
{
ulong steamId = entry.Key;
PlayerSession session = entry.Value;
long connect = session.Connect;
long disconnect = session.Disconnect;
long earned = 0;
bool online = disconnect > connect;
bool giveEarned = false;
players[steamId] = new Dictionary<string, long> { { "connect", connect }, { "disconnect", disconnect } };
if (playerActive.TryGetValue(entry.Key, out PlayerActive active) && afkRecord)
{
long lastActive = active.LastActiveTime;
long activeAfterAFK = active.ActiveAfterAFK;
players[steamId].Add("lastActive", lastActive);
players[steamId].Add("activeAfterAFK", activeAfterAFK);
if (active.openedMenu && lastActive + 590 <= importTime && !online) {
BasePlayer player = BasePlayer.FindByID(steamId);
if (player != null && player.IsConnected) StartAfkPlayerTell(player);
}
else giveEarned = true;
}
if (giveEarned) {
if (status != "active") {
continue;
}
else if (online) {
session.Earned = 0f;
}
else if (session.Earned == 0f) {
earned = importTime - connect;
}
else if (session.Earned > 0f) {
earned = importTime - LastImportTime;
}
session.Earned += earned * (price / 3600);
}
}
LastImportTime = importTime;
var postData = new Dictionary<string, object> { { "players", players }, { "secretKey", config.secretKey }, { "sendTime", importTime } };
ApiPost(ApiImport, postData, (code, response) =>
{
if (response["success"]?.ToObject<bool>() == true)
{
if (config.logging) Puts("Инфорация отправлена");
price = response["price"].ToObject<float>();
status = response["status"].ToString();
afkRecord = response["afkRecord"].ToObject<bool>();
if (!afkRecord && playerAfkTimers.Count > 0) StopAllAfkPlayerTell();
if (status == "inactive") {
foreach (var entry in playerActive)
{
BasePlayer player = BasePlayer.FindByID(entry.Key);
if (player != null && player.IsConnected && !entry.Value.alerted && entry.Value.openedMenu)
{
entry.Value.alerted = true;
OpenUI(player);
}
}
} else if (status == "active") playerActive.Values.ToList().ForEach(p => p.alerted = false);
}
else PrintError($"{response["message"].ToString()}");
});
}
private void OnPlayerConnected(BasePlayer player, bool pluginLoaded = false)
{
ulong steamId = player.userID;
long playerConnectLax = GetTimeStampConnected(player);
long playerConnect = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (!playerSessions.TryGetValue(steamId, out PlayerSession playerSession))
{
playerSession = new PlayerSession
{
Connect = pluginLoaded ? playerConnectLax : playerConnect,
Disconnect = 0
};
playerSessions.Add(steamId, playerSession);
}
else
{
if (pluginLoaded)
{
if (playerConnectLax - 300 >= playerSession.Connect)
return;
if (playerSession.Connect < playerConnectLax)
playerSession.Connect = playerConnectLax;
if (playerSession.Disconnect > playerConnectLax)
playerSession.Disconnect = 0;
}
else
{
playerSession.Connect = playerConnect;
}
}
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
ulong steamId = player.userID;
long playerConnectLax = GetTimeStampConnected(player);
long playerDisconnect = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (!playerSessions.TryGetValue(steamId, out PlayerSession playerSession))
{
playerSession = new PlayerSession
{
Connect = playerConnectLax,
Disconnect = playerDisconnect,
Earned = 0.0f
};
playerSessions.Add(steamId, playerSession);
}
else
{
playerSession.Disconnect = playerDisconnect;
playerSession.Earned = 0.0f;
}
StopAfkPlayerTell(player);
}
private void OnPlayerInput(BasePlayer player, InputState input)
{
if (!afkRecord) return;
if (player == null && !player.IsConnected) return;
Vector3 currentPosition = player.transform.position;
if (!playerActive.TryGetValue(player.userID, out PlayerActive active))
{
active = new PlayerActive
{
LastActiveTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
LastPosition = currentPosition
};
playerActive.Add(player.userID, active);
}
else if (Vector3.Distance(currentPosition, active.LastPosition) > 3)
{
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
//НЕ ТРОГАТЬ - ОТ ЭТОГО НЕ ЗАВИСИТ ВРЕМЯ ЧЕРЕЗ СКОЛЬКО БУДЕТ ИГРОК В АФК!!!
if (currentTime - active.LastActiveTime > 600) {
active.ActiveAfterAFK = currentTime;
StopAfkPlayerTell(player);
}
active.LastActiveTime = currentTime;
active.LastPosition = currentPosition;
}
}
object OnPlayerRespawn(BasePlayer player, BasePlayer.SpawnPoint spawnPoint)
{
Vector3 currentPosition = spawnPoint.pos;
if (!playerActive.TryGetValue(player.userID, out PlayerActive active)) {
active = new PlayerActive
{
LastActiveTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
LastPosition = currentPosition
};
playerActive.Add(player.userID, active);
}
active.LastPosition = currentPosition;
return null;
}
private void OpenUICommand(BasePlayer player)
{
OpenUI(player);
}
private void CloseUICommand(BasePlayer player)
{
CuiHelper.DestroyUi(player, UI_MAIN);
}
string UI_MAIN = "MAIN_PANEL";
private void OpenUI(BasePlayer player/*, bool afk = false*/)
{
CuiHelper.DestroyUi(player, UI_MAIN);
var url = (status == "inactive"/* || afk*/) ? "https://rewardplay.org/assets/images/plugin/empty.png" : "https://rewardplay.org/assets/images/plugin/default.png";
PlayerSession playerSession = playerSessions[player.userID];
playerActive[player.userID].openedMenu = true;
var element = new CuiElementContainer();
element.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0.343 0.115",
AnchorMax = "0.64 0.284" },
Image = { Color = "1 1 1 0" },
//CursorEnabled = true
}, "Overlay", UI_MAIN);
element.Add(new CuiElement
{
Parent = UI_MAIN,
Components =
{
new CuiRawImageComponent
{
Url = url,
},
new CuiRectTransformComponent
{
AnchorMin = "0 0",
AnchorMax = "1 1",
}
}
});
element.Add(new CuiButton
{
Button =
{
Command = "chat.say /rewardClose",
Color = "1 1 1 0",
},
RectTransform =
{
AnchorMin = "0.94 0.8",
AnchorMax = "0.988 0.97",
}
}, UI_MAIN);
// if (afk) {
// textUI(element, "Вы были неактивны более 10 минут. Начисление на баланс возобновится, как только вы продолжите играть.");
// }
if (status == "inactive") {
textUI(element, "У администратора сервера закончились средства. Попросите его пополнить баланс или выберите другой сервер на нашем сайте.");
}
else {
ActiveUI(element, playerSession);
}
CuiHelper.AddUi(player, element);
}
private void textUI(CuiElementContainer element, string text) {
element.Add(new CuiElement
{
Parent = UI_MAIN,
Components =
{
new CuiTextComponent
{
Text = text,
FontSize = 12,
Font = "robotocondensed-regular.ttf",
Color = "0.902 0.902 0.902 1",
Align = TextAnchor.UpperLeft,
},
new CuiRectTransformComponent
{
OffsetMin = "105 -10",
OffsetMax = "-30 -50",
},
}
});
element.Add(new CuiElement
{
Parent = UI_MAIN,
Components =
{
new CuiTextComponent
{
Text = $"Оплата приостановлена!",
FontSize = 12,
Font = "robotocondensed-regular.ttf",
Color = "0.541 0.537 0.537 1",
Align = TextAnchor.MiddleLeft,
},
new CuiRectTransformComponent
{
OffsetMin = "135 95",
OffsetMax = "-30 -10",
},
}
});
}
private void ActiveUI(CuiElementContainer element, PlayerSession playerSession) {
element.Add(new CuiElement
{
Parent = UI_MAIN,
Components =
{
new CuiTextComponent
{
Text = $"Оплата за час: <color='white'><size='11'>{price}₽</size></color>",
FontSize = 12,
Font = "robotocondensed-regular.ttf",
Color = "0.439 0.439 0.439 1",
Align = TextAnchor.MiddleLeft,
},
new CuiRectTransformComponent
{
OffsetMin = "130 60",
OffsetMax = "50 -40",
},
}
});
element.Add(new CuiElement
{
Parent = UI_MAIN,
Components =
{
new CuiTextComponent
{
Text = $"Время игры: <color='white'><size='11'>{ConvertSecondsToTimeFormat((int)(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - playerSession.Connect))}</size></color>",
FontSize = 12,
Font = "robotocondensed-regular.ttf",
Color = "0.439 0.439 0.439 1",
Align = TextAnchor.MiddleLeft,
},
new CuiRectTransformComponent
{
OffsetMin = "130 42",
OffsetMax = "50 -60",
},
}
});
element.Add(new CuiElement
{
Parent = UI_MAIN,
Components =
{
new CuiTextComponent
{
Text = $"Заработано за сеанс: <color='white'><size='11'>~ {(float)Math.Round(playerSession.Earned, 2)}₽</size></color>",
FontSize = 12,
Font = "robotocondensed-regular.ttf",
Color = "0.439 0.439 0.439 1",
Align = TextAnchor.MiddleLeft,
},
new CuiRectTransformComponent
{
OffsetMin = "130 23",
OffsetMax = "50 -83",
},
}
});
element.Add(new CuiElement
{
Parent = UI_MAIN,
Components =
{
new CuiTextComponent
{
Text = $"*Информация обновляется каждые 5 минут",
FontSize = 8,
Font = "robotocondensed-regular.ttf",
Color = "0.357 0.357 0.357 1",
Align = TextAnchor.MiddleLeft,
},
new CuiRectTransformComponent
{
OffsetMin = "90 3",
OffsetMax = "50 -105",
},
}
});
}
private ConfigData config;
public class ConfigData
{
public string secretKey { get; set; } = "";
public bool logging { get; set; } = true;
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<ConfigData>();
if (config == null)
{
LoadDefaultConfig();
}
}
catch
{
PrintError("Ошибка загрузки конфигурации! Создаём новую.");
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig()
{
config = new ConfigData();
SaveConfig();
}
protected override void SaveConfig()
{
Config.WriteObject(config, true);
}
private void LoadData()
{
playerSessions = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, PlayerSession>>("RewardPlay/playerSessions") ?? new Dictionary<ulong, PlayerSession>();
playerActive = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, PlayerActive>>("RewardPlay/playerActive") ?? new Dictionary<ulong, PlayerActive>();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject("RewardPlay/playerSessions", playerSessions);
Interface.Oxide.DataFileSystem.WriteObject("RewardPlay/playerActive", playerActive);
}
private class PlayerSession
{
public long Connect { get; set; } = 0;
public long Disconnect { get; set; } = 0;
public float Earned { get; set; } = 0.0f;
}
private class PlayerActive
{
public long LastActiveTime;
public long ActiveAfterAFK;
public Vector3 LastPosition;
public bool openedMenu;
public bool alerted;
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList) {
CuiHelper.DestroyUi(player, UI_MAIN);
OnPlayerDisconnected(player, "unloaded");
}
//ImportPlayers();
SaveData();
}
private void OnNewSave(string filename)
{
if (config.logging) Puts("Новый save обнаружен!\nОтправляем инфорацию и чистим дату");
ImportPlayers();
playerSessions.Clear();
playerActive.Clear();
SaveData();
}
public string ConvertSecondsToTimeFormat(int seconds)
{
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
return $"{hours}ч {minutes}м";
}
public long GetTimeStampConnected(BasePlayer player)
{
long serverStartTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - (long)UnityEngine.Time.realtimeSinceStartup;
long loginTimestamp = serverStartTime + (long)player.net.connection.connectionTime;
return loginTimestamp;
}
public void StartAfkPlayerTell(BasePlayer player)
{
if (playerAfkTimers.ContainsKey(player.userID))
{
playerAfkTimers[player.userID].Destroy();
playerAfkTimers.Remove(player.userID);
}
string text = "<color=#fcd535>[RewardPlay]</color>\nВы были неактивны более 10 минут. Начисление на баланс возобновится, как только вы продолжите играть!";
sendMessage(player, text);
playerAfkTimers[player.userID] = timer.Every(120f, () => {
sendMessage(player, text);
});
}
public void StopAfkPlayerTell(BasePlayer player)
{
if (playerAfkTimers.ContainsKey(player.userID))
{
playerAfkTimers[player.userID].Destroy();
playerAfkTimers.Remove(player.userID);
sendMessage(player, "<color=#fcd535>[RewardPlay]</color>\nВы вышли из АФК!");
}
}
public void StopAllAfkPlayerTell()
{
foreach (var timer in playerAfkTimers.Values) timer.Destroy();
playerAfkTimers.Clear();
}
public void sendMessage(BasePlayer player, string message) {
Player.Message(player, message, 76561199557013128);
}
}
}