C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Oxide.Core;
using Oxide.Core.Plugins;
using Newtonsoft.Json.Linq;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Voting", "https://discord.gg/9vyTXsJyKR", "1.3.1")]
[Description("Allows to easily create votes on server")]
public class Voting : RustPlugin {
[PluginReference]
Plugin ServerRewards, Economics, PopupNotifications, GUIAnnouncements, PlaytimeTracker;
StoredData storedData;
StoredVotesData storedVotesData;
static readonly Dictionary<ulong, string> guiInfo = new Dictionary<ulong, string>();
class StoredData {
public Dictionary<int, Vote> Votes = new Dictionary<int, Vote>();
public StoredData() {
}
}
class Vote {
public string text;
public bool enabled;
public DateTime voteEnd;
public Dictionary<int, VoteVariant> variants;
}
class VoteVariant {
public string variantText;
public int totalVotes = 0;
}
class StoredVotesData {
public Dictionary<int, Dictionary<ulong, PlayerVote>> Votes = new Dictionary<int, Dictionary<ulong, PlayerVote>>();
public StoredVotesData() {
}
}
class PlayerVote {
public ulong steamID;
public int voteID;
public int variant;
public string voteDate;
}
string configChatCommand;
string configUIBackgroundColor;
string configUIAnchorMin;
string configUIAnchorMax;
string configUIVoteNameColor;
int configUIVoteNameFontSize;
string configUIVoteNameAlreadyVotedColor;
string configUIVoteValueColor;
string configUIVoteValueColorAlreadyVoted;
int configUIVoteValueFontSize;
double configDepositEconomicsForVote;
int configAddPointsServerRewardsForVote;
int configAnnounceTimer;
bool configAnnounceNewVote;
bool configAnnounceUsePopupNotifications;
bool configAnnounceUseGUIAnnouncements;
string configAnnounceGUIMessage;
string configAnnounceGUIMessageNewVote;
string configUIVoteValueBackground;
int configLimitByPlayedTime;
bool configUseLimitByPlayedTime;
Timer _timer;
int availVotes = 0;
bool Changed = false;
protected override void LoadDefaultConfig() {
PrintWarning("No configuration file found, generating...");
Config.Clear();
LoadVariables();
}
private void LoadVariables() {
configAnnounceNewVote = Convert.ToBoolean(GetConfig("Overall", "AnnounceNewVote", false));
configAnnounceUseGUIAnnouncements = Convert.ToBoolean(GetConfig("Overall", "AnnounceUseGUIAnnouncements", false));
configAnnounceUsePopupNotifications = Convert.ToBoolean(GetConfig("Overall", "AnnounceUsePopupNotifications", false));
configAnnounceGUIMessage = Convert.ToString(GetConfig("Overall", "AnnounceMessage", "{0} votes available, type /{1}, help server to get better"));
configAnnounceGUIMessageNewVote = Convert.ToString(GetConfig("Overall", "AnnounceMessageNewVote", "New vote {0} added, use /{1}"));
configAnnounceTimer = Convert.ToInt32(GetConfig("Overall", "AnnounceTimer", 0));
configChatCommand = Convert.ToString(GetConfig("Overall", "ChatCommand", "vote"));
configUseLimitByPlayedTime = Convert.ToBoolean(GetConfig("Overall", "UseLimitByPlayedTime", false));
configLimitByPlayedTime = Convert.ToInt32(GetConfig("Overall", "LimitByPlayedTime", 180000));
configUIBackgroundColor = Convert.ToString(GetConfig("UI", "BackgroundColor", "0 0 0 0.8"));
configUIAnchorMin = Convert.ToString(GetConfig("UI", "AnchorMin", "0.05 0.05"));
configUIAnchorMax = Convert.ToString(GetConfig("UI", "AnchorMax", "0.95 0.95"));
configUIVoteNameColor = Convert.ToString(GetConfig("UI", "VoteNameColor", "1 0 0 1"));
configUIVoteNameFontSize = Convert.ToInt32(GetConfig("UI", "VoteNameFontSize", 18));
configUIVoteNameAlreadyVotedColor = Convert.ToString(GetConfig("UI", "VoteNameAlreadyVotedColor", "1 0 0 1"));
configUIVoteValueColor = Convert.ToString(GetConfig("UI", "VoteValueColor", "1 1 1 1"));
configUIVoteValueColorAlreadyVoted = Convert.ToString(GetConfig("UI", "VoteValueColorAlreadyVoted", "0 0 0 1"));
configUIVoteValueFontSize = Convert.ToInt32(GetConfig("UI", "VoteValueFontSize", 18));
configUIVoteValueBackground = Convert.ToString(GetConfig("UI", "VoteValueBackground", "0.5 0.5 0.5 1"));
configDepositEconomicsForVote = Convert.ToDouble(GetConfig("Rewards", "DepositEconomicsForVote", 0));
configAddPointsServerRewardsForVote = Convert.ToInt32(GetConfig("Rewards", "AddPointsServerRewardsForVote", 0));
if (Changed) {
SaveConfig();
Changed = false;
}
}
object GetConfig(string menu, string datavalue, object defaultValue) {
var data = Config[menu] as Dictionary<string, object>;
if (data == null) {
data = new Dictionary<string, object>();
Config[menu] = data;
Changed = true;
}
object value;
if (!data.TryGetValue(datavalue, out value)) {
value = defaultValue;
data[datavalue] = value;
Changed = true;
}
return value;
}
void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string>() {
{
"Title", "Voting: "
}
, {
"availVotes", "Available votes."
}
, {
"voted", "Thanks for your vote"
}
, {
"noVote", "No such vote"
}
, {
"noVoteVariant", "No such vote variant"
}
, {
"badSyntax", "Error, use /vote"
}
, {
"syntax", "Syntax: /vote addtimed DDMMYYYY \"name of vote\" \"first value\" \"second value\"\n/vote add \"name of vote\" \"first value\" \"second value\"\n/vote stop <number>\n/vote start <number>\n/vote remove <number>\n/vote reset <number>"
}
, {
"alreadyVoted", "You have already voted"
}
, {
"voteReset", "Vote reseted"
}
, {
"voteAdd", "New vote added"
}
, {
"voteAddAnnounce", "New vote {0} added, use /{1}"
}
, {
"voteRemove", "Vote removed"
}
, {
"voteStart", "Vote started"
}
, {
"voteStop", "Vote stopped"
}
, {
"voteRewardSR2", "You've been rewarded for voting {0} RP for /s"
}
, {
"voteRewardE", "You've been rewarded for voting"
}
, {
"tillDate", " till "
}
, {
"votes", "votes"
}
, {
"announceWithReward", "{0} votes available, type /{1} and recieve some rewards, help server to get better"
}
, {
"announce", "{0} votes available, type /{1}, help server to get better"
}
, {
"notEnoughTime", "Sorry, you cannot use the vote as being a new player, plase stay at server for another {0} hours"
}
,
}
, this);
lang.RegisterMessages(new Dictionary<string, string>() {
{
"Title", "Голосования: "
}
, {
"availVotes", "Доступные голосования"
}
, {
"voted", "Спасибо, ваш голос учтён"
}
, {
"noVote", "Ошибка! Такого голосования нет"
}
, {
"noVoteVariant", "Ошибка! Такого номера варианта нет"
}
, {
"badSyntax", "Ошибка, напиши /vote"
}
, {
"syntax", "Синтаксис: /vote addtimed ДДММГГГГ \"название о чём голосование\" \"выбор первый\" \"выбор второй\"\n/vote add \"название о чём голосование\" \"выбор первый\" \"выбор второй\"\n/vote stop <номер>\n/vote start <номер>\n/vote remove <номер>\n/vote reset <номер>"
}
, {
"alreadyVoted", "Вы уже голосовали"
}
, {
"voteReset", "Результаты голосования сброшены"
}
, {
"voteAdd", "Новое голосование добавлено"
}
, {
"voteAddAnnounce", "Новое голосование {0} добавлено, /{1}"
}
, {
"voteRemove", "Голосование удалено"
}
, {
"voteStart", "Голосование запущено"
}
, {
"voteStop", "Голосование остановлено"
}
, {
"voteRewardSR2", "Ты получил награду {0} RP для магазина /s"
}
, {
"voteRewardE", "Ты получил награду"
}
, {
"tillDate", " до "
}
, {
"votes", "голосов"
}
, {
"announceWithReward", "доступно {0} голосований, набери /{1} и получи небольшую награду, помоги серверу стать лучше"
}
, {
"announce", "доступно {0} голосований, набери /{1}, помоги серверу стать лучше"
}
, {
"notEnoughTime", "Только активные игроки могут голосовать на сервере, отыграй у нас ещё {0} часов"
}
,
}
, this, "ru");
}
void Init() {
LoadVariables();
cmd.AddChatCommand(configChatCommand, this, "cmdVote");
cmd.AddConsoleCommand(configChatCommand, this, "ccmdVote");
storedData = Interface.GetMod().DataFileSystem.ReadObject<StoredData>(this.Title);
storedVotesData = Interface.GetMod().DataFileSystem.ReadObject<StoredVotesData>(this.Title + "Votes");
}
void OnServerInitialized() {
CheckTimer();
}
void CheckTimer() {
if (configAnnounceTimer != 0) {
availVotes = storedData.Votes.Where(v => v.Value.enabled).Count();
if (availVotes > 0 && _timer == null) {
_timer = timer.Repeat(configAnnounceTimer, 0, () => {
Announce();
}
);
}
else if (availVotes < 1 && _timer != null) {
_timer.Destroy();
}
}
}
[ConsoleCommand("vote.announce")]
void cmdAnnounce(ConsoleSystem.Arg arg) {
if (arg.Connection != null && arg.Connection.authLevel<2) return;
Announce();
}
void Unload() {
foreach(var pl in guiInfo) {
var player = BasePlayer.activePlayerList.FirstOrDefault(f => f.userID == pl.Key);
if (player == null) continue;
CuiHelper.DestroyUi(player, pl.Value);
}
guiInfo.Clear();
if (_timer != null) _timer.Destroy();
}
void SaveData() {
Interface.Oxide.DataFileSystem.WriteObject(this.Title+"Votes", storedVotesData);
Interface.Oxide.DataFileSystem.WriteObject(this.Title, storedData);
}
void Announce() {
if (configAnnounceUseGUIAnnouncements) GUIAnnouncements?.Call("CreateAnnouncement", String.Format(configAnnounceGUIMessage, availVotes.ToString(), configChatCommand), "Grey", "White");
else if (configAnnounceUsePopupNotifications) PopupNotifications?.Call("CreatePopupNotification", String.Format(configAnnounceGUIMessage, availVotes.ToString(), configChatCommand));
else BroadcastToChat((configDepositEconomicsForVote >0 || configAddPointsServerRewardsForVote >0 ? "announceWithReward" : "announce"), new string[] {
availVotes.ToString(), configChatCommand
}
);
}
void ccmdVote(ConsoleSystem.Arg arg) {
var player = arg?.Player() ?? null;
if (player?.net.connection.authLevel < 2) return;
if (arg.Args == null || arg.Args.Length < 1) {
SendReply(arg, msg("syntax"));
return;
}
string[] args = arg.Args;
if (args[0] == "add") {
if (args.Length < 4) {
SendReply(arg, msg("syntax"));
return;
}
var vote = new Vote();
vote.text = args[1];
vote.enabled = true;
vote.variants = new Dictionary<int, VoteVariant>();
int i = 1;
foreach (string opt in args.Skip(2)) {
vote.variants.Add(i, new VoteVariant() {
variantText = opt
}
);
i++;
}
int voteNum = 1;
if (storedData.Votes.Count > 0) voteNum = storedData.Votes.Keys.Max() + 1;
storedData.Votes.Add(voteNum, vote);
SaveData();
CheckTimer();
SendReply(arg, msg("voteAdd"));
if (configAnnounceNewVote) {
if (configAnnounceUseGUIAnnouncements) GUIAnnouncements?.Call("CreateAnnouncement", String.Format(configAnnounceGUIMessageNewVote, vote.text, configChatCommand), "Grey", "White");
else if (configAnnounceUsePopupNotifications) PopupNotifications?.Call("CreatePopupNotification", String.Format(configAnnounceGUIMessageNewVote, vote.text, configChatCommand));
else BroadcastToChat("voteAddAnnounce", new string[] {
vote.text, configChatCommand
}
);
}
}
else if (args[0] == "addtimed") {
if (args.Length < 5) {
SendReply(arg, msg("syntax"));
return;
}
var date = new DateTime();
if (!DateTime.TryParseExact(args[1], "ddMMyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out date)) {
SendReply(arg, msg("syntax"));
return;
}
var vote = new Vote();
vote.text = args[2];
vote.enabled = true;
vote.voteEnd = date;
vote.variants = new Dictionary<int, VoteVariant>();
int i = 1;
foreach (string opt in args.Skip(3)) {
vote.variants.Add(i, new VoteVariant() {
variantText = opt
}
);
i++;
}
int voteNum = 1;
if (storedData.Votes.Count > 0) voteNum = storedData.Votes.Keys.Max() + 1;
storedData.Votes.Add(voteNum, vote);
SaveData();
CheckTimer();
SendReply(arg, msg("voteAdd"));
}
else if (args[0] == "remove") {
if (args.Length != 2) {
SendReply(player, msg("syntax"));
return;
}
int voteNumber = 0;
if (!int.TryParse(args[1], out voteNumber)) {
SendReply(arg, msg("syntax"));
return;
}
object exists = CheckVoteExists(voteNumber, 0, true);
if (exists != null) {
SendReply(arg, msg((string)exists));
return;
}
storedData.Votes.Remove(voteNumber);
storedVotesData.Votes.Remove(voteNumber);
SaveData();
CheckTimer();
SendReply(arg, msg("voteRemove"));
}
else if (args[0] == "reset") {
if (args.Length != 2) {
SendReply(arg, msg("syntax"));
return;
}
int voteNumber = 0;
if (!int.TryParse(args[1], out voteNumber)) {
SendReply(arg, msg("syntax"));
return;
}
object exists = CheckVoteExists(voteNumber, 0, true);
if (exists != null) {
SendReply(arg, (string)exists);
return;
}
if (!storedVotesData.Votes.ContainsKey(voteNumber)) {
SendReply(arg, msg("noVote"));
return;
}
storedVotesData.Votes.Remove(voteNumber);
foreach (var v in storedData.Votes[voteNumber].variants) {
v.Value.totalVotes = 0;
}
SaveData();
SendReply(arg, msg("voteReset"));
}
else if (args[0] == "stop" || args[0] == "start") {
if (args.Length != 2) {
SendReply(arg, "syntax");
return;
}
int voteNumber = 0;
if (!int.TryParse(args[1], out voteNumber)) {
SendReply(arg, "syntax");
return;
}
object exists = CheckVoteExists(voteNumber, 0, true);
if (exists != null) {
SendReply(arg, (string)exists);
return;
}
storedData.Votes[voteNumber].enabled = (args[0] == "stop" ? false : true);
SaveData();
CheckTimer();
SendReply(arg, (args[0] == "stop" ? msg("voteStop") : msg("voteStart")));
}
else {
SendReply(arg, msg("syntax"));
}
}
void cmdVote(BasePlayer player, string command, string[] args) {
if (player.net.connection.authLevel != 2 && configUseLimitByPlayedTime && PlaytimeTracker != null) {
int playerTime = Convert.ToInt32(PlaytimeTracker?.Call("GetPlayTime", player.UserIDString) ?? 0);
if (playerTime < configLimitByPlayedTime) {
SendMsg(player, "notEnoughTime", true, new string[] {
Convert.ToInt32((configLimitByPlayedTime - playerTime) /3600).ToString()
}
);
return;
}
}
if (args == null || args.Length == 0) {
RenderUI(player);
}
else if (args.Length==2) {
int voteNumber = 0;
int voteVariant = 0;
if (!int.TryParse(args[0], out voteNumber)) {
return;
}
if (!int.TryParse(args[1], out voteVariant)) {
return;
}
AddVote(player, voteNumber, voteVariant);
}
else {
SendMsg(player, "badSyntax");
}
}
object CheckVoteExists(int voteNumber, int voteVariant = 0, bool dontCheckEnable = false) {
Vote vote;
if (!storedData.Votes.TryGetValue(voteNumber, out vote)) {
return "noVote";
}
if (!dontCheckEnable) {
if (!vote.enabled) {
return "noVote";
}
}
if (voteVariant != 0) {
VoteVariant vVariant;
if (!vote.variants.TryGetValue(voteVariant, out vVariant)) {
return "noVoteVariant";
}
}
return null;
}
void PrintVotes(BasePlayer player) {
SendMsg(player, "availVotes");
foreach(var vote in storedData.Votes) {
if (!vote.Value.enabled) continue;
string text = $"<color=red>{vote.Key}.</color> {vote.Value.text}: ";
foreach(var variant in vote.Value.variants) {
text += $"<color=orange>{variant.Key}.</color> {variant.Value.variantText} ({variant.Value.totalVotes} голосов) ";
}
SendReply(player, text);
}
}
[ConsoleCommand("voting.add")]
void cmdVotingAdd(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null || arg.Args == null || arg?.Args?.Length <= 0) return;
int voteNumber = 0;
int voteVariant = 0;
if (!int.TryParse(arg.Args[0], out voteNumber)) {
SendMsg(player, "badSyntax");
return;
}
if (!int.TryParse(arg.Args[1], out voteVariant)) {
SendMsg(player, "badSyntax");
return;
}
AddVote(player, voteNumber, voteVariant);
RenderUI(player);
}
void AddVote(BasePlayer player, int voteNumber, int variant) {
object res;
if ((res = CheckVoteExists(voteNumber, variant))!=null) {
SendMsg(player, res.ToString());
return;
}
if (HasVoted(player, voteNumber)) {
SendMsg(player, "alreadyVoted");
return;
}
Dictionary<ulong, PlayerVote> vote;
if (!storedVotesData.Votes.TryGetValue(voteNumber, out vote)) {
storedVotesData.Votes.Add(voteNumber, new Dictionary<ulong, PlayerVote>());
}
storedVotesData.Votes[voteNumber].Add(player.userID, new PlayerVote() {
steamID = player.userID, voteID = voteNumber, variant = variant, voteDate = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss")
}
);
storedData.Votes[voteNumber].variants[variant].totalVotes+=1;
SaveData();
if (configDepositEconomicsForVote > 0) {
Economics?.CallHook("Deposit", player.userID, configDepositEconomicsForVote);
SendMsg(player, "voteRewardE");
}
if (configAddPointsServerRewardsForVote > 0) {
ServerRewards?.CallHook("AddPoints", player.userID, configAddPointsServerRewardsForVote);
SendMsg(player, "voteRewardSR2", true, new string[] {
configAddPointsServerRewardsForVote.ToString()
}
);
}
SendMsg(player, "voted");
}
bool HasVoted(BasePlayer player, int voteNumber) {
Dictionary<ulong, PlayerVote> vote;
if (storedVotesData.Votes.TryGetValue(voteNumber, out vote)) {
PlayerVote pl;
if (vote.TryGetValue(player.userID, out pl)) return true;
}
return false;
}
void PutUIVotes(BasePlayer player, CuiElementContainer elements, object panel) {
float YMax = 0.9f;
foreach(var vote in storedData.Votes) {
if (!vote.Value.enabled) continue;
if (vote.Value.voteEnd != DateTime.MinValue && vote.Value.voteEnd < DateTime.UtcNow) continue;
elements.Add(new CuiLabel() {
Text = {
Text = $"{vote.Key}."+vote.Value.text + (HasVoted(player, vote.Key) ? " " + msg("alreadyVoted", player) : "" ) + (vote.Value.voteEnd != DateTime.MinValue ? msg("tillDate", player)+vote.Value.voteEnd.ToShortDateString() : ""), FontSize = configUIVoteNameFontSize, Color = (HasVoted(player, vote.Key) ? configUIVoteNameAlreadyVotedColor : configUIVoteNameColor), Align = TextAnchor.UpperLeft
}
, RectTransform = {
AnchorMin = "0.025 0", AnchorMax = "1 "+YMax.ToString()
}
}
, (string)panel);
YMax -= 0.04f;
foreach(var variant in vote.Value.variants) {
if (HasVoted(player, vote.Key)) elements.Add(new CuiLabel() {
Text = {
Text = $"{variant.Value.variantText} ({variant.Value.totalVotes} "+msg("votes", player)+")", FontSize = configUIVoteValueFontSize, Color = configUIVoteValueColorAlreadyVoted, Align = TextAnchor.UpperLeft
}
, RectTransform = {
AnchorMin = "0.03 0", AnchorMax = "1 "+YMax.ToString()
}
}
, (string)panel);
else elements.Add(new CuiButton() {
Button = {
Command = $"voting.add {vote.Key} {variant.Key}", Color = configUIVoteValueBackground
}
, Text = {
Text = $"{variant.Value.variantText} ({variant.Value.totalVotes} "+msg("votes", player)+")", FontSize = configUIVoteValueFontSize, Color = configUIVoteValueColor, Align = TextAnchor.UpperLeft
}
, RectTransform = {
AnchorMin = "0.1 "+(YMax-0.04f), AnchorMax = "0.8 "+(YMax-0.01f)
}
}
, (string)panel);
YMax -= 0.04f;
}
}
}
void RenderUI(BasePlayer player) {
DestroyUi(player);
guiInfo[player.userID] = CuiHelper.GetGuid();
var elements = new CuiElementContainer();
var panel = elements.Add(new CuiPanel() {
Image = {
Color = configUIBackgroundColor
}
, CursorEnabled = true, RectTransform = {
AnchorMin = configUIAnchorMin, AnchorMax = configUIAnchorMax
}
}
, "Overlay", guiInfo[player.userID]);
PutUIVotes(player, elements, panel);
elements.Add(new CuiPanel {
CursorEnabled = false, Image = {
Color = "0 0 0 0.6",
}
, RectTransform = {
AnchorMin = "0 0.93", AnchorMax = "1 1"
}
}
, (string)panel);
elements.Add(new CuiLabel {
Text = {
Text = msg("availVotes", player), FontSize = 30, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1.0"
}
, RectTransform = {
AnchorMin = "0 0.93", AnchorMax = "1 1"
}
}
, (string)panel);
elements.Add(new CuiButton {
Button = {
Command = "voting.guiclose", Close = panel, Color = "1 0 0 0.5"
}
, RectTransform = {
AnchorMin = "0.93 0.93", AnchorMax = "1 1"
}
, Text = {
Text = "X", FontSize = 30, Align = TextAnchor.MiddleCenter
}
}
, (string)panel);
CuiHelper.AddUi(player, elements);
}
[ConsoleCommand("voting.guiclose")]
void cmdGuiClose(ConsoleSystem.Arg arg) {
if (arg.Connection == null || arg.Connection.player == null) return;
var player = arg.Connection.player as BasePlayer;
if (player == null) return;
DestroyUi(player);
}
void DestroyUi(BasePlayer player) {
string gui;
if(guiInfo.TryGetValue(player.userID, out gui)) CuiHelper.DestroyUi(player, gui);
guiInfo.Remove(player.userID);
}
public void BroadcastToChat(string key, params string[] args) {
foreach(var player in BasePlayer.activePlayerList) {
if (player.net.connection.authLevel != 2 && configUseLimitByPlayedTime && PlaytimeTracker != null && Convert.ToInt32(PlaytimeTracker?.Call("GetPlayTime", player.UserIDString) ?? 0) < configLimitByPlayedTime) continue;
SendMsg(player, key, true, args);
}
}
private string msg(string key, BasePlayer player = null) => lang.GetMessage(key, this, player?.UserIDString);
private void SendMsg(BasePlayer player, string langkey, bool title = true, params string[] args) {
string message = $"<color=white>{String.Format(msg(langkey,player),args)}</color>";
if (title) message = $"<color=orange>{msg("Title",player)}</color> " + message;
SendReply(player, message);
}
}
}