C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using ConVar;
using Pool = Facepunch.Pool;
using Object = System.Object;
using System;
using System.Collections;
using UnityEngine.Networking;
using Oxide.Game.Rust.Cui;
using Newtonsoft.Json;
using Oxide.Core.Database;
using System.Linq;
using Oxide.Core;
using System.Text;
using Oxide.Core.Plugins;
using System.Collections.Generic;
using UnityEngine;
namespace Oxide.Plugins {
[Info("IQEconomic", "Mercury", "2.10.13")]
[Description("Экономика на ваш сервер")]
public class IQEconomic : RustPlugin {
private Boolean isUsedSQL = false;
private const String arrowLogRemoved = "=======>";
private void OnPlayerSleep(BasePlayer player) {
if (_interface == null) return;
InterfaceBuilder.DestroyPlayerUI(player);
}
private void DisconnectedPlayer(BasePlayer player) {
if (isUsedSQL) {
PlayerInfo pInfo = PlayerInfo.Get(player.UserIDString);
if (pInfo == null) return;
ImportPlayerSQL(player.UserIDString, pInfo.Balance, pInfo.LimitBalance, pInfo.Time, pInfo.DateTime, pInfo.IsHide);
return;
}
PlayerInfo.Save(player.UserIDString);
PlayerInfo.Remove(player.UserIDString);
}
private void OnPlayerRespawned(BasePlayer player) {
if (!saveAfterDeath.TryGetValue(player, out Int32 balance)) return;
if (balance == 0) return;
AddBalance(player, balance, TypeLog.ReturnedSaveAfterDeath);
}
private List<Item> GetPhysicsItems(BasePlayer player) {
Configuration.GeneralSetting.PhysicsItem physicConfig = config.generalSetting.physicsItem;
List<Item> acceptedItems = Pool.Get<List<Item>>();
List<Item> playerInventory = GetAllItems(player);
foreach (Item item in playerInventory) {
if (physicConfig.IsPhysicMoney(item)) acceptedItems.Add(item);
}
Pool.FreeUnmanaged(ref playerInventory);
return acceptedItems;
}
public String FormatTime(TimeSpan time, string userId) {
if (time.Days > 0) return Format(time.Days, GetLang("EXCHANGER_COURSE_TITLE_FORMAT_TIME_DAYS", userId));
if (time.Hours > 0) return Format(time.Hours, GetLang("EXCHANGER_COURSE_TITLE_FORMAT_TIME_HOURSE", userId));
if (time.Minutes > 0) return Format(time.Minutes, GetLang("EXCHANGER_COURSE_TITLE_FORMAT_TIME_MINUTES", userId));
return time.Seconds > 0 ? Format(time.Seconds, GetLang("EXCHANGER_COURSE_TITLE_FORMAT_TIME_SECONDS", userId)) : String.Empty;
}
private (BasePlayer, String) FindPlayerNameOrID(BasePlayer finder, String nameOrID) {
List<BasePlayer> players = Pool.Get<List<BasePlayer>>();
players.AddRange(BasePlayer.activePlayerList);
if (nameOrID.IsSteamId() && ulong.TryParse(nameOrID, out UInt64 userID)) {
BasePlayer player = BasePlayer.FindByID(userID);
Pool.FreeUnmanaged(ref players);
return (player, null);
}
List<BasePlayer> matchingPlayers = Pool.Get<List<BasePlayer>>();
try {
matchingPlayers.AddRange(players.Where(player => player.displayName != null && player.displayName.IndexOf(nameOrID, StringComparison.OrdinalIgnoreCase) >= 0));
switch (matchingPlayers.Count) {
case 1 : {
BasePlayer foundPlayer = matchingPlayers[0];
return (foundPlayer, null);
}
case > 1 : {
String nicknameList = String.Join(", ", matchingPlayers.Take(3).Select(p => p.displayName));
return (null, GetLang("CHAT_ALERT_TRANSFER_MATCHES_PLAYERS", finder.UserIDString, nameOrID, nicknameList));
}
default : return (null, null);
}
}
finally {
Pool.FreeUnmanaged(ref players);
Pool.FreeUnmanaged(ref matchingPlayers);
}
}
private void OnPlayerDeath(BasePlayer target, HitInfo hitInfo) {
if(!target) return;
Boolean isRealPlayerTarget = IsRealPlayer(target);
Configuration.EarningCoins.PresetEarning earningPlayer = config.earningCoins.killedPlayer;
Configuration.EarningCoins.PresetEarning earningNpc = config.earningCoins.killedNpcs;
if (isRealPlayerTarget) {
if (isUsedUI) InterfaceBuilder.DestroyPlayerUI(target);
if (config.generalSetting.physicsItem.useSaveDeath && config.generalSetting.typeCoins == TypeCoins.Physics) {
Int32 balance = GetBalancePlayer(target);
RemoveBalance(target, balance, TypeLog.SaveAfterDeath);
saveAfterDeath[target] = balance;
}
}
if (!earningPlayer.useEarning && !earningNpc.useEarning) return;
if (hitInfo == null) return;
BasePlayer attacker = hitInfo.InitiatorPlayer;
if (!attacker) return;
if (!IsRealPlayer(attacker)) return;
if (attacker == target) return;
if (isRealPlayerTarget) {
if (IsBlockedEarningKillPlayer(attacker, target)) return;
if (!IsOnlyTakeEarning(attacker, earningPlayer)) return;
AddBalance(attacker, earningPlayer.countMoney, TypeLog.Action, earningPlayer.useChatAlert ? "CHAT_ALERT_EARNING_KILLED_PLAYER" : default);
return;
}
if (!IsOnlyTakeEarning(attacker, earningNpc)) return;
AddBalance(attacker, earningNpc.countMoney, TypeLog.Action, earningNpc.useChatAlert ? "CHAT_ALERT_EARNING_KILLED_NPC" : default);
}
private void ImportPlayerSQL(String userID, Int32 balance, Int32 limitBalance, Int32 time, Double dateTime, Boolean isHide, Boolean isMigrate = false) {
if (sqlConnection == null) return;
Configuration.GeneralSetting.MySQLConnection sqlInfo = config.generalSetting.mySQLConnectionSettings;
String hideString = isHide.ToString();
String sqlQuery = $"UPDATE {sqlInfo.dbTableName} SET `steamid` = @0, `balance` = @1,`limit_balance` = @2,`time` = @3,`last_connection` = @4, `is_hide` = @5 WHERE `steamid` = @0";
Sql updateCommand = Sql.Builder.Append(sqlQuery, userID, balance, limitBalance, time, dateTime, hideString);
sqlLibrary.Update(updateCommand, sqlConnection, rowsAffected => {
if (rowsAffected > 0) return;
String Query = String.Format(SQL_Query_InsertUser(), userID, balance, limitBalance, time, dateTime, hideString);
Sql sql = Sql.Builder.Append(Query);
sqlLibrary.Insert(sql, sqlConnection, rowsAffecteds => {
if (!isMigrate) Puts(LanguageEn? $"" : $"Данные игрока {userID} были внесены в базу-данных");
}
);
}
);
}
private String SQL_Query_CreatedDatabase() {
String CreatedDB = $"CREATE TABLE IF NOT EXISTS `{config.generalSetting.mySQLConnectionSettings.dbTableName}`(" + "`id` INT(11) NOT NULL AUTO_INCREMENT," + "`steamid` VARCHAR(255) NOT NULL," + "`balance` VARCHAR(255) NOT NULL," + "`limit_balance` VARCHAR(255) NOT NULL," + "`time` VARCHAR(255) NOT NULL," + "`last_connection` VARCHAR(255) NOT NULL," + "`is_hide` VARCHAR(6) NOT NULL," + " PRIMARY KEY(`id`))";
return CreatedDB;
}
private void TransferToPlayer(BasePlayer player, BasePlayer targetPlayer, Int32 amount) {
if (!IsValidTransfer(player, targetPlayer, amount)) return;
ExecuteTransfer(player, targetPlayer, amount);
}
private List<String> GetOrSetCacheUI(String keyCache, String interfaceJson = null) {
if (cachedUI.TryGetValue(keyCache, out List<String> ui)) return ui;
if (interfaceJson == null) return null;
List<String> newUI = new() {
interfaceJson
}
;
cachedUI[keyCache] = newUI;
return newUI;
}
private void ConsoleCommandBalance(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Player();
ChatCommandBalance(player);
}
private void OnEntityDeath(LootContainer entity, HitInfo info) {
if (!entity.PrefabName.Contains("barrel") || entity.PrefabName.Contains("hobobarrel")) return;
Configuration.EarningCoins.PresetEarning earning = config.earningCoins.killedBarrels;
if (!earning.useEarning) return;
if (!entity || info == null) return;
BasePlayer player = info.InitiatorPlayer;
if(!IsTakeEarning(player, earning)) return;
AddBalance(player, earning.countMoney, TypeLog.Action, earning.useChatAlert ? "CHAT_ALERT_EARNING_DESTROY_BARREL" : default);
}
private void RemoveBalance(String userID, Int32 amount, TypeLog typeLog) {
if (config.generalSetting.typeCoins == TypeCoins.Physics) {
PrintWarning(LanguageEn ? "" : $"Нельзя забрать валюту у игрока {userID} пока он оффлайн");
return;
}
if (isUsedSQL) GetAndFunctionalBalancePlayerSQL(userID, false, amount);
else {
PlayerInfo pInfo = PlayerInfo.GetOffline(userID);
if (pInfo == null) return;
if (!DoesHaveEnoughBalance(pInfo.Balance)) {
pInfo.Balance = 0;
LogAction(userID, pInfo.Balance, typeLog, false);
}
else {
pInfo.Balance -= amount;
LogAction(userID, amount, typeLog, false);
}
PlayerInfo.SaveOffline(userID, pInfo);
Interface.Oxide.CallHook("OnRemovedBalance", UInt64.Parse(userID), amount, null, pInfo.Balance);
}
}
private static Boolean IsUnblockExchangerCourse(Int32 timeExchange) => ((SaveRestore.SaveCreatedTime.ToUniversalTime().Subtract(epoch).TotalSeconds + timeExchange) - CurrentTime) <= 0;
void API_TRANSFERS(BasePlayer player, BasePlayer transferUserID, Int32 balance) => TransferToPlayer(player, transferUserID, balance);
private String Format(Int32 units, String form) {
Int32 lastDigit = units % 10;
Int32 lastTwoDigits = units % 100;
if (lastTwoDigits is >= 11 and <= 19) return $"{units}{form}";
return lastDigit switch {
1 => $"{units}{form}", 2 or 3 or 4 => $"{units}{form}", _ => $"{units}{form}"
}
;
}
private Int32 GetLimiteExchanged(PlayerInfo pInfo) {
Int32 limitExchange = config.exchangerSetting.limitStoreMoney;
if (limitExchange <= 0) return -1;
Int32 leftLimite = limitExchange - pInfo.LimitBalance;
return leftLimite <= 0 ? 0 : leftLimite;
}
private void AddBalance(BasePlayer player, PlayerInfo pInfo, Int32 amount, TypeLog typeLog, String alertMessage = default) {
if (!player || amount <= 0) return;
if (config.generalSetting.typeCoins == TypeCoins.Physics) {
Item physicMoney = config.generalSetting.physicsItem.CreatePhysicMoney(amount);
if (physicMoney == null) return;
if (!TryMoveItem(player, physicMoney)) {
physicMoney.Remove();
return;
}
if (!String.IsNullOrWhiteSpace(alertMessage)) SendChat(GetLang("CHAT_ALERT_EARNING_TITLE", player.UserIDString, amount, GetLang(alertMessage, player.UserIDString)), player);
Int32 balancePlayer = GetBalancePlayer(player);
UpdateExchangedUI(player, balancePlayer, GetLimiteExchanged(pInfo));
LogAction(player, amount, typeLog, true);
Interface.Oxide.CallHook("OnAddedBalance", player.userID.Get(), amount, player, balancePlayer);
return;
}
pInfo.Balance += amount;
if (!String.IsNullOrWhiteSpace(alertMessage)) SendChat(GetLang("CHAT_ALERT_EARNING_TITLE", player.UserIDString, amount, GetLang(alertMessage, player.UserIDString)), player);
LogAction(player, amount, typeLog, true);
DrawUI_UpdateTitleBalance(player, pInfo);
UpdateExchangedUI(player, pInfo.Balance, GetLimiteExchanged(pInfo));
Interface.Oxide.CallHook("OnAddedBalance", player.userID.Get(), amount, player, pInfo.Balance);
}
private void MoscovOVHBalanceSet(BasePlayer player, (Int32 exchangeCoins, Int32 storeMoney) exchangeDetail, UInt64 userID = 0) {
if (!config.exchangerSetting.storeSetting.useMoscovOvh) return;
if (!RustStore) {
PrintWarning(LanguageEn ? "" : "У вас не установлен магазин MoscovOVH");
return;
}
if (exchangeDetail.storeMoney <= 0) return;
Boolean isSetOffline = !player || exchangeDetail.exchangeCoins <= 0;
userID = !isSetOffline ? player.userID : userID;
RustStore.CallHook("APIChangeUserBalance", userID, exchangeDetail.storeMoney, new Action<String>((result) => {
PlayerInfo pInfo = PlayerInfo.Get(userID.ToString());
if (result == "SUCCESS") {
if (isSetOffline) return;
RemoveBalance(player, exchangeDetail.exchangeCoins, TypeLog.Exchanger);
SendChat(GetLang("CHAT_ALERT_EXCHANGED_SUCCES", player.UserIDString), player);
if (config.exchangerSetting.limitStoreMoney <= 0) return;
if (pInfo != null) pInfo.LimitBalance += exchangeDetail.storeMoney;
return;
}
if (isSetOffline) return;
SendChat(GetLang("CHAT_ALERT_EXCHANGED_FAIL", player.UserIDString), player);
}
));
}
private List<Item> GetAllItems(BasePlayer player) {
List<Item> itemList = Pool.Get<List<Item>>();
player.inventory.GetAllItems(itemList);
player.inventory.AddBackpackContentsToList(itemList);
return itemList;
}
private enum TypeCoins {
Virtual, Physics
}
void API_REMOVE_BALANCE(BasePlayer player, Int32 balance) => RemoveBalance(player, balance, TypeLog.API);
private void DrawUI_UpdateTitleBalance(BasePlayer player, PlayerInfo pInfo) {
if (!isUsedUI) return;
if (player.IsSleeping()) return;
if (_interface == null) return;
if (pInfo == null) return;
if (pInfo.IsHide) return;
String Interface = InterfaceBuilder.GetInterface("UI_Balance_Title_Update");
if (Interface == null) return;
Int32 balance = GetBalancePlayer(pInfo);
String balanceLang = balance >= 99999999 ? GetLang("BALANCE_TITLE_SHORT", player.UserIDString, balance) : GetLang("BALANCE_TITLE", player.UserIDString, balance);
Interface = Interface.Replace("%BALANCE_TITLE%", balanceLang);
AddUI(player, Interface);
}
private void OnPlayerConnected(BasePlayer player) => ConnectedPlayer(player);
private Timer timerTrackerTimes;
private Boolean isUsedStores = false;
private void GetPlayerSQL(String userID, BasePlayer player = null) {
if (sqlConnection == null) return;
Sql sql = Sql.Builder.Append(SQL_Query_SelectedDatabase(userID));
sqlLibrary.Query(sql, sqlConnection, list => {
if (list == null) return;
if (list.Count <= 0) {
PlayerInfo.Import(userID, new PlayerInfo {
Balance = 0, LimitBalance = 0, Time = 0, DateTime = CurrentTime, IsHide = false,
}
);
if (player) DrawUI_HudPanel(player);
return;
}
foreach (Dictionary<String, Object> entry in list) {
String steamID = (String)entry["steamid"];
if (!steamID.IsSteamId()) return;
if (!Int32.TryParse((String)entry["balance"], out Int32 balance)) return;
if (!Int32.TryParse((String)entry["limit_balance"], out Int32 limitBalance)) return;
if (!Int32.TryParse((String)entry["time"], out Int32 time)) return;
if (!Double.TryParse((String)entry["last_connection"], out Double dateTime)) return;
if (!Boolean.TryParse((String)entry["is_hide"], out Boolean isHideStatus)) return;
PlayerInfo.Import(userID, new PlayerInfo {
Balance = balance, LimitBalance = limitBalance, Time = time, DateTime = CurrentTime, IsHide = isHideStatus,
}
);
if (player) DrawUI_HudPanel(player);
}
}
);
}
private Int32 GetBalancePlayer(PlayerInfo pInfo) => pInfo?.Balance ?? 0;
void API_TRANSFERS(UInt64 userID, String transferUserID, Int32 balance) {
BasePlayer player = BasePlayer.FindByID(userID);
if (!player) return;
TransferToPlayer(player, transferUserID, balance);
}
private void RemovePhysicMoney(BasePlayer player, Int32 amount) {
List<Item> physicMoney = GetPhysicsItems(player);
foreach (Item pItem in physicMoney) {
if (amount <= 0) break;
if (pItem.amount >= amount) {
pItem.amount -= amount;
if (pItem.amount == 0) {
pItem.RemoveFromContainer();
pItem.Remove();
}
amount = 0;
}
else {
amount -= pItem.amount;
pItem.RemoveFromContainer();
pItem.Remove();
}
pItem.MarkDirty();
}
Pool.FreeUnmanaged(ref physicMoney);
}
private void GetAndFunctionalBalancePlayerSQL(String userID, Boolean addOrRemove, Int32 amount = 0) {
if (sqlConnection == null) return;
Sql sql = Sql.Builder.Append(SQL_Query_SelectedDatabase(userID));
sqlLibrary.Query(sql, sqlConnection, list => {
if (list == null) return;
if (list.Count <= 0) {
PlayerInfo.Import(userID, new PlayerInfo {
Balance = addOrRemove ? amount : 0, LimitBalance = 0, Time = 0, DateTime = CurrentTime, IsHide = false,
}
);
return;
}
foreach (Dictionary<String, Object> entry in list) {
String steamID = (String)entry["steamid"];
if (!steamID.IsSteamId()) return;
if (!Int32.TryParse((String)entry["balance"], out Int32 balance)) return;
if (!Int32.TryParse((String)entry["limit_balance"], out Int32 limitBalance)) return;
if (!Int32.TryParse((String)entry["time"], out Int32 time)) return;
if (!Double.TryParse((String)entry["last_connection"], out Double dateTime)) return;
if (!Boolean.TryParse((String)entry["is_hide"], out Boolean isHideStatus)) return;
PlayerInfo.Import(userID, new PlayerInfo {
Balance = addOrRemove ? balance + amount : Math.Max(balance - amount, 0), LimitBalance = limitBalance, Time = time, DateTime = dateTime, IsHide = isHideStatus,
}
);
}
PlayerInfo pInfo = PlayerInfo.Get(userID);
ImportPlayerSQL(userID, pInfo.Balance, pInfo.LimitBalance, pInfo.Time, pInfo.DateTime, pInfo.IsHide);
Interface.Oxide.CallHook(addOrRemove ? "OnAddedBalance" : "OnRemovedBalance", UInt64.Parse(userID), amount, null, pInfo.Balance);
}
);
}
/// <summary>
/// Plans :
/// - Исправлена возможная ошибка при Unload с закрытием SQL соединения
/// - Добавлена возможность полной очистки дата-файла при вайпе сервера, включается отдельно в конфигурации
/// - Убрана лишняя обработка игроков при загрузке плагина, если используется MySQL
/// - Добавлены дополнительные проверки в OnPlayerDeath на случай ошибки в конфигурации или в случае возможных конфликтов
/// - Добавлена возможность запретить использовать команду для обмена валюты пока игрок не находится в безопасной зоне
/// - Добавлено сообщение о некорректном синтаксисе если включен обмен между игроками, но не включен обмен на магазин
/// </summary>
[PluginReference]
Plugin IQChat, Friends, Clans, Battles, Duel, RustStore, GameStoresRUST, Duelist, ArenaTournament, XFarmRoom;
private Configuration.ExchangerSetting.CourseController GetActualyCourse() {
foreach (Configuration.ExchangerSetting.CourseController course in orderedListExchanged) {
if (IsUnblockExchangerCourse(course.secondsAfterWipe)) return course;
}
return null;
}
private void UpdateMultiplier(BasePlayer player, Int32 balancePlayer, Int32 limitExchanged, Int32 multiplier) {
exchangerDataPlayer[player] += multiplier;
DrawUI_UpdateExchangeOne_Menu(player, balancePlayer, limitExchanged, exchangerDataPlayer[player]);
}
private void UpdateExchangerCourse() {
Configuration.ExchangerSetting.CourseController courseUpdated = GetActualyCourse();
if (actualyCourse != null && courseUpdated == actualyCourse) return;
actualyCourse = courseUpdated;
if (courseData.keyCourse == courseUpdated.secondsAfterWipe) return;
courseData.keyCourse = actualyCourse.secondsAfterWipe;
courseData.lastUpdateCourseTime = CurrentTime;
if (!config.exchangerSetting.useMessageUpdateCourse) return;
foreach (BasePlayer basePlayer in BasePlayer.activePlayerList) SendChat(GetLang("CHAT_ALERT_COURSE_UPDATE", basePlayer.UserIDString, config.commandExchanger), basePlayer);
}
void API_TRANSFERS(UInt64 userID, UInt64 transferUserID, Int32 balance) {
BasePlayer player = BasePlayer.FindByID(userID);
if (!player) return;
TransferToPlayer(player, transferUserID.ToString(), balance);
}
void API_TRANSFERS(BasePlayer player, UInt64 transferUserID, Int32 balance) {
if (!player) return;
BasePlayer targetPlayer = BasePlayer.FindByID(transferUserID);
if (!targetPlayer) return;
TransferToPlayer(player, targetPlayer, balance);
}
private Dictionary<String, List<String>> cachedUI = new();
void API_REMOVE_BALANCE(UInt64 userID, Int32 balance) {
String idString = userID.ToString();
API_REMOVE_BALANCE(idString, balance);
}
private String SQL_Query_CheckColumn() {
String checkColumnQuery = $@"SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = DATABASE()
AND table_name = '{config.generalSetting.mySQLConnectionSettings.dbTableName}'
AND column_name = 'is_hide';";
return checkColumnQuery;
}
Int32 API_GET_BALANCE(BasePlayer player) => GetBalancePlayer(player);
private String SQL_Query_InsertUser() {
String InserUser = $"INSERT INTO `{config.generalSetting.mySQLConnectionSettings.dbTableName}`" + "(`steamid`, `balance`, `limit_balance`, `time`, `last_connection`, `is_hide`) VALUES ('{0}','{1}','{2}','{3}','{4}','{5}')";
return InserUser;
}
private Dictionary<BasePlayer, Int32> saveAfterDeath = new();
private static void AddUI(BasePlayer player, String json) {
if (!player || player.net?.connection == null) return;
CommunityEntity.ServerInstance.ClientRPC<String>(RpcTarget.Player("AddUI", player.net.connection), json);
}
private void ReadData() => courseData = Oxide.Core.Interface.Oxide.DataFileSystem.ReadObject<CourseInfo>("IQSystem/IQEconomic/Exchanger/UpdateCourseTime");
private static IQEconomic _;
private void GameStoreBalanceSet(BasePlayer player, (Int32 exchangeCoins, Int32 storeMoney) exchangeDetail, UInt64 userID = 0) {
if (!config.exchangerSetting.storeSetting.useGameStores) return;
if (!GameStoresRUST) {
PrintWarning(LanguageEn ? "" : "У вас не установлен магазин GameStores");
return;
}
if (exchangeDetail.storeMoney <= 0) return;
Boolean isSetOffline = !player || exchangeDetail.exchangeCoins <= 0;
userID = !isSetOffline ? player.userID : userID;
PlayerInfo pInfo = PlayerInfo.Get(userID.ToString());
if (!isSetOffline) {
RemoveBalance(player, exchangeDetail.exchangeCoins, TypeLog.Exchanger);
SendChat(GetLang("CHAT_ALERT_EXCHANGED_SUCCES", player.UserIDString), player);
if (config.exchangerSetting.limitStoreMoney > 0) {
if (pInfo != null) pInfo.LimitBalance += exchangeDetail.storeMoney;
}
}
GameStoresRUST.CallHook("API_ChangePlayerBalance", userID, exchangeDetail.storeMoney, "plus", null, new Action<Boolean, String>((code, result) => {
LogToFile("DebugGS", $"GameStores DebugFile : isSetOffline : {isSetOffline}, exchangeCoins : {exchangeDetail.exchangeCoins}, code : {code}, result : {result}", this);
//Cloud temp...// PlayerInfo pInfo = PlayerInfo.Get(userID.ToString());
//
if (code) {
// if (isSetOffline) return;
// RemoveBalance(player, exchangeDetail.exchangeCoins, TypeLog.Exchanger);
// SendChat(GetLang("CHAT_ALERT_EXCHANGED_SUCCES", player.UserIDString), player);
//
// if (config.exchangerSetting.limitStoreMoney <= 0) return;
// if (pInfo != null)
// pInfo.LimitBalance += exchangeDetail.storeMoney;
//
return;
}
if (isSetOffline) return;
SendChat(GetLang("CHAT_ALERT_EXCHANGED_FAIL", player.UserIDString), player);
}
));
}
private Int32 GetLimiteExchanged(BasePlayer player) {
Int32 limitExchange = config.exchangerSetting.limitStoreMoney;
if (limitExchange <= 0) return -1;
PlayerInfo pInfo = PlayerInfo.Get(player.UserIDString);
if (pInfo == null) return -1;
Int32 leftLimite = limitExchange - pInfo.LimitBalance;
return leftLimite <= 0 ? 0 : leftLimite;
}
Boolean API_IS_REMOVED_BALANCE(UInt64 userID, Int32 amount) {
BasePlayer player = BasePlayer.FindByID(userID);
return player && DoesHaveEnoughBalance(player, amount);
}
private Boolean IsLimiteBalance(PlayerInfo pInfo) {
Int32 limitExchange = config.exchangerSetting.limitStoreMoney;
if (limitExchange <= 0) return false;
if (pInfo == null) return false;
return pInfo.LimitBalance >= limitExchange;
}
private Configuration.ExchangerSetting.CourseController actualyCourse = null;
private static Double CurrentTime => DateTime.UtcNow.Subtract(epoch).TotalSeconds;
private const String transferPrivilage = "iqeconomic.transferuse";
private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0);
Boolean API_MONEY_TYPE() => config.generalSetting.typeCoins == TypeCoins.Virtual;
private const Boolean LanguageEn = false;
private Boolean IsBlockedEarningKillPlayer(BasePlayer player, BasePlayer targetPlayer) => IsFriends(player, targetPlayer.userID) || IsDuel(player.userID);
private String SQL_Query_AddNewColumn() {
String addColumnQuery = $@"
ALTER TABLE `{config.generalSetting.mySQLConnectionSettings.dbTableName}`
ADD COLUMN `is_hide` VARCHAR(6) NOT NULL DEFAULT 'false';";
return addColumnQuery;
}
void API_SET_BALANCE(UInt64 userID, Int32 balance, ItemContainer itemContainer = null) {
String idString = userID.ToString();
API_SET_BALANCE(idString, balance, itemContainer);
}
private void DrawUI_StaticIconBalance(BasePlayer player) {
if (_interface == null) return;
DestroyUI(player, InterfaceBuilder.UI_PANEL_STATIC_BALANCE);
List<String> cashedStaticPanel = GetOrSetCacheUI("UI_Balance_Panel_Icon_Static");
if (cashedStaticPanel != null) {
foreach (String uiCached in cashedStaticPanel) AddUI(player, uiCached);
}
else {
String Interface = InterfaceBuilder.GetInterface("UI_Balance_Panel_Icon_Static");
if (Interface == null) return;
List<String> newUI = GetOrSetCacheUI("UI_Balance_Panel_Icon_Static", Interface);
cashedStaticPanel = newUI;
foreach (String uiCached in cashedStaticPanel) AddUI(player, uiCached);
}
}
private Boolean DoesHaveEnoughBalance(Int32 balancePlayer, Int32 amount = 0) {
if (balancePlayer == 0) return false;
return balancePlayer >= amount;
}
void API_SET_BALANCE(String userID, Int32 balance, ItemContainer itemContainer = null) {
if (!UInt64.TryParse(userID, out UInt64 id)) return;
BasePlayer player = BasePlayer.FindByID(id);
if(!player) AddBalance(userID, balance, TypeLog.API);
else AddBalance(player, balance, TypeLog.API);
}
private Object CanUserLogin(String name, String id) {
if (sqlConnection == null) {
PrintError(LanguageEn ? "" : "Невозможно сохранить или получить данные игрока! SQL соединение недоступно....");
return null;
}
GetPlayerSQL(id);
return null;
}
private IEnumerator MigrateProcess() {
foreach (BasePlayer player in BasePlayer.activePlayerList) DisconnectedPlayer(player);
InterfaceBuilder.DestroyAll();
String[] playersData = PlayerInfo.GetFiles();
Int32 countFiles = playersData.Length;
PrintWarning(LanguageEn ? $"[MIGRATION] The process of transferring data from the local storage to MySQL -> {countFiles} records has started, it may take some time, do not turn off the server and do not restart the plugin, after closing you will see a message.Approximate waiting time : {Math.Round((countFiles * 0.5) / 60)} minutes" : $"[MIGRATION] Запущен процесс переноса данных с локального хранилища в MySQL -> {countFiles} записей, это может занять какое-то время, не выключайте сервер и не перезагружайте плагин, после заверешения вы увидите сообщение.Примерное время ожидания : {Math.Round((countFiles * 0.5) / 60)} минут(ы)");
yield return CoroutineEx.waitForSeconds(0.25f);
PrintWarning(LanguageEn ? "[MIGRATION] Trying to connect to MySQL" : "[MIGRATION] Попытка подключения к MySQL");
SQL_OpenConnection(true);
yield return CoroutineEx.waitForSeconds(10f);
if (sqlConnection != null) PrintWarning(LanguageEn ? "[MIGRATION] Successfully connected to MySQL" : "[MIGRATION] Успешно подключились к MySQL");
else {
PrintWarning(LanguageEn ? "[MIGRATION] Failed to connect to MySQL" : "[MIGRATION] Не удалось подключиться к MySQL");
if (coroutineMigrate != null) {
ServerMgr.Instance.StopCoroutine(coroutineMigrate);
coroutineMigrate = null;
}
}
yield return CoroutineEx.waitForSeconds(1f);
PrintWarning(LanguageEn ? "[MIGRATION] The process of transferring player data to MySQL has been started" : "[MIGRATION] Запущен процесс переноса данных игроков в MySQL");
Int32 iterationCount = 0;
foreach (String playerIDs in playersData) {
PlayerInfo pInfo = PlayerInfo.GetOffline(playerIDs);
if(pInfo == null) continue;
ImportPlayerSQL(playerIDs, pInfo.Balance, pInfo.LimitBalance, pInfo.Time, pInfo.DateTime, pInfo.IsHide, true);
iterationCount++;
if (iterationCount % 100 == 0 || iterationCount == countFiles) {
Int32 remaining = countFiles - iterationCount;
PrintWarning(LanguageEn ? $"[MIGRATION] The transfer is in progress...There are {remaining} players left" : $"[MIGRATION] Перенос в процессе...Осталось {remaining} игроков");
}
yield return CoroutineEx.waitForSeconds(0.25f);
}
yield return CoroutineEx.waitForSeconds(10f);
PrintWarning(LanguageEn ? "[MIGRATION] The transfer is complete! The plugin has been unloaded, now you can delete all the data files" : "[MIGRATION] Перенос завершен! Плагин выгружен, теперь вы можете удалить все дата-файлы");
config.generalSetting.mySQLConnectionSettings.useMySQL = true;
SaveConfig();
NextTick(() => Interface.Oxide.UnloadPlugin(Name));
}
private class PlayerInfo : DataManager<PlayerInfo> {
public static void Import(String id, PlayerInfo data) => ImportPlayer(id, data);
public static void Save(String id) => SavePlayer(id);
public static void SaveOffline(String id, PlayerInfo data) => SaveOfflinePlayer(id, data);
public static void Remove(String id) => RemovePlayer(id);
public static void Delete(String id) => DeletePlayer(id);
public static PlayerInfo Load(String id) => LoadPlayer(id);
public static PlayerInfo Get(String id) => GetPlayer(id);
public static PlayerInfo GetOffline(String id) => GetOfflinePlayer(id);
public static String[] GetFiles() => GetFilesPlayers();
[JsonProperty(LanguageEn ? "Player Balance" : "Баланс игрока")]
public Int32 Balance;
[JsonProperty(LanguageEn ? "Player's balance withdrawal limit" : "Лимит вывода баланса игрока")]
public Int32 LimitBalance;
[JsonProperty(LanguageEn ? "Time counter" : "Счетчик времени")]
public Int32 Time;
[JsonProperty(LanguageEn ? "The player's last login to the server" : "Последний вход игрока на сервер")]
public Double DateTime;
[JsonProperty(LanguageEn ? "Is the player's balance menu hidden" : "Скрыта ли меню баланса у игрока")]
public Boolean IsHide;
}
private void OnLootSpawn(LootContainer container) {
if (!container) return;
String shortPrefabName = container.ShortPrefabName;
Item droppedItem = config.generalSetting.physicsItem.GetDroppedItem(shortPrefabName);
if (droppedItem == null) return;
container.Invoke(() => {
if (container.inventory == null) return;
if(!droppedItem.MoveToContainer(container.inventory)) droppedItem.Remove();
}
, 0.25f);
}
private void ExecuteTransfer(BasePlayer player, BasePlayer targetPlayer, Int32 amount) {
RemoveBalance(player, amount, TypeLog.Transfer);
SendChat(GetLang("CHAT_ALERT_TRANSFERED_SUCCES", player.UserIDString, amount, targetPlayer.displayName), player);
AddBalance(targetPlayer, amount, TypeLog.Transfer);
SendChat(GetLang("CHAT_ALERT_TRANSFERED_SUCCES", targetPlayer.UserIDString, amount, player.displayName), targetPlayer);
}
private void OnEntityDeath(BradleyAPC entity, HitInfo info) {
Configuration.EarningCoins.PresetEarning earning = config.earningCoins.killedBradley;
if (!earning.useEarning) return;
if (!entity || info == null) return;
BasePlayer player = info.InitiatorPlayer;
if(!IsTakeEarning(player, earning)) return;
AddBalance(player, earning.countMoney, TypeLog.Action, earning.useChatAlert ? "CHAT_ALERT_EARNING_DESTROY_BRADLEY" : default);
}
private const String arrowLogReceived = "=====>";
private void DrawUI_HudPanel(BasePlayer player) {
if (!isUsedUI) return;
if (player.IsSleeping()) return;
PlayerInfo pInfo = PlayerInfo.Get(player.UserIDString);
if (pInfo == null) return;
Configuration.GeneralSetting.InterfaceSetting.PresetUI presetUI = config.generalSetting.interfaceSetting.presetUI;
if (pInfo.IsHide) DrawUI_StaticIconBalance(player);
else DrawUI_StaticPanelBalance(player);
if (presetUI.useTransferButton && config.exchangerSetting.useExchanger) DrawUI_StaticExchangerPanel(player);
if (presetUI.useHideUI) DrawUI_StaticHider(player, pInfo.IsHide);
}
private const String actionLogRemoved = (LanguageEn ? "written off" : "списано");
Boolean API_IS_USER(UInt64 userID) => API_IS_USER(userID.ToString());
private String SQL_Query_SelectedDatabase(String steamId) {
String SelectUser = $"SELECT * FROM `{config.generalSetting.mySQLConnectionSettings.dbTableName}` WHERE steamid = '{steamId}'";
return SelectUser;
}
private const String actionLogReceived = (LanguageEn ? "received" : "получил");
private void AddBalance(BasePlayer player, Int32 amount, TypeLog typeLog, String alertMessage = default) {
if (!player) return;
PlayerInfo pInfo = PlayerInfo.Get(player.UserIDString);
if (pInfo == null) return;
AddBalance(player, pInfo, amount, typeLog, alertMessage);
}
void API_SET_BALANCE(BasePlayer player, Int32 balance, ItemContainer itemContainer = null) => AddBalance(player, balance, TypeLog.API);
private void HookController() {
Configuration.EarningCoins earningCoins = config.earningCoins;
if (!isUsedUI) {
Unsubscribe(nameof(OnPlayerSleepEnded));
Unsubscribe(nameof(OnPlayerSleep));
}
if (!isUsedSQL) Unsubscribe(nameof(CanUserLogin));
else {
Unsubscribe(nameof(OnNewSave));
Unsubscribe(nameof(OnPlayerConnected));
}
if (config.generalSetting.typeCoins == TypeCoins.Virtual) {
Unsubscribe(nameof(OnLootSpawn));
Unsubscribe(nameof(OnItemSplit));
}
else {
if (!config.generalSetting.physicsItem.droppedItem.isUseDropped) Unsubscribe(nameof(OnLootSpawn));
if (config.generalSetting.physicsItem.skinID == 0 || plugins.Find("Stacks") || plugins.Find("Loottable") || plugins.Find("CustomSkinsStacksFix") || plugins.Find("SkinBox") || plugins.Find("StackModifier")) {
Unsubscribe(nameof(OnPluginLoaded));
Unsubscribe(nameof(OnItemSplit));
}
}
Boolean anyGather = earningCoins.resourceGatherEarning.Count == 0 || earningCoins.resourceGatherEarning.Any(x => x.Value.useEarning);
if(!anyGather) Unsubscribe(nameof(OnDispenserBonusReceived));
Boolean anyCollectable = earningCoins.resourceCollectable.Count == 0 || earningCoins.resourceCollectable.Any(x => x.Value.useEarning);
if(!anyCollectable) Unsubscribe(nameof(OnCollectiblePickedup));
if (!isUsedUI && !earningCoins.killedPlayer.useEarning && !earningCoins.killedNpcs.useEarning && !config.generalSetting.physicsItem.useSaveDeath) Unsubscribe(nameof(OnPlayerDeath));
if(!config.generalSetting.physicsItem.useSaveDeath || config.generalSetting.typeCoins == TypeCoins.Virtual) Unsubscribe(nameof(OnPlayerRespawned));
if (!earningCoins.killedBradley.useEarning && !earningCoins.killedAnimal.useEarning && !earningCoins.killedBarrels.useEarning) Unsubscribe(nameof(OnEntityDeath));
if (!earningCoins.killedHelicopter.useEarning) {
Unsubscribe(nameof(OnPatrolHelicopterKill));
Unsubscribe(nameof(OnEntityKill));
}
}
private void ChatCommandBalance(BasePlayer player) {
if (!player) return;
Int32 balancePlayer = GetBalancePlayer(player);
SendChat(GetLang("CHAT_ALERT_BALANCE", player.UserIDString, balancePlayer), player);
}
private class ImageUI {
private const String _path = "IQSystem/IQEconomic/Images/";
private const String _printPath = "data/" + _path;
private readonly Dictionary<String, ImageData> _images = new() {
{
"PANEL_BALANCE", new ImageData()
}
, {
"PANEL_EXCAHNGER", new ImageData()
}
, {
"ICON_BALANCE", new ImageData()
}
, {
"ICON_EXCHANGER", new ImageData()
}
, {
"ICON_UNHIDE", new ImageData()
}
, {
"ICON_HIDE", new ImageData()
}
,
}
;
private enum ImageStatus {
NotLoaded, Loaded, Failed
}
private class ImageData {
public ImageStatus Status = ImageStatus.NotLoaded;
public String Id {
get;
set;
}
}
public String GetImage(String name) {
if (_images.TryGetValue(name, out ImageData image) && image.Status == ImageStatus.Loaded) return image.Id;
return null;
}
public void DownloadImage() {
if (_ == null) return;
KeyValuePair<String, ImageData>? image = null;
foreach (KeyValuePair<String, ImageData> img in _images) {
if (img.Value.Status == ImageStatus.NotLoaded) {
image = img;
break;
}
}
if (image != null) {
ServerMgr.Instance.StartCoroutine(ProcessDownloadImage(image.Value));
}
else {
List<String> failedImages = new List<String>();
foreach (KeyValuePair<String, ImageData> img in _images) {
if (img.Value.Status == ImageStatus.Failed) {
failedImages.Add(img.Key);
}
}
if (failedImages.Count > 0) {
String images = string.Join(", ", failedImages);
_.PrintError(LanguageEn ? $"Failed to load the following images: {images}.Perhaps you did not upload them to the '{_printPath}' folder." : $"Не удалось загрузить следующие изображения: {images}.Возможно, вы не загрузили их в папку '{_printPath}'.");
Interface.Oxide.UnloadPlugin(_.Name);
}
else {
_.Puts(LanguageEn ? $"{_images.Count} images downloaded successfully!" : $"{_images.Count} изображений успешно загружено!");
_interface = new InterfaceBuilder();
if(!_.isUsedSQL) foreach (BasePlayer player in BasePlayer.activePlayerList) {
_.OnPlayerConnected(player);
_.DrawUI_HudPanel(player);
}
}
}
}
public void UnloadImages() {
foreach (KeyValuePair<string, ImageData> item in _images) if(item.Value.Status == ImageStatus.Loaded) if (item.Value?.Id != null) FileStorage.server.Remove(uint.Parse(item.Value.Id), FileStorage.Type.png, CommunityEntity.ServerInstance.net.ID);
_images?.Clear();
}
private IEnumerator ProcessDownloadImage(KeyValuePair<String, ImageData> image) {
String url = $"file://{Interface.Oxide.DataDirectory}/{_path}{image.Key}.png";
using UnityWebRequest www = UnityWebRequestTexture.GetTexture(url);
yield return www.SendWebRequest();
if (www.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError) image.Value.Status = ImageStatus.Failed;
else {
Texture2D tex = DownloadHandlerTexture.GetContent(www);
image.Value.Id = FileStorage.server.Store(tex.EncodeToPNG(), FileStorage.Type.png, CommunityEntity.ServerInstance.net.ID).ToString();
image.Value.Status = ImageStatus.Loaded;
UnityEngine.Object.DestroyImmediate(tex);
}
DownloadImage();
}
}
private void DrawUI_UpdateExchangeFull_Menu(BasePlayer player, Int32 balance, Int32 limiteExchanged) {
if (_interface == null) return;
String Interface = InterfaceBuilder.GetInterface("UI_Exchanger_Update_FullExchange_Button");
if (Interface == null) return;
Interface = Interface.Replace("%FULL_COURSE_TITLE%", actualyCourse.GetFullBalanceCourseString(player, balance, limiteExchanged));
AddUI(player, Interface);
}
private void OnDispenserBonusReceived(ResourceDispenser dispenser, BasePlayer player, Item item) {
if (dispenser == null || !player || item == null) return;
Dictionary<String, Configuration.EarningCoins.PresetEarning> resourceGather = config.earningCoins.resourceGatherEarning;
String shortname = item.info.shortname;
if(!resourceGather.TryGetValue(shortname, out Configuration.EarningCoins.PresetEarning resourcePreset)) return;
if(!resourcePreset.useEarning) return;
if(!resourcePreset.IsEa
// ... [Code preview truncated due to length. Full source in attached file] ...