C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// Reference: Oxide.Core.MySql
// Reference: Oxide.Core.SQLite
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Oxide.Core;
using Oxide.Core.Database;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Zeiser Levels Remastered", "Zeiser/Visagalis", "1.6.6", ResourceId = 1453)]
[Description("Lets players level up as they harvest different resources and when crafting")]
class ZLevelsRemastered : RustPlugin {
[PluginReference]
Plugin EventManager;
#region SQL Things
readonly Core.MySql.Libraries.MySql _mySql = new Core.MySql.Libraries.MySql();
readonly Core.SQLite.Libraries.SQLite _sqLite = new Core.SQLite.Libraries.SQLite();
Connection _mySqlConnection;
Connection _sqLiteConnection;
public Dictionary<ulong, Dictionary<string, long>> playerList = new Dictionary<ulong, Dictionary<string, long>>();
readonly string sqLiteDBFile = "ZLevelsRemastered.db";
void StartConnection() {
if (usingMySQL() && _mySqlConnection == null) {
_mySqlConnection = _mySql.OpenDb(dbConnection["Host"].ToString(), Convert.ToInt32(dbConnection["Port"]), dbConnection["Database"].ToString(), dbConnection["Username"].ToString(), dbConnection["Password"].ToString(), this);
Puts("Connection opened.(MySQL)");
}
else {
_sqLiteConnection = _sqLite.OpenDb(sqLiteDBFile, this);
CheckConnection();
}
}
void CheckConnection() {
var tableStucture = new Dictionary<string, string> {
{
"UserID", "INTEGER\tNOT NULL"
}
, {
"Name", "TEXT\tNOT NULL"
}
, {
"WCLevel", "INTEGER"
}
, {
"WCPoints", "INTEGER"
}
, {
"MLevel", "INTEGER"
}
, {
"MPoints", "INTEGER"
}
, {
"SLevel", "INTEGER"
}
, {
"SPoints", "INTEGER"
}
, {
"CLevel", "INTEGER"
}
, {
"CPoints", "INTEGER"
}
, {
"LastDeath", "INTEGER"
}
, {
"LastLoginDate", "INTEGER"
}
, {
"XPMultiplier", "INTEGER\tNOT NULL\tDEFAULT 100"
}
}
;
var queryText = "CREATE TABLE IF NOT EXISTS \"RPG_User\" (";
foreach (var structItem in tableStucture) {
queryText += "`" + structItem.Key + "` " + structItem.Value + ", ";
}
queryText += "PRIMARY KEY(UserID))";
var sql = new Sql(queryText);
_sqLite.Query(sql, _sqLiteConnection, list => {
//CheckTableIntegrity(tableStucture);
}
);
}
/* TODO: Will finish this one day!
void CheckTableIntegrity(Dictionary<string, string> tableStucture)
{
var sql = new Sql("PRAGMA table_info(RPG_User)");
_sqLite.Query(sql, _sqLiteConnection, list =>
{
if (list.Count > 0) // Save to DB failed.foreach (var listItem in list)
{
string currColumn = listItem["type"] +
(listItem["notnull"].ToString() == "1" ? "\tNOT NULL" : "") +
(listItem["dflt_value"].ToString() != string.Empty ? "\tDEFAULT " + listItem["dflt_value"].ToString() : "");
if (tableStucture[listItem["name"].ToString()] != currColumn)
alterTable(currColumn, tableStucture[listItem["name"].ToString()]);
}
});
}
void alterTable(string currColumn, string fixedColumn)
{
Puts("Altering table from: [" + currColumn + "] to [" + fixedColumn + "]");
var sql = new Sql("BEGIN TRANSACTION;PRAGMA schema_version;");
_sqLite.Query(sql, _sqLiteConnection, list =>
{
if (list.Count > 0)
{
Puts("1");
int schemaVersion = Convert.ToInt32(list[0]["schema_version"]);
var sql2 = new Sql("PRAGMA writable_schema=ON;");
_sqLite.Query(sql2, _sqLiteConnection, list2 =>
{
Puts("2");
var sql3 = new Sql("SELECT * FROM sqlite_master WHERE type='table' and name='RPG_User';");
_sqLite.Query(sql3, _sqLiteConnection, list3 =>
{
if (list3.Count > 0)
{
Puts("3");
string modifiedSql = list3[0]["sql"].ToString();
Puts("Before: " + modifiedSql);
modifiedSql = modifiedSql.Replace(currColumn, fixedColumn);
Puts("After: " + modifiedSql);
var sql4 =
new Sql("UPDATE sqlite_master SET sql=@0 WHERE type='table' and name='RPG_User';",
modifiedSql);
_sqLite.Query(sql4, _sqLiteConnection, list4 =>
{
if (list4.Count > 0)
{
Puts("4");
var sql5 =
new Sql("PRAGMA schema_version=" + ++schemaVersion +
";PRAGMA writable_schema=OFF;END TRANSACTION");
_sqLite.Query(sql5, _sqLiteConnection, list5 =>
{
Puts("5");
var sql6 = new Sql("PRAGMA integrity_check");
_sqLite.Query(sql5, _sqLiteConnection, list6 =>
{
if (list6.Count > 0)
{
Puts("6");
Puts(list6[0]["integrity_check"].ToString());
}
});
});
}
});
}
});
});
}
});
}
*/
public void setPointsAndLevel(ulong userID, string skill, long points, long level) {
if (!playerList.ContainsKey(userID)) playerList.Add(userID, new Dictionary<string, long>());
setPlayerData(userID, skill + "Points", points == 0 ? getLevelPoints(level) : points);
setPlayerData(userID, skill + "Level", level);
}
public void loadUser(BasePlayer player) {
var statsInit = new Dictionary<string, long>();
foreach (var skill in Skills.ALL) {
statsInit.Add(skill + "Level", 1);
statsInit.Add(skill + "Points", 10);
}
var currTime = ToEpochTime(DateTime.UtcNow);
statsInit.Add("LastDeath", currTime);
statsInit.Add("LastLoginDate", currTime);
statsInit.Add("XPMultiplier", 100);
var sql = Sql.Builder.Append("SELECT * FROM RPG_User WHERE UserID = @0", player.userID);
if (usingMySQL()) {
_mySql.Query(sql, _mySqlConnection, list => {
initPlayer(player, statsInit, list);
}
);
}
else {
_sqLite.Query(sql, _sqLiteConnection, list => {
initPlayer(player, statsInit, list);
}
);
}
}
void initPlayer(BasePlayer player, Dictionary<string, long> statsInit, List<Dictionary<string, object>> sqlData) {
var needToSave = true;
var tempElement = new Dictionary<string, long>();
if (sqlData.Count > 0) {
foreach (var key in statsInit.Keys) {
if (sqlData[0][key] != DBNull.Value) tempElement.Add(key, Convert.ToInt64(sqlData[0][key]));
}
needToSave = false;
}
foreach (var tempItem in tempElement) statsInit[tempItem.Key] = tempItem.Value;
initPlayerData(player, statsInit);
if (needToSave) saveUser(player);
RenderUI(player);
}
void setPlayerData(ulong userID, string key, long value) {
if (playerList[userID].ContainsKey(key)) playerList[userID][key] = value;
else playerList[userID].Add(key, value);
}
void initPlayerData(BasePlayer player, Dictionary<string, long> playerData) {
foreach (var skill in Skills.ALL) setPointsAndLevel(player.userID, skill, playerData[skill + "Points"], playerData[skill + "Level"]);
foreach (var dataItem in playerData) {
if (dataItem.Key.EndsWith("Level") || dataItem.Key.EndsWith("Points")) continue;
setPlayerData(player.userID, dataItem.Key, dataItem.Value);
}
}
void OnPlayerLootEnd(PlayerLoot inventory) {
var player = inventory.GetComponent<BasePlayer>();
if (player != null && inPlayerList(player.userID)) {
RenderUI(player);
}
}
void OnPlayerDisconnected(BasePlayer player) {
if (guioff.Contains(player.userID)) guioff.Remove(player.userID);
else CuiHelper.DestroyUi(player, "StatsUI");
if (inPlayerList(player.userID)) {
saveUser(player);
if (playerList.ContainsKey(player.userID)) playerList.Remove(player.userID);
}
}
void OnPlayerInit(BasePlayer player) {
long multiplier = 100;
var playerPermissions = permission.GetUserPermissions(player.UserIDString);
if (playerPermissions.Any(x => x.ToLower().StartsWith("zlvlboost"))) {
var perm = playerPermissions.First(x => x.ToLower().StartsWith("zlvlboost"));
if (!long.TryParse(perm.ToLower().Replace("zlvlboost", ""), out multiplier)) multiplier = 100;
}
editMultiplierForPlayer(multiplier, player.userID);
loadUser(player);
}
public void SaveUsers() {
foreach (var user in BasePlayer.activePlayerList) saveUser(user);
}
static string EncodeNonAsciiCharacters(string value) {
var sb = new StringBuilder();
foreach (var c in value) {
if (c > 127) {
// This character is too big for ASCII
var encodedValue = "";
sb.Append(encodedValue);
}
else {
sb.Append(c);
}
}
return sb.ToString();
}
public void saveUser(BasePlayer player) {
if (!playerList.ContainsKey(player.userID)) {
Puts("Trying to save player, who haven't been loaded yet? Player name: " + player.displayName);
return;
}
var statsInit = getConnectedPlayerDetailsData(player.userID);
var name = EncodeNonAsciiCharacters(player.displayName);
var sqlText = "REPLACE INTO RPG_User (UserID, Name, WCLevel, WCPoints, MLevel, MPoints, SLevel, SPoints, CLevel, CPoints, LastDeath, LastLoginDate, XPMultiplier) " + "VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12)";
var sql = Sql.Builder.Append(sqlText, player.userID, //0
name, //1
statsInit["WCLevel"], //2
statsInit["WCPoints"], //3
statsInit["MLevel"], //4
statsInit["MPoints"], //5
statsInit["SLevel"], //6
statsInit["SPoints"], //7
statsInit["CLevel"], //8
statsInit["CPoints"], //9
statsInit["LastDeath"], //10
statsInit["LastLoginDate"], //11
statsInit["XPMultiplier"]);
//12
if (usingMySQL()) {
_mySql.Insert(sql, _mySqlConnection, list => {
if (list == 0) // Save to DB failed.Puts("OMG WE DIDN'T SAVED IT!: " + sql.SQL);
}
);
}
else {
_sqLite.Insert(sql, _sqLiteConnection, list => {
if (list == 0) // Save to DB failed.Puts("OMG WE DIDN'T SAVED IT!: " + sql.SQL);
}
);
}
}
public Dictionary<string, long> getConnectedPlayerDetailsData(ulong userID) {
if (!playerList.ContainsKey(userID)) return null;
var statsInit = new Dictionary<string, long>();
foreach (var skill in Skills.ALL) {
statsInit.Add(skill + "Level", getLevel(userID, skill));
statsInit.Add(skill + "Points", getPoints(userID, skill));
}
statsInit.Add("LastDeath", playerList[userID]["LastDeath"]);
statsInit.Add("LastLoginDate", playerList[userID]["LastLoginDate"]);
statsInit.Add("XPMultiplier", playerList[userID]["XPMultiplier"]);
return statsInit;
}
#endregion
public static class Skills {
public static string CRAFTING = "C";
public static string WOODCUTTING = "WC";
public static string SKINNING = "S";
public static string MINING = "M";
public static string[] ALL = {
WOODCUTTING, MINING, SKINNING, CRAFTING
}
;
}
List<ulong> guioff = new List<ulong>();
Dictionary<string, string> colors = new Dictionary<string, string>() {
{
Skills.WOODCUTTING, "#FFDDAA"
}
, {
Skills.MINING, "#DDDDDD"
}
, {
Skills.SKINNING, "#FFDDDD"
}
, {
Skills.CRAFTING, "#CCFF99"
}
}
;
class CraftData {
public Dictionary<string, CraftInfo> CraftList = new Dictionary<string, CraftInfo>();
}
CraftData _craftData;
#region Stats
[HookMethod("SendHelpText")]
void SendHelpText(BasePlayer player) {
var text = "/stats - Displays your stats.\n/statsui - Displays/hides stats UI.\n/statinfo [statsname] - Displays information about stat.\n" + "/topskills - Display max levels reached so far.";
player.ChatMessage(text);
}
[ChatCommand("topskills")]
void StatsTopCommand(BasePlayer player, string command, string[] args) {
PrintToChat(player, "Max stats on server so far:");
foreach (var skill in Skills.ALL) {
if (!IsSkillDisabled(skill)) printMaxSkillDetails(player, skill);
}
}
void printMaxSkillDetails(BasePlayer player, string skill) {
var sql = Sql.Builder.Append("SELECT * FROM RPG_User ORDER BY " + skill + "Level DESC," + skill + "Points DESC LIMIT 1;");
if (usingMySQL()) {
_mySql.Query(sql, _mySqlConnection, list => {
if (list.Count > 0) printMaxSkillDetails(player, skill, list);
}
);
}
else {
_sqLite.Query(sql, _sqLiteConnection, list => {
if (list.Count > 0) printMaxSkillDetails(player, skill, list);
}
);
}
}
void printMaxSkillDetails(BasePlayer player, string skill, List<Dictionary<string, object>> sqlData) {
PrintToChat(player, "<color=" + colors[skill] + ">" + messages[skill + "Skill"] + ": " + sqlData[0][skill + "Level"] + " (XP: " + sqlData[0][skill + "Points"] + ")</color> <- " + sqlData[0]["Name"]);
}
[ConsoleCommand("zinfo")]
void InfoCommand(ConsoleSystem.Arg arg) {
if (arg.connection != null) return;
if (arg.Args == null || arg.Args.Length != 1) {
Puts("Syntax is: zinfo name/steamid");
Puts("Example: zinfo visagalis");
return;
}
var playerName = arg.Args[0];
var player = rust.FindPlayer(playerName);
if (player != null) {
var playerData = getConnectedPlayerDetailsData(player.userID);
if (playerData == null) Puts("PlayerData IS NULL!!!");
Puts("Stats for player: [" + player.displayName + "]");
Puts("Woodcutting: " + playerData["WCLevel"] + " XP: [" + playerData["WCPoints"] + "]");
Puts("Mining: " + playerData["MLevel"] + " XP: [" + playerData["MPoints"] + "]");
Puts("Skinning: " + playerData["SLevel"] + " XP: [" + playerData["SPoints"] + "]");
Puts("Crafting: " + playerData["CLevel"] + " XP: [" + playerData["CPoints"] + "]");
Puts("XP Multiplier: " + playerData["XPMultiplier"] + " % ");
}
}
[ConsoleCommand("zlvl")]
void ZlvlCommand(ConsoleSystem.Arg arg) {
if (arg.connection != null) return;
if (arg.Args == null || arg.Args.Length != 3) {
Puts("Syntax is: zlvl name/steamid skill [OPERATOR]NUMBER");
Puts("Example: zlvl Visagalis WC /2 -- visagalis gets his WC level divided by 2.");
Puts("Example: zlvl * * +3 -- Everyone currently playing in the server gets +3 for all skills.");
Puts("Example: zlvl ** * /2 -- Everyone (including offline players) gets their level divided by 2.");
Puts("Instead of names you can use wildcard(*): * - affects online players, ** - affects all players");
Puts("Possible operators: *(XP Modified %), +(Adds level), -(Removes level), /(Divides level)");
return;
}
var playerName = arg.Args[0];
var p = rust.FindPlayer(playerName);
if (p != null || (playerName == "*" || playerName == "**")) {
var playerMode = 0;
// Exact player
if (playerName == "*") playerMode = 1;
// Online players
else if (playerName == "**") playerMode = 2;
// All players
var skill = arg.Args[1].ToUpper();
if (skill == Skills.WOODCUTTING || skill == Skills.MINING || skill == Skills.SKINNING || skill == Skills.CRAFTING || skill == "*") {
var allSkills = skill == "*";
var mode = 0;
// 0 = SET, 1 = ADD, 2 = SUBTRACT, 3 = multiplier, 4 = divide
int value;
var correct = false;
if (arg.Args[2][0] == '+') {
mode = 1;
correct = int.TryParse(arg.Args[2].Replace("+", ""), out value);
}
else if (arg.Args[2][0] == '-') {
mode = 2;
correct = int.TryParse(arg.Args[2].Replace("-", ""), out value);
}
else if (arg.Args[2][0] == '*') {
mode = 3;
correct = int.TryParse(arg.Args[2].Replace("*", ""), out value);
}
else if (arg.Args[2][0] == '/') {
mode = 4;
correct = int.TryParse(arg.Args[2].Replace("/", ""), out value);
}
else {
correct = int.TryParse(arg.Args[2], out value);
}
if (correct) {
if (mode == 3) // Change XP Multiplier.{
if (!allSkills) {
Puts("XPMultiplier is changeable for all skills! Use * instead of " + skill + ".");
return;
}
if (playerMode == 1) {
foreach (var currPlayer in BasePlayer.activePlayerList) editMultiplierForPlayer(value, currPlayer.userID);
}
else if (playerMode == 2) editMultiplierForPlayer(value);
else if (p != null) editMultiplierForPlayer(value, p.userID);
Puts("XP rates has changed to " + value + "% of normal XP for " + (playerMode == 1 ? "ALL ONLINE PLAYERS" : (playerMode == 2 ? "ALL PLAYERS" : p.displayName)));
return;
}
if (playerMode == 1) {
foreach (var currPlayer in BasePlayer.activePlayerList) adminModifyPlayerStats(skill, value, mode, currPlayer);
}
else if (playerMode == 2) adminModifyPlayerStats(skill, value, mode);
else adminModifyPlayerStats(skill, value, mode, p);
}
}
else {
Puts("Incorrect skill.Possible skills are: WC, M, S, C, *(All skills).");
}
}
else {
Puts("Player with name: " + arg.Args[0] + " haven't been found online.");
}
}
void adminModifyPlayerStats(string skill, long level, int mode, BasePlayer p = null) {
if (skill == "*") {
foreach (var currSkill in Skills.ALL) {
if (p == null) {
var action = "";
switch (mode) {
case 1 : action = "+";
break;
case 2 : action = "-";
break;
case 4 : action = "/";
break;
}
if (string.IsNullOrEmpty(action)) {
Puts("You can't just SET everyone's level, use + - or / operator.");
return;
}
var sqlText = "UPDATE RPG_User SET ";
var skillLevel = currSkill + "Level";
sqlText += skillLevel + "=" + skillLevel + action + level + ", ";
sqlText += currSkill + "Points=0;" + (levelCaps[currSkill].ToString() != "0" ? ("UPDATE RPG_User SET " + skillLevel + "=" + levelCaps[currSkill] + " WHERE " + skillLevel + ">" + levelCaps[currSkill] + ";") : "") + "UPDATE RPG_User SET " + skillLevel + "=1 WHERE " + skillLevel + "< 1;";
var sql = Sql.Builder.Append(sqlText);
if (usingMySQL()) _mySql.ExecuteNonQuery(sql, _mySqlConnection);
else _sqLite.ExecuteNonQuery(sql, _sqLiteConnection);
foreach (var onlinePlayer in BasePlayer.activePlayerList) loadUser(onlinePlayer);
}
else {
var modifiedLevel = getLevel(p.userID, currSkill);
if (mode == 0) // SET
modifiedLevel = level;
else if (mode == 1) // ADD
modifiedLevel += level;
else if (mode == 2) // SUBTRACT
modifiedLevel -= level;
else if (mode == 4) // DIVIDE
modifiedLevel /= level;
if (modifiedLevel < 1) modifiedLevel = 1;
if (modifiedLevel > Convert.ToInt32(levelCaps[currSkill]) && Convert.ToInt32(levelCaps[currSkill]) != 0) {
modifiedLevel = Convert.ToInt32(levelCaps[currSkill]);
// Don't allow to ADD levels above limits.Puts(
"Warning! You tried to level up player above levelCaps, use SET if you want to have player level over levelCaps.");
}
setPointsAndLevel(p.userID, currSkill, getLevelPoints(modifiedLevel), modifiedLevel);
RenderUI(p);
Puts(messages[currSkill + "Skill"] + " Level for [" + p.displayName + "] has been set to: [" + modifiedLevel + "]");
SendReply(p, "Admin has set your " + messages[currSkill + "Skill"] + " level to: [" + modifiedLevel + "] ");
}
}
}
else {
if (p == null) {
var action = "";
switch (mode) {
case 1 : action = "+";
break;
case 2 : action = "-";
break;
case 4 : action = "/";
break;
}
if (string.IsNullOrEmpty(action)) {
Puts("You can't just SET everyone's level, use + - or / operator.");
return;
}
var sqlText = "UPDATE RPG_User SET ";
var skillLevel = skill + "Level";
sqlText += skillLevel + "=" + skillLevel + action + level + ", ";
sqlText += skill + "Points=0;" + (levelCaps[skill].ToString() != "0" ? ("UPDATE RPG_User SET " + skillLevel + "=" + levelCaps[skill] + " WHERE " + skillLevel + ">" + levelCaps[skill] + ";") : "") + "UPDATE RPG_User SET " + skillLevel + "=1 WHERE " + skillLevel + "< 1;";
var sql = Sql.Builder.Append(sqlText);
if (usingMySQL()) _mySql.ExecuteNonQuery(sql, _mySqlConnection);
else _sqLite.ExecuteNonQuery(sql, _sqLiteConnection);
foreach (var onlinePlayer in BasePlayer.activePlayerList) loadUser(onlinePlayer);
return;
}
var modifiedLevel = getLevel(p.userID, skill);
if (mode == 0) // SET
modifiedLevel = level;
else if (mode == 1) // ADD
modifiedLevel += level;
else if (mode == 2) // SUBTRACT
modifiedLevel -= level;
else if (mode == 4) // DIVIDE
modifiedLevel /= level;
if (modifiedLevel < 1) modifiedLevel = 1;
if (modifiedLevel > Convert.ToInt32(levelCaps[skill]) && Convert.ToInt32(levelCaps[skill]) != 0) {
modifiedLevel = Convert.ToInt32(levelCaps[skill]);
// Don't allow to ADD levels above limits.Puts("Warning! You tried to level up player above levelCaps, use SET if you want to have player level over levelCaps.");
}
setPointsAndLevel(p.userID, skill, getLevelPoints(modifiedLevel), modifiedLevel);
RenderUI(p);
Puts(messages[skill + "Skill"] + " Level for [" + p.displayName + "] has been set to: [" + modifiedLevel + "]");
SendReply(p, "Admin has set your " + messages[skill + "Skill"] + " level to: [" + modifiedLevel + "] ");
}
}
void editMultiplierForPlayer(long multiplier, ulong userID = ulong.MinValue) {
var sqlText = "UPDATE RPG_User SET XPMultiplier = @0";
if (userID != ulong.MinValue) sqlText += " WHERE UserID = @1";
if (userID == ulong.MinValue) {
foreach (var playerDetails in playerList) {
playerDetails.Value["XPMultiplier"] = multiplier;
}
}
else {
if (playerList.ContainsKey(userID)) playerList[userID]["XPMultiplier"] = multiplier;
}
var sql = Sql.Builder.Append(sqlText, multiplier, userID);
if (usingMySQL()) _mySql.ExecuteNonQuery(sql, _mySqlConnection);
else _sqLite.ExecuteNonQuery(sql, _sqLiteConnection);
}
[ChatCommand("stats")]
void StatsCommand(BasePlayer player, string command, string[] args) {
var text = "<color=blue>ZLevels Remastered [" + Version + "] by Visagalis</color>\n" + "<color=yellow>" + (string)messages["StatsHeadline"] + "</color>\n";
foreach (var skill in Skills.ALL) {
text += getStatPrint(player, skill);
}
rust.SendChatMessage(player, text, null, "76561198002115162");
var details = playerList[player.userID];
if (details.ContainsKey("LastDeath")) {
var currentTime = DateTime.UtcNow;
var lastDeath = ToDateTimeFromEpoch(details["LastDeath"]);
var timeAlive = currentTime - lastDeath;
PrintToChat(player, "Time alive: " + ReadableTimeSpan(timeAlive));
if (details["XPMultiplier"].ToString() != "100") PrintToChat(player, "XP rates for you are " + details["XPMultiplier"] + "%");
}
RenderUI(player);
}
public static string ReadableTimeSpan(TimeSpan span) {
var formatted = string.Format("{0}{1}{2}{3}{4}", (span.Days / 7) > 0 ? string.Format("{0:0} weeks, ", span.Days / 7) : string.Empty, span.Days % 7 > 0 ? string.Format("{0:0} days, ", span.Days % 7) : string.Empty, span.Hours > 0 ? string.Format("{0:0} hours, ", span.Hours) : string.Empty, span.Minutes > 0 ? string.Format("{0:0} minutes, ", span.Minutes) : string.Empty, span.Seconds > 0 ? string.Format("{0:0} seconds, ", span.Seconds) : string.Empty);
if (formatted.EndsWith(", ")) formatted = formatted.Substring(0, formatted.Length - 2);
return formatted;
}
[ChatCommand("statinfo")]
void StatInfoCommand(BasePlayer player, string command, string[] args) {
var messagesText = "";
long xpMultiplier = 100;
if (inPlayerList(player.userID)) xpMultiplier = playerList[player.userID]["XPMultiplier"];
if (args.Length == 1) {
var statname = args[0].ToLower();
switch (statname) {
case "mining" : messagesText = "<color=" + colors[Skills.MINING] + ">Mining</color>" + (IsSkillDisabled(Skills.MINING) ? "(DISABLED)" : "") + "\n";
messagesText += "XP per hit: <color=" + colors[Skills.MINING] + ">" + ((int)pointsPerHit[Skills.MINING] * (xpMultiplier / 100f)) + "</color>\n";
messagesText += "Bonus materials per level: <color=" + colors[Skills.MINING] + ">" + ((getGathMult(2, Skills.MINING) - 1) * 100).ToString("0.##") + "%</color>\n";
break;
case "woodcutting" : messagesText = "<color=" + colors[Skills.WOODCUTTING] + ">Woodcutting</color>" + (IsSkillDisabled(Skills.WOODCUTTING) ? "(DISABLED)" : "") + "\n";
messagesText += "XP per hit: <color=" + colors[Skills.WOODCUTTING] + ">" + ((int)pointsPerHit[Skills.WOODCUTTING] * (xpMultiplier / 100f)) + "</color>\n";
messagesText += "Bonus materials per level: <color=" + colors[Skills.WOODCUTTING] + ">" + ((getGathMult(2, Skills.WOODCUTTING) - 1) * 100).ToString("0.##") + "%</color>\n";
break;
case "skinning" : messagesText = "<color=" + colors[Skills.SKINNING] + '>' + "Skinning" + "</color>" + (IsSkillDisabled(Skills.SKINNING) ? "(DISABLED)" : "") + "\n";
messagesText += "XP per hit: <color=" + colors[Skills.SKINNING] + ">" + ((int)pointsPerHit[Skills.SKINNING] * (xpMultiplier / 100f)) + "</color>\n";
messagesText += "Bonus materials per level: <color=" + colors[Skills.SKINNING] + ">" + ((getGathMult(2, Skills.SKINNING) - 1) * 100).ToString("0.##") + "%</color>\n";
break;
case "crafting" : messagesText = "<color=" + colors[Skills.CRAFTING] + '>' + "Crafting" + "</color>" + (IsSkillDisabled(Skills.CRAFTING) ? "(DISABLED)" : "") + "\n";
messagesText += "XP gain: <color=" + colors[Skills.SKINNING] + ">You get " + craftingDetails["XPPerTimeSpent"] + " XP per " + craftingDetails["TimeSpent"] + "s spent crafting.</color>\n";
messagesText += "Bonus: <color=" + colors[Skills.SKINNING] + ">Crafting time is decreased by " + craftingDetails["PercentFasterPerLevel"] + "% per every level.</color>\n";
break;
default : messagesText = "No such stat: " + args[0];
messagesText += "\nYou must choose from these stats: <color=" + colors[Skills.MINING] + ">Mining</color>, <color=" + colors[Skills.SKINNING] + ">Skinning</color>, <color=" + colors[Skills.WOODCUTTING] + ">Woodcutting</color>, <color=" + colors[Skills.CRAFTING] + ">Crafting</color>";
break;
}
}
else {
messagesText = "You must choose from these stats: <color=" + colors[Skills.MINING] + ">Mining</color>, <color=" + colors[Skills.SKINNING] + ">Skinning</color>, <color=" + colors[Skills.WOODCUTTING] + ">Woodcutting</color>, <color=" + colors[Skills.CRAFTING] + ">Crafting</color>";
}
PrintToChat(player, messagesText);
}
[ChatCommand("statsui")]
void StatsUICommand(BasePlayer player, string command, string[] args) {
if (guioff.Contains(player.userID)) {
guioff.Remove(player.userID);
RenderUI(player);
}
else {
guioff.Add(player.userID);
CuiHelper.DestroyUi(player, "StatsUI");
;
}
}
void OnPlayerSleepEnded(BasePlayer player) {
if (inPlayerList(player.userID)) RenderUI(player);
}
void OnLootEntity(BasePlayer looter, BaseEntity target) {
if (!guioff.Contains(looter.userID)) {
CuiHelper.DestroyUi(looter, "StatsUI");
}
}
void OnLootPlayer(BasePlayer looter, BasePlayer beingLooter) {
OnLootEntity(looter, null);
}
void OnLootItem(BasePlayer looter, Item lootedItem) {
OnLootEntity(looter, null);
}
void FillElements(ref CuiElementContainer elements, string mainPanel, int rowNumber, int maxRows, long level, int percent, string skillName, string progressColor, int fontSize, string xpBarAnchorMin, string xpBarAnchorMax) {
var value = 1 / (float)maxRows;
var positionMin = 1 - (value * rowNumber);
var positionMax = 2 - (1 - (value * (1 - rowNumber)));
var xpBarPlaceholder1 = new CuiElement {
Name = CuiHelper.GetGuid(), Parent = mainPanel, Components = {
new CuiImageComponent {
Color = "0.4 0.4 0.4 0.2"
}
, new CuiRectTransformComponent {
AnchorMin = "0 " + positionMin.ToString("0.####"), AnchorMax = $"1 "+ positionMax.ToString("0.####")
}
}
}
;
elements.Add(xpBarPlaceholder1);
var innerXPBar1 = new CuiElement {
Name = CuiHelper.GetGuid(), Parent = xpBarPlaceholder1.Name, Components = {
new CuiImageComponent {
Color = "0 0 0 0.8"
}
, new CuiRectTransformComponent {
AnchorMin = xpBarAnchorMin, AnchorMax = xpBarAnchorMax
}
}
}
;
elements.Add(innerXPBar1);
var innerXPBarProgress1 = new CuiElement {
Name = CuiHelper.GetGuid(), Parent = innerXPBar1.Name, Components = {
new CuiImageComponent() {
Color = progressColor
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = (percent / 100.0) + " 1"
}
}
}
;
elements.Add(innerXPBarProgress1);
var innerXPBarText1 = new CuiElement {
Name = CuiHelper.GetGuid(), Parent = innerXPBar1.Name, Components = {
new CuiTextComponent {
Color = "1 1 1 1", Text = skillName, FontSize = fontSize, Align = TextAnchor.MiddleCenter
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = "1 1"
}
}
}
;
elements.Add(innerXPBarText1);
var xpText1 = new CuiElement {
Name = CuiHelper.GetGuid(), Parent = xpBarPlaceholder1.Name, Components = {
new CuiTextComponent {
Text = percent + "%", FontSize = fontSize, Align = TextAnchor.MiddleRight, Color = "0.749019608 0.760784314 0.780392157 1"
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = $"0.98 1"
}
}
}
;
elements.Add(xpText1);
var lvText1 = new CuiElement {
Name = CuiHelper.GetGuid(), Parent = xpBarPlaceholder1.Name, Components = {
new CuiTextComponent {
Text = "Lv." + level, FontSize = fontSize, Align = TextAnchor.MiddleLeft, Color = "0.749019608 0.760784314 0.780392157 1"
}
, new CuiRectTransformComponent {
AnchorMin = "0.01 0", AnchorMax = $"0.5 1"
}
}
}
;
elements.Add(lvText1);
}
void RenderUI(BasePlayer player) {
if (guioff.Contains(player.userID)) return;
var skillColors = new Dictionary<string, string>();
skillColors.Add("WC", "0.8 0.4 0 1");
skillColors.Add("M", "0.1 0.5 0.8 0.6");
skillColors.Add("S", "0.8 0.1 0 0.6");
skillColors.Add("C", "0.2 0.72 0.5 0.8");
var enabledSkillCount = 0;
foreach (var skill in Skills.ALL) {
if (!IsSkillDisabled(skill)) enabledSkillCount++;
}
CuiHelper.DestroyUi(player, "StatsUI");
var elements = new CuiElementContainer();
var mainName = elements.Add(new CuiPanel {
Image = {
Color = "0.1 0.1 0.1 0.0"
}
, RectTransform = {
AnchorMin = "0.69 0.0140", AnchorMax = "0.83 0.1335"
}
}
, "Hud", "StatsUI");
var fontSize = 12;
var xpBarAnchorMin = "0.16 0.1";
var xpBarAnchorMax = "0.88 0.9";
var currentSKillIndex = 1;
foreach (var skill in Skills.ALL) {
if (!IsSkillDisabled(skill)) {
FillElements(ref elements, mainName, currentSKillIndex, enabledSkillCount, getLevel(player.userID, skill), getExperiencePercentInt(player, skill), messages[skill + "Skill"].ToString(), skillColors[skill], fontSize, xpBarAnchorMin, xpBarAnchorMax);
currentSKillIndex++;
}
}
CuiHelper.AddUi(player, elements);
}
string getStatPrint(BasePlayer player, string skill) {
if (IsSkillDisabled(skill)) return "";
var skillMaxed = (int)levelCaps[skill] != 0 && getLevel(player.userID, skill) == (int)levelCaps[skill];
var bonusText = "";
if (skill == Skills.CRAFTING) bonusText = (getLevel(player.userID, skill) * (int)craftingDetails["PercentFasterPerLevel"]).ToString("0.##");
else bonusText = ((getGathMult(getLevel(player.userID, skill), skill) - 1) * 100).ToString("0.##");
return string.Format("<color=" + colors[skill] + '>' + (string)messages["StatsText"] + "</color>\n", (string)messages[skill + "Skill"], getLevel(player.userID, skill) + (Convert.ToInt32(levelCaps[skill]) > 0 ? ("/" + levelCaps[skill]) : ""), getPoints(player.userID, skill), skillMaxed ? "â" : getLevelPoints(getLevel(player.userID, skill) + 1).ToString(), bonusText, getExperiencePercent(player, skill), getPenaltyPercent(player, skill) + "%");
}
#endregion
#region Main/Other
/// <summary>
/// Converts the given date value to epoch time./// </summary>
long ToEpochTime(DateTime dateTime) {
var date = dateTime.ToUniversalTime();
var ticks = date.Ticks - new DateTime(1970, 1, 1, 0, 0, 0, 0).Ticks;
var ts = ticks / TimeSpan.TicksPerSecond;
return ts;
}
/// <summary>
/// Converts the given epoch time to a <see cref="DateTime"/> with <see cref="DateTimeKind.Utc"/> kind./// </summary>
DateTime ToDateTimeFromEpoch(long intDate) {
var timeInTicks = intDate * TimeSpan.TicksPerSecond;
return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddTicks(timeInTicks);
}
void Loaded() {
StartConnection();
if ((_craftData = Interface.GetMod().DataFileSystem.ReadObject<CraftData>("ZLevelsCraftDetails")) == null) {
_craftData = new CraftData();
}
foreach (var player in BasePlayer.activePlayerList) {
loadUser(player);
}
}
void OnEntityDeath(BaseCombatEntity entity, HitInfo hitInfo) {
if (entity is BasePlayer) {
var player = (BasePlayer)entity;
var isPlaying = EventManager?.Call("isPlaying", player);
if (!inPlayerList(player.userID) || (isPlaying is bool && (bool)isPlaying)) return;
var penaltyText = "<color=#FF0000>You have lost XP for dying:";
var penaltyExist = false;
foreach (var skill in Skills.ALL) {
if (!IsSkillDisabled(skill)) {
var penalty = GetPenalty(player, skill);
if (penalty > 0) {
penaltyText += "\n* -" + penalty + " " + messages[skill + "Skill"] + " XP.";
removePoints(player.userID, skill, penalty);
penaltyExist = true;
}
}
}
penaltyText += "</color>";
if (penaltyExist) PrintToChat(player, penaltyText);
SetPlayerLastDeathDate(player.userID);
RenderUI(player);
}
}
void SetPlayerLastDeathDate(ulong userID) => setPlayerData(userID, "LastDeath", ToEpochTime(DateTime.UtcNow));
void OnDispenserGather(ResourceDispenser dispenser, BaseEntity entity, Item item) {
var player = entity as BasePlayer;
if (player == null) return;
if (!IsSkillDisabled(Skills.WOODCUTTING)) if ((int)dispenser.gatherType == 0) levelHandler(player, item, Skills.WOODCUTTING);
if (!IsSkillDisabled(Skills.MINING)) if ((int)dispenser.gatherType == 1) levelHandler(player, item, Skills.MINING);
if (!IsSkillDisabled(Skills.SKINNING)) if ((int)dispenser.gatherType == 2) levelHandler(player, item, Skills.SKINNING);
}
void OnCollectiblePickup(Item item, BasePlayer player) {
var skillName = string.Empty;
switch (item.info.shortname.ToLower()) {
case "wood" : skillName = Skills.WOODCUTTING;
break;
case "cloth" : case "mushroom" : case "corn" : case "pumpkin" : case "seed.hemp" : case "seed.pumpkin" : case "seed.corn" : skillName = Skills.SKINNING;
break;
case "metal.ore" : case "sulfur.ore" : case "stones" : skillName = Skills.MINING;
break;
}
if (!string.IsNullOrEmpty(skillName)) levelHandler(player, item, skillName);
else Puts("Developer missed this item, which can be picked up: [" + item.info.shortname + "].Let him know on Oxide forums!");
}
void levelHandler(BasePlayer player, Item item, string skill) {
var xpPercentBefore = getExperiencePercent(player, skill);
var Level = getLevel(player.userID, skill);
var Points = getPoints(player.userID, skill);
item.amount = (int)(item.amount * getGathMult(Level, skill));
var pointsToGet = (int)pointsPerHit[skill];
var xpMultiplier = Convert.ToInt64(playerList[player.userID]["XPMultiplier"]);
Points += Convert.ToInt64(pointsToGet * (xpMultiplier / 100f));
getPointsLevel(Points, skill);
try {
if (Points >= getLevelPoints(Level + 1)) {
var maxLevel = (int)levelCaps[skill] > 0 && Level + 1 > (int)levelCaps[skill];
if (!maxLevel) {
Level = getPointsLevel(Points, skill);
PrintToChat(player, string.Format("<color=" + colors[skill] + '>' + (string)messages["LevelUpText"] + "</color>", (string)messages[skill + "Skill"], Level, Points, getLevelPoints(Level + 1), ((getGathMult(Level, skill) - 1) * 100).ToString("0.##") ) );
}
}
}
catch (Exception ex) {
Puts(ex.Message);
}
setPointsAndLevel(player.userID, skill, Points, Level);
var xpPercentAfter = getExperiencePercent(player, skill);
if (!xpPercentAfter.Equals(xpPercentBefore)) RenderUI(player);
}
#endregion
#region Utility
long getLevelPoints(long level) => 110 * level * level - 100 * level;
long getPointsLevel(long points, string skill) {
var a = 110;
var b = 100;
var c = -points;
var x1 = (-b - Math.Sqrt(b * b - 4 * a * c)) / (2 * a);
if ((int)levelCaps[skill] == 0 || (int)-x1 <= (int)levelCaps[skill]) return (int)-x1;
return (int)levelCaps[skill];
}
double getGathMult(long skillLevel, string skill) {
return 1 + Convert.ToDouble(resourceMultipliers[skill]) * 0.1 * (skillLevel - 1);
}
bool inPlayerList(UInt64 userID) {
return playerList.ContainsKey(userID);
}
#endregion
#region Saving
void OnServerSave() => SaveUsers();
void Unload() {
SaveUsers();
if (_mySqlConnection != null) _mySqlConnection = null;
else {
_sqLiteConnection = null;
}
foreach (var player in BasePlayer.activePlayerList) // destroy UI when unloading.{
if (guioff.Contains(player.userID)) return;
CuiHelper.DestroyUi(player, "StatsUI");
}
}
#endregion
#region Config
Dictionary<string, object> resourceMultipliers;
Dictionary<string, object> levelCaps;
Dictionary<string, object> pointsPerHit;
Dictionary<string, object> craftingDetails;
Dictionary<string, object> percentLostOnDeath;
Dictionary<string, object> messages;
Dictionary<string, object> dbConnection;
protected override void LoadDefaultConfig() {
}
void Init() {
resourceMultipliers = checkCfg<Dictionary<string, object>>("ResourcePerLevelMultiplier", new Dictionary<string, object> {
{
Skills.WOODCUTTING, 2.0d
}
, {
Skills.MINING, 2.0d
}
, {
Skills.SKINNING, 2.0d
}
}
);
levelCaps = checkCfg<Dictionary<string, object>>("LevelCaps", new Dictionary<string, object> {
{
Skills.WOODCUTTING, 200
}
, {
Skills.
// ... [Code preview truncated due to length. Full source in attached file] ...