· 9 years ago · Nov 07, 2016, 11:22 AM
1// Reference: Oxide.Ext.MySql
2// Reference: Oxide.Ext.SQLite
3
4using UnityEngine;
5using System.Collections.Generic;
6using System;
7using Oxide.Core;
8using Oxide.Core.Plugins;
9using Oxide.Game.Rust.Cui;
10using System.Linq;
11using Rust;
12using System.Text;
13using Oxide.Core.Database;
14
15namespace Oxide.Plugins
16{
17 [Info("Zeiser Levels REMASTERED", "Zeiser/Visagalis", "1.6.4", ResourceId = 1453)]
18 [Description("Lets players level up as they harvest different resources and when crafting.")]
19 public class ZLevelsRemastered : RustPlugin
20 {
21 [PluginReference]
22 Plugin EventManager;
23
24 #region SQL Things
25 private readonly Ext.MySql.Libraries.MySql _mySql = new Ext.MySql.Libraries.MySql();
26 private readonly Ext.SQLite.Libraries.SQLite _sqLite = new Ext.SQLite.Libraries.SQLite();
27 private Connection _mySqlConnection = null;
28 private Connection _sqLiteConnection = null;
29 public Dictionary<ulong, Dictionary<string, long>> playerList = new Dictionary<ulong, Dictionary<string, long>>();
30 private readonly string sqLiteDBFile = "ZLevelsRemastered.db";
31
32 private void StartConnection()
33 {
34 if (usingMySQL() && _mySqlConnection == null)
35 {
36 _mySqlConnection = _mySql.OpenDb(dbConnection["Host"].ToString(), Convert.ToInt32(dbConnection["Port"]),
37 dbConnection["Database"].ToString(), dbConnection["Username"].ToString(),
38 dbConnection["Password"].ToString(), this);
39 Puts("Connection opened.(MySQL)");
40 }
41 else
42 {
43 _sqLiteConnection = _sqLite.OpenDb(sqLiteDBFile, this);
44 CheckConnection();
45 }
46 }
47
48 private void CheckConnection()
49 {
50 Dictionary<string, string> tableStucture = new Dictionary<string, string>();
51 tableStucture.Add("UserID", "INTEGER\tNOT NULL");
52 tableStucture.Add("Name", "TEXT\tNOT NULL");
53 tableStucture.Add("WCLevel", "INTEGER");
54 tableStucture.Add("WCPoints", "INTEGER");
55 tableStucture.Add("MLevel", "INTEGER");
56 tableStucture.Add("MPoints", "INTEGER");
57 tableStucture.Add("SLevel", "INTEGER");
58 tableStucture.Add("SPoints", "INTEGER");
59 tableStucture.Add("CLevel", "INTEGER");
60 tableStucture.Add("CPoints", "INTEGER");
61 tableStucture.Add("LastDeath", "INTEGER");
62 tableStucture.Add("LastLoginDate", "INTEGER");
63 tableStucture.Add("XPMultiplier", "INTEGER\tNOT NULL\tDEFAULT 100");
64
65 string queryText = "CREATE TABLE IF NOT EXISTS \"RPG_User\" (";
66 foreach (var structItem in tableStucture)
67 {
68 queryText += "`" + structItem.Key + "` " + structItem.Value + ", ";
69 }
70 queryText += "PRIMARY KEY(UserID))";
71 var sql = new Sql(queryText);
72 _sqLite.Query(sql, _sqLiteConnection, list =>
73 {
74 //CheckTableIntegrity(tableStucture);
75 });
76 }
77
78 /* TODO: Will finish this one day!
79 public void CheckTableIntegrity(Dictionary<string, string> tableStucture)
80 {
81 var sql = new Sql("PRAGMA table_info(RPG_User)");
82 _sqLite.Query(sql, _sqLiteConnection, list =>
83 {
84 if (list.Count > 0) // Save to DB failed.
85 foreach (var listItem in list)
86 {
87 string currColumn = listItem["type"] +
88 (listItem["notnull"].ToString() == "1" ? "\tNOT NULL" : "") +
89 (listItem["dflt_value"].ToString() != string.Empty ? "\tDEFAULT " + listItem["dflt_value"].ToString() : "");
90
91 if (tableStucture[listItem["name"].ToString()] != currColumn)
92 alterTable(currColumn, tableStucture[listItem["name"].ToString()]);
93 }
94
95 });
96 }
97
98 public void alterTable(string currColumn, string fixedColumn)
99 {
100 Puts("Altering table from: [" + currColumn + "] to [" + fixedColumn + "]");
101 var sql = new Sql("BEGIN TRANSACTION;PRAGMA schema_version;");
102 _sqLite.Query(sql, _sqLiteConnection, list =>
103 {
104 if (list.Count > 0)
105 {
106 Puts("1");
107 int schemaVersion = Convert.ToInt32(list[0]["schema_version"]);
108 var sql2 = new Sql("PRAGMA writable_schema=ON;");
109 _sqLite.Query(sql2, _sqLiteConnection, list2 =>
110 {
111 Puts("2");
112 var sql3 = new Sql("SELECT * FROM sqlite_master WHERE type='table' and name='RPG_User';");
113 _sqLite.Query(sql3, _sqLiteConnection, list3 =>
114 {
115 if (list3.Count > 0)
116 {
117 Puts("3");
118 string modifiedSql = list3[0]["sql"].ToString();
119 Puts("Before: " + modifiedSql);
120 modifiedSql = modifiedSql.Replace(currColumn, fixedColumn);
121 Puts("After: " + modifiedSql);
122 var sql4 =
123 new Sql("UPDATE sqlite_master SET sql=@0 WHERE type='table' and name='RPG_User';",
124 modifiedSql);
125 _sqLite.Query(sql4, _sqLiteConnection, list4 =>
126 {
127 if (list4.Count > 0)
128 {
129 Puts("4");
130 var sql5 =
131 new Sql("PRAGMA schema_version=" + ++schemaVersion +
132 ";PRAGMA writable_schema=OFF;END TRANSACTION");
133 _sqLite.Query(sql5, _sqLiteConnection, list5 =>
134 {
135 Puts("5");
136 var sql6 = new Sql("PRAGMA integrity_check");
137 _sqLite.Query(sql5, _sqLiteConnection, list6 =>
138 {
139 if (list6.Count > 0)
140 {
141 Puts("6");
142 Puts(list6[0]["integrity_check"].ToString());
143 }
144 });
145 });
146 }
147 });
148 }
149 });
150 });
151 }
152 });
153 }
154 */
155
156 public void setPointsAndLevel(ulong userID, string skill, long points, long level)
157 {
158 if (!playerList.ContainsKey(userID))
159 playerList.Add(userID, new Dictionary<string, long>());
160
161 setPlayerData(userID, skill + "Points", points == 0 ? getLevelPoints(level) : points);
162 setPlayerData(userID, skill + "Level", level);
163 }
164
165 public void loadUser(BasePlayer player)
166 {
167 Dictionary<string, long> statsInit = new Dictionary<string, long>();
168 foreach (string skill in Skills.ALL)
169 {
170 statsInit.Add(skill + "Level", 1);
171 statsInit.Add(skill + "Points", 10);
172 }
173 long currTime = ToEpochTime(DateTime.UtcNow);
174 statsInit.Add("LastDeath", currTime);
175 statsInit.Add("LastLoginDate", currTime);
176 statsInit.Add("XPMultiplier", 100);
177 var sql = Sql.Builder.Append("SELECT * FROM RPG_User WHERE UserID = @0", player.userID);
178
179
180 if (usingMySQL())
181 {
182 _mySql.Query(sql, _mySqlConnection, list =>
183 {
184 initPlayer(player, statsInit, list);
185 });
186 }
187 else
188 {
189 _sqLite.Query(sql, _sqLiteConnection, list =>
190 {
191 initPlayer(player, statsInit, list);
192 });
193 }
194 }
195
196 void initPlayer(BasePlayer player, Dictionary<string, long> statsInit, List<Dictionary<string, object>> sqlData)
197 {
198
199 bool needToSave = true;
200 Dictionary<string, long> tempElement = new Dictionary<string, long>();
201 if (sqlData.Count > 0)
202 {
203 foreach (string key in statsInit.Keys)
204 {
205 if (sqlData[0][key] != DBNull.Value)
206 tempElement.Add(key, Convert.ToInt64(sqlData[0][key]));
207 }
208
209 needToSave = false;
210 }
211
212 foreach (var tempItem in tempElement)
213 statsInit[tempItem.Key] = tempItem.Value;
214
215 initPlayerData(player, statsInit);
216 if (needToSave)
217 saveUser(player);
218
219 RenderUI(player);
220 }
221
222 void setPlayerData(ulong userID, string key, long value)
223 {
224 if (playerList[userID].ContainsKey(key))
225 playerList[userID][key] = value;
226 else
227 playerList[userID].Add(key, value);
228 }
229
230 void initPlayerData(BasePlayer player, Dictionary<string, long> playerData)
231 {
232 foreach (string skill in Skills.ALL)
233 setPointsAndLevel(player.userID, skill, playerData[skill + "Points"], playerData[skill + "Level"]);
234
235 foreach (var dataItem in playerData)
236 {
237 if (dataItem.Key.EndsWith("Level") || dataItem.Key.EndsWith("Points"))
238 continue;
239 setPlayerData(player.userID, dataItem.Key, dataItem.Value);
240 }
241 }
242
243 void OnPlayerLootEnd(PlayerLoot inventory)
244 {
245 BasePlayer player = inventory.GetComponent<BasePlayer>();
246 if (player != null && inPlayerList(player.userID))
247 {
248 RenderUI(player);
249 }
250 }
251
252 void OnPlayerDisconnected(BasePlayer player)
253 {
254 if (guioff.Contains(player.userID))
255 guioff.Remove(player.userID);
256 else
257 CuiHelper.DestroyUi(player, "StatsUI");
258
259 if (inPlayerList(player.userID))
260 {
261 saveUser(player);
262
263 if (playerList.ContainsKey(player.userID))
264 playerList.Remove(player.userID);
265 }
266 }
267
268 void OnPlayerInit(BasePlayer player)
269 {
270 long multiplier = 100;
271 string[] playerPermissions = permission.GetUserPermissions(player.UserIDString);
272 if (playerPermissions.Any(x => x.ToLower().StartsWith("zlvlboost")))
273 {
274 string permission = playerPermissions.First(x => x.ToLower().StartsWith("zlvlboost"));
275
276 if (!long.TryParse(permission.ToLower().Replace("zlvlboost", ""), out multiplier))
277 multiplier = 100;
278 }
279 editMultiplierForPlayer(multiplier, player.userID);
280
281
282 loadUser(player);
283 }
284
285 public void SaveUsers()
286 {
287 foreach (var user in BasePlayer.activePlayerList)
288 {
289 saveUser(user);
290 }
291 }
292
293 static string EncodeNonAsciiCharacters(string value)
294 {
295 StringBuilder sb = new StringBuilder();
296 foreach (char c in value)
297 {
298 if (c > 127)
299 {
300 // This character is too big for ASCII
301 string encodedValue = "";
302 sb.Append(encodedValue);
303 }
304 else
305 {
306 sb.Append(c);
307 }
308 }
309 return sb.ToString();
310 }
311
312 public void saveUser(BasePlayer player)
313 {
314 if (!playerList.ContainsKey(player.userID))
315 {
316 Puts("Trying to save player, who haven't been loaded yet? Player name: " + player.displayName);
317 return;
318 }
319
320 Dictionary<string, long> statsInit = getConnectedPlayerDetailsData(player.userID);
321
322 string name = EncodeNonAsciiCharacters(player.displayName);
323 string sqlText =
324 "REPLACE INTO RPG_User (UserID, Name, WCLevel, WCPoints, MLevel, MPoints, SLevel, SPoints, CLevel, CPoints, LastDeath, LastLoginDate, XPMultiplier) " +
325 "VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12)";
326 var sql = Sql.Builder.Append(sqlText,
327 player.userID, //0
328 name, //1
329 statsInit["WCLevel"], //2
330 statsInit["WCPoints"], //3
331 statsInit["MLevel"], //4
332 statsInit["MPoints"], //5
333 statsInit["SLevel"], //6
334 statsInit["SPoints"], //7
335 statsInit["CLevel"], //8
336 statsInit["CPoints"], //9
337 statsInit["LastDeath"], //10
338 statsInit["LastLoginDate"], //11
339 statsInit["XPMultiplier"]); //12
340 if (usingMySQL())
341 {
342 _mySql.Insert(sql, _mySqlConnection, list =>
343 {
344 if (list == 0) // Save to DB failed.
345 Puts("OMG WE DIDN'T SAVED IT!: " + sql.SQL);
346 });
347 }
348 else
349 {
350 _sqLite.Insert(sql, _sqLiteConnection, list =>
351 {
352 if (list == 0) // Save to DB failed.
353 Puts("OMG WE DIDN'T SAVED IT!: " + sql.SQL);
354 });
355 }
356 }
357
358 public Dictionary<string, long> getConnectedPlayerDetailsData(ulong userID)
359 {
360 if (!playerList.ContainsKey(userID))
361 return null;
362
363 Dictionary<string, long> statsInit = new Dictionary<string, long>();
364 foreach (string skill in Skills.ALL)
365 {
366 statsInit.Add(skill + "Level", getLevel(userID, skill));
367 statsInit.Add(skill + "Points", getPoints(userID, skill));
368 }
369 statsInit.Add("LastDeath", playerList[userID]["LastDeath"]);
370 statsInit.Add("LastLoginDate", playerList[userID]["LastLoginDate"]);
371 statsInit.Add("XPMultiplier", playerList[userID]["XPMultiplier"]);
372 return statsInit;
373 }
374
375 #endregion
376 public static class Skills
377 {
378 public static string CRAFTING = "C";
379 public static string WOODCUTTING = "WC";
380 public static string SKINNING = "S";
381 public static string MINING = "M";
382 public static string[] ALL = new[] { WOODCUTTING, MINING, SKINNING, CRAFTING };
383 }
384 System.Collections.Generic.List<ulong> guioff = new System.Collections.Generic.List<ulong>();
385
386 private Dictionary<string, string> colors = new Dictionary<string, string>()
387 {
388 {Skills.WOODCUTTING, "#FFDDAA"},
389 {Skills.MINING, "#DDDDDD"},
390 {Skills.SKINNING, "#FFDDDD"},
391 {Skills.CRAFTING, "#CCFF99"}
392 };
393
394 class CraftData
395 {
396 public Dictionary<string, CraftInfo> CraftList = new Dictionary<string, CraftInfo>();
397 public CraftData() { }
398 }
399
400 CraftData _craftData;
401
402 #region Stats
403 [HookMethod("SendHelpText")]
404 private void SendHelpText(BasePlayer player)
405 {
406 string text = "/stats - Displays your stats.\n/statsui - Displays/hides stats UI.\n/statinfo [statsname] - Displays information about stat.\n" +
407 "/topskills - Display max levels reached so far.";
408 player.ChatMessage(text);
409 }
410
411 [ChatCommand("topskills")]
412 private void StatsTopCommand(BasePlayer player, string command, string[] args)
413 {
414 PrintToChat(player, "Max stats on server so far:");
415 foreach (string skill in Skills.ALL)
416 {
417 if (!IsSkillDisabled(skill))
418 printMaxSkillDetails(player, skill);
419 }
420 }
421
422 private void printMaxSkillDetails(BasePlayer player, string skill)
423 {
424 var sql =
425 Sql.Builder.Append("SELECT * FROM RPG_User ORDER BY " + skill + "Level DESC," + skill +
426 "Points DESC LIMIT 1;");
427 if (usingMySQL())
428 {
429 _mySql.Query(sql, _mySqlConnection, list =>
430 {
431 if (list.Count > 0)
432 printMaxSkillDetails(player, skill, list);
433 });
434 }
435 else
436 {
437 _sqLite.Query(sql, _sqLiteConnection, list =>
438 {
439 if (list.Count > 0)
440 printMaxSkillDetails(player, skill, list);
441 });
442 }
443 }
444
445 void printMaxSkillDetails(BasePlayer player, string skill, List<Dictionary<string, object>> sqlData)
446 {
447 PrintToChat(player,
448 "<color=" + colors[skill] + ">" + messages[skill + "Skill"] + ": " +
449 sqlData[0][skill + "Level"] + " (XP: " + sqlData[0][skill + "Points"] + ")</color> <- " +
450 sqlData[0]["Name"]);
451 }
452
453 [ConsoleCommand("zinfo")]
454 private void InfoCommand(ConsoleSystem.Arg arg)
455 {
456 if (arg.connection != null)
457 return;
458
459 if (arg.Args == null || arg.Args.Length != 1)
460 {
461 Puts("Syntax is: zinfo name/steamid");
462 Puts("Example: zinfo visagalis");
463 return;
464 }
465 string playerName = arg.Args[0];
466 BasePlayer player = rust.FindPlayer(playerName);
467
468 if (player != null)
469 {
470
471 var playerData = getConnectedPlayerDetailsData(player.userID);
472 if (playerData == null)
473 Puts("PlayerData IS NULL!!!");
474
475 Puts("Stats for player: [" + player.displayName + "]");
476 Puts("Woodcutting: " + playerData["WCLevel"] + " XP: [" + playerData["WCPoints"] + "]");
477 Puts("Mining: " + playerData["MLevel"] + " XP: [" + playerData["MPoints"] + "]");
478 Puts("Skinning: " + playerData["SLevel"] + " XP: [" + playerData["SPoints"] + "]");
479 Puts("Crafting: " + playerData["CLevel"] + " XP: [" + playerData["CPoints"] + "]");
480 Puts("XP Multiplier: " + playerData["XPMultiplier"] + " % ");
481 }
482 }
483
484 [ConsoleCommand("zlvl")]
485 private void ZlvlCommand(ConsoleSystem.Arg arg)
486 {
487 if (arg.connection != null)
488 return;
489
490 if (arg.Args == null || arg.Args.Length != 3)
491 {
492 Puts("Syntax is: zlvl name/steamid skill [OPERATOR]NUMBER");
493 Puts("Example: zlvl Visagalis WC /2 -- visagalis gets his WC level divided by 2.");
494 Puts("Example: zlvl * * +3 -- Everyone currently playing in the server gets +3 for all skills.");
495 Puts("Example: zlvl ** * /2 -- Everyone (including offline players) gets their level divided by 2.");
496 Puts("Instead of names you can use wildcard(*): * - affects online players, ** - affects all players");
497 Puts("Possible operators: *(XP Modified %), +(Adds level), -(Removes level), /(Divides level)");
498 return;
499 }
500 string playerName = arg.Args[0];
501 BasePlayer p = rust.FindPlayer(playerName);
502
503 if (p != null || (playerName == "*" || playerName == "**"))
504 {
505 int playerMode = 0; // Exact player
506 if (playerName == "*")
507 playerMode = 1; // Online players
508 else if (playerName == "**")
509 playerMode = 2; // All players
510 string skill = arg.Args[1].ToUpper();
511 if (skill == Skills.WOODCUTTING || skill == Skills.MINING || skill == Skills.SKINNING ||
512 skill == Skills.CRAFTING || skill == "*")
513 {
514 bool allSkills = skill == "*";
515 int mode = 0; // 0 = SET, 1 = ADD, 2 = SUBTRACT, 3 = multiplier, 4 = divide
516 int value;
517 bool correct = false;
518 if (arg.Args[2][0] == '+')
519 {
520 mode = 1;
521 correct = int.TryParse(arg.Args[2].Replace("+", ""), out value);
522 }
523 else if (arg.Args[2][0] == '-')
524 {
525 mode = 2;
526 correct = int.TryParse(arg.Args[2].Replace("-", ""), out value);
527 }
528 else if (arg.Args[2][0] == '*')
529 {
530 mode = 3;
531 correct = int.TryParse(arg.Args[2].Replace("*", ""), out value);
532 }
533 else if (arg.Args[2][0] == '/')
534 {
535 mode = 4;
536 correct = int.TryParse(arg.Args[2].Replace("/", ""), out value);
537 }
538 else
539 {
540 correct = int.TryParse(arg.Args[2], out value);
541 }
542 if (correct)
543 {
544 if (mode == 3) // Change XP Multiplier.
545 {
546 if (!allSkills)
547 {
548 Puts("XPMultiplier is changeable for all skills! Use * instead of " + skill + ".");
549 return;
550 }
551 if (playerMode == 1)
552 {
553 foreach (var currPlayer in BasePlayer.activePlayerList)
554 editMultiplierForPlayer(value, currPlayer.userID);
555 }
556 else if (playerMode == 2)
557 editMultiplierForPlayer(value);
558 else if (p != null)
559 editMultiplierForPlayer(value, p.userID);
560
561 Puts("XP rates has changed to " + value + "% of normal XP for " + (playerMode == 1 ? "ALL ONLINE PLAYERS" : (playerMode == 2 ? "ALL PLAYERS" : p.displayName)));
562 return;
563 }
564
565 if (playerMode == 1)
566 {
567 foreach (var currPlayer in BasePlayer.activePlayerList)
568 adminModifyPlayerStats(skill, value, mode, currPlayer);
569 }
570 else if (playerMode == 2)
571 adminModifyPlayerStats(skill, value, mode);
572 else
573 adminModifyPlayerStats(skill, value, mode, p);
574
575 }
576 }
577 else
578 {
579 Puts("Incorrect skill. Possible skills are: WC, M, S, C, *(All skills).");
580 }
581 }
582 else
583 {
584 Puts("Player with name: " + arg.Args[0] + " haven't been found online.");
585 }
586
587 }
588
589 private void adminModifyPlayerStats(string skill, long level, int mode, BasePlayer p = null)
590 {
591 if (skill == "*")
592 {
593 foreach (var currSkill in Skills.ALL)
594 {
595 if (p == null)
596 {
597 string action = "";
598 switch (mode)
599 {
600 case 1:
601 action = "+";
602 break;
603 case 2:
604 action = "-";
605 break;
606 case 4:
607 action = "/";
608 break;
609 default:
610 break;
611 }
612 if (string.IsNullOrEmpty(action))
613 {
614 Puts("You can't just SET everyone's level, use + - or / operator.");
615 return;
616 }
617 string sqlText = "UPDATE RPG_User SET ";
618 string skillLevel = currSkill + "Level";
619 sqlText += skillLevel + "=" + skillLevel + action + level + ", ";
620 sqlText += currSkill + "Points=0;" +
621 (levelCaps[currSkill].ToString() != "0" ? ("UPDATE RPG_User SET " + skillLevel + "=" + levelCaps[currSkill] + " WHERE " + skillLevel + ">" + levelCaps[currSkill] + ";") : "") +
622 "UPDATE RPG_User SET " + skillLevel + "=1 WHERE " + skillLevel + "< 1;";
623 var sql = Sql.Builder.Append(sqlText);
624 if (usingMySQL())
625 _mySql.ExecuteNonQuery(sql, _mySqlConnection);
626 else
627 _sqLite.ExecuteNonQuery(sql, _sqLiteConnection);
628
629 foreach (var onlinePlayer in BasePlayer.activePlayerList)
630 loadUser(onlinePlayer);
631 }
632 else
633 {
634 long modifiedLevel = getLevel(p.userID, currSkill);
635 if (mode == 0) // SET
636 modifiedLevel = level;
637 else if (mode == 1) // ADD
638 modifiedLevel += level;
639 else if (mode == 2) // SUBTRACT
640 modifiedLevel -= level;
641 else if (mode == 4) // DIVIDE
642 modifiedLevel /= level;
643 if (modifiedLevel < 1)
644 modifiedLevel = 1;
645 if (modifiedLevel > Convert.ToInt32(levelCaps[currSkill]) &&
646 Convert.ToInt32(levelCaps[currSkill]) != 0)
647 {
648 modifiedLevel = Convert.ToInt32(levelCaps[currSkill]);
649 // Don't allow to ADD levels above limits.
650 Puts(
651 "Warning! You tried to level up player above levelCaps, use SET if you want to have player level over levelCaps.");
652 }
653
654 setPointsAndLevel(p.userID, currSkill, getLevelPoints(modifiedLevel), modifiedLevel);
655 RenderUI(p);
656 Puts(messages[currSkill + "Skill"] + " Level for [" + p.displayName + "] has been set to: [" +
657 modifiedLevel +
658 "]");
659 SendReply(p,
660 "Admin has set your " + messages[currSkill + "Skill"] + " level to: [" + modifiedLevel +
661 "] ");
662 }
663 }
664 }
665 else
666 {
667 if (p == null)
668 {
669 string action = "";
670 switch (mode)
671 {
672 case 1:
673 action = "+";
674 break;
675 case 2:
676 action = "-";
677 break;
678 case 4:
679 action = "/";
680 break;
681 default:
682 break;
683 }
684 if (string.IsNullOrEmpty(action))
685 {
686 Puts("You can't just SET everyone's level, use + - or / operator.");
687 return;
688 }
689 string sqlText = "UPDATE RPG_User SET ";
690 string skillLevel = skill + "Level";
691 sqlText += skillLevel + "=" + skillLevel + action + level + ", ";
692 sqlText += skill + "Points=0;" +
693 (levelCaps[skill].ToString() != "0" ? ("UPDATE RPG_User SET " + skillLevel + "=" + levelCaps[skill] + " WHERE " + skillLevel + ">" + levelCaps[skill] + ";") : "") +
694 "UPDATE RPG_User SET " + skillLevel + "=1 WHERE " + skillLevel + "< 1;";
695 var sql = Sql.Builder.Append(sqlText);
696 if (usingMySQL())
697 _mySql.ExecuteNonQuery(sql, _mySqlConnection);
698 else
699 _sqLite.ExecuteNonQuery(sql, _sqLiteConnection);
700
701 foreach (var onlinePlayer in BasePlayer.activePlayerList)
702 loadUser(onlinePlayer);
703 return;
704 }
705 long modifiedLevel = getLevel(p.userID, skill);
706 if (mode == 0) // SET
707 modifiedLevel = level;
708 else if (mode == 1) // ADD
709 modifiedLevel += level;
710 else if (mode == 2) // SUBTRACT
711 modifiedLevel -= level;
712 else if (mode == 4) // DIVIDE
713 modifiedLevel /= level;
714 if (modifiedLevel < 1)
715 modifiedLevel = 1;
716 if (modifiedLevel > Convert.ToInt32(levelCaps[skill]) && Convert.ToInt32(levelCaps[skill]) != 0)
717 {
718 modifiedLevel = Convert.ToInt32(levelCaps[skill]); // Don't allow to ADD levels above limits.
719 Puts("Warning! You tried to level up player above levelCaps, use SET if you want to have player level over levelCaps.");
720 }
721
722 setPointsAndLevel(p.userID, skill, getLevelPoints(modifiedLevel), modifiedLevel);
723 RenderUI(p);
724 Puts(messages[skill + "Skill"] + " Level for [" + p.displayName + "] has been set to: [" + modifiedLevel + "]");
725 SendReply(p, "Admin has set your " + messages[skill + "Skill"] + " level to: [" + modifiedLevel + "] ");
726 }
727
728 }
729
730 private void editMultiplierForPlayer(long multiplier, ulong userID = ulong.MinValue)
731 {
732 string sqlText = "UPDATE RPG_User SET XPMultiplier = @0";
733 if (userID != ulong.MinValue)
734 sqlText += " WHERE UserID = @1";
735
736 if (userID == ulong.MinValue)
737 {
738 foreach (var playerDetails in playerList)
739 {
740 playerDetails.Value["XPMultiplier"] = multiplier;
741 }
742 }
743 else
744 {
745 if (playerList.ContainsKey(userID))
746 playerList[userID]["XPMultiplier"] = multiplier;
747 }
748 var sql = Sql.Builder.Append(sqlText, multiplier, userID);
749
750 if (usingMySQL())
751 _mySql.ExecuteNonQuery(sql, _mySqlConnection);
752 else
753 _sqLite.ExecuteNonQuery(sql, _sqLiteConnection);
754 }
755
756 [ChatCommand("stats")]
757 private void StatsCommand(BasePlayer player, string command, string[] args)
758 {
759 string text = "<color=blue>ZLevels Remastered [" + Version + "] by Visagalis</color>\n" + "<color=yellow>" +
760 (string)messages["StatsHeadline"] + "</color>\n";
761
762
763 foreach (string skill in Skills.ALL)
764 {
765 text += getStatPrint(player, skill);
766 }
767
768 rust.SendChatMessage(player, text, null, "76561198002115162");
769
770 Dictionary<string, long> details = playerList[player.userID];
771 if (details.ContainsKey("LastDeath"))
772 {
773 DateTime currentTime = DateTime.UtcNow;
774 DateTime lastDeath = ToDateTimeFromEpoch(details["LastDeath"]);
775 TimeSpan timeAlive = currentTime - lastDeath;
776 PrintToChat(player, "Time alive: " + ReadableTimeSpan(timeAlive));
777 if (details["XPMultiplier"].ToString() != "100")
778 PrintToChat(player, "XP rates for you are " + details["XPMultiplier"] + "%");
779 }
780
781 RenderUI(player);
782 }
783
784 public static string ReadableTimeSpan(TimeSpan span)
785 {
786 string formatted = string.Format("{0}{1}{2}{3}{4}",
787 (span.Days / 7) > 0 ? string.Format("{0:0} weeks, ", span.Days / 7) : string.Empty,
788 span.Days % 7 > 0 ? string.Format("{0:0} days, ", span.Days % 7) : string.Empty,
789 span.Hours > 0 ? string.Format("{0:0} hours, ", span.Hours) : string.Empty,
790 span.Minutes > 0 ? string.Format("{0:0} minutes, ", span.Minutes) : string.Empty,
791 span.Seconds > 0 ? string.Format("{0:0} seconds, ", span.Seconds) : string.Empty);
792
793 if (formatted.EndsWith(", ")) formatted = formatted.Substring(0, formatted.Length - 2);
794
795 return formatted;
796 }
797
798 [ChatCommand("statinfo")]
799 private void StatInfoCommand(BasePlayer player, string command, string[] args)
800 {
801 string messagesText = "";
802 long xpMultiplier = 100;
803 if (inPlayerList(player.userID))
804 xpMultiplier = playerList[player.userID]["XPMultiplier"];
805
806 if (args.Length == 1)
807 {
808 string statname = args[0].ToLower();
809 switch (statname)
810 {
811 case "mining":
812 messagesText = "<color=" + colors[Skills.MINING] + ">Mining</color>" + (IsSkillDisabled(Skills.MINING) ? "(DISABLED)" : "") + "\n";
813 messagesText += "XP per hit: <color=" + colors[Skills.MINING] + ">" + ((int)pointsPerHit[Skills.MINING] * (xpMultiplier / 100f)) + "</color>\n";
814 messagesText += "Bonus materials per level: <color=" + colors[Skills.MINING] + ">" + ((getGathMult(2, Skills.MINING) - 1) * 100).ToString("0.##") + "%</color>\n";
815 break;
816 case "woodcutting":
817 messagesText = "<color=" + colors[Skills.WOODCUTTING] + ">Woodcutting</color>" + (IsSkillDisabled(Skills.WOODCUTTING) ? "(DISABLED)" : "") + "\n";
818 messagesText += "XP per hit: <color=" + colors[Skills.WOODCUTTING] + ">" + ((int)pointsPerHit[Skills.WOODCUTTING] * (xpMultiplier / 100f)) + "</color>\n";
819 messagesText += "Bonus materials per level: <color=" + colors[Skills.WOODCUTTING] + ">" + ((getGathMult(2, Skills.WOODCUTTING) - 1) * 100).ToString("0.##") + "%</color>\n";
820 break;
821 case "skinning":
822 messagesText = "<color=" + colors[Skills.SKINNING] + '>' + "Skinning" + "</color>" + (IsSkillDisabled(Skills.SKINNING) ? "(DISABLED)" : "") + "\n";
823 messagesText += "XP per hit: <color=" + colors[Skills.SKINNING] + ">" + ((int)pointsPerHit[Skills.SKINNING] * (xpMultiplier / 100f)) + "</color>\n";
824 messagesText += "Bonus materials per level: <color=" + colors[Skills.SKINNING] + ">" + ((getGathMult(2, Skills.SKINNING) - 1) * 100).ToString("0.##") + "%</color>\n";
825 break;
826 case "crafting":
827 messagesText = "<color=" + colors[Skills.CRAFTING] + '>' + "Crafting" + "</color>" + (IsSkillDisabled(Skills.CRAFTING) ? "(DISABLED)" : "") + "\n";
828 messagesText += "XP gain: <color=" + colors[Skills.SKINNING] + ">You get " + craftingDetails["XPPerTimeSpent"] + " XP per " + craftingDetails["TimeSpent"] + "s spent crafting.</color>\n";
829 messagesText += "Bonus: <color=" + colors[Skills.SKINNING] + ">Crafting time is decreased by " + craftingDetails["PercentFasterPerLevel"] + "% per every level.</color>\n";
830 break;
831 default:
832 messagesText = "No such stat: " + args[0];
833 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>";
834 break;
835 }
836 }
837 else
838 {
839 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>";
840 }
841 PrintToChat(player, messagesText);
842 }
843
844 [ChatCommand("statsui")]
845 private void StatsUICommand(BasePlayer player, string command, string[] args)
846 {
847 if (guioff.Contains(player.userID))
848 {
849 guioff.Remove(player.userID);
850 RenderUI(player);
851 }
852 else
853 {
854 guioff.Add(player.userID);
855 CuiHelper.DestroyUi(player, "StatsUI"); ;
856 }
857 }
858
859 void OnPlayerSleepEnded(BasePlayer player)
860 {
861 if (inPlayerList(player.userID))
862 RenderUI(player);
863 }
864
865 void OnLootEntity(BasePlayer looter, BaseEntity target)
866 {
867 if (!guioff.Contains(looter.userID))
868 {
869 CuiHelper.DestroyUi(looter, "StatsUI");
870 }
871 }
872
873 void OnLootPlayer(BasePlayer looter, BasePlayer beingLooter)
874 {
875 OnLootEntity(looter, null);
876 }
877
878 void OnLootItem(BasePlayer looter, Item lootedItem)
879 {
880 OnLootEntity(looter, null);
881 }
882
883 private 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)
884 {
885 float value = 1 / (float)maxRows;
886 float positionMin = 1 - (value * rowNumber);
887 float positionMax = 2 - (1 - (value * (1 - rowNumber)));
888 var xpBarPlaceholder1 = new CuiElement
889 {
890 Name = CuiHelper.GetGuid(),
891 Parent = mainPanel,
892 Components =
893 {
894 new CuiImageComponent { Color = "0.4 0.4 0.4 0.2" },
895 new CuiRectTransformComponent{ AnchorMin = "0 " + positionMin.ToString("0.####"), AnchorMax = $"1 "+ positionMax.ToString("0.####") }
896 }
897 };
898 elements.Add(xpBarPlaceholder1);
899
900 var innerXPBar1 = new CuiElement
901 {
902 Name = CuiHelper.GetGuid(),
903 Parent = xpBarPlaceholder1.Name,
904 Components =
905 {
906 new CuiImageComponent { Color = "0 0 0 0.8"},
907 new CuiRectTransformComponent{ AnchorMin = xpBarAnchorMin, AnchorMax = xpBarAnchorMax }
908 }
909 };
910 elements.Add(innerXPBar1);
911
912 var innerXPBarProgress1 = new CuiElement
913 {
914 Name = CuiHelper.GetGuid(),
915 Parent = innerXPBar1.Name,
916 Components =
917 {
918 new CuiImageComponent() { Color = progressColor},
919 new CuiRectTransformComponent{ AnchorMin = "0 0", AnchorMax = (percent / 100.0).ToString() + " 1" }
920 }
921 };
922 elements.Add(innerXPBarProgress1);
923
924 var innerXPBarText1 = new CuiElement
925 {
926 Name = CuiHelper.GetGuid(),
927 Parent = innerXPBar1.Name,
928 Components =
929 {
930 new CuiTextComponent { Color = "1 1 1 1", Text = skillName, FontSize = fontSize, Align = TextAnchor.MiddleCenter},
931 new CuiRectTransformComponent{ AnchorMin = "0 0", AnchorMax = "1 1" }
932 }
933 };
934 elements.Add(innerXPBarText1);
935
936 var xpText1 = new CuiElement
937 {
938 Name = CuiHelper.GetGuid(),
939 Parent = xpBarPlaceholder1.Name,
940 Components =
941 {
942 new CuiTextComponent { Text = percent + "%", FontSize = fontSize, Align = TextAnchor.MiddleRight, Color = "0.749019608 0.760784314 0.780392157 1" },
943 new CuiRectTransformComponent{ AnchorMin = "0 0", AnchorMax = $"0.98 1" }
944 }
945 };
946 elements.Add(xpText1);
947
948 var lvText1 = new CuiElement
949 {
950 Name = CuiHelper.GetGuid(),
951 Parent = xpBarPlaceholder1.Name,
952 Components =
953 {
954 new CuiTextComponent { Text = "Lv." + level, FontSize = fontSize, Align = TextAnchor.MiddleLeft, Color = "0.749019608 0.760784314 0.780392157 1" },
955 new CuiRectTransformComponent{ AnchorMin = "0.01 0", AnchorMax = $"0.5 1" }
956 }
957 };
958 elements.Add(lvText1);
959 }
960
961 private void RenderUI(BasePlayer player)
962 {
963 if (guioff.Contains(player.userID))
964 return;
965 Dictionary<string, string> skillColors = new Dictionary<string, string>();
966 skillColors.Add("WC", "0.8 0.4 0 1");
967 skillColors.Add("M", "0.1 0.5 0.8 0.6");
968 skillColors.Add("S", "0.8 0.1 0 0.6");
969 skillColors.Add("C", "0.2 0.72 0.5 0.8");
970 int enabledSkillCount = 0;
971 foreach (string skill in Skills.ALL)
972 {
973 if (!IsSkillDisabled(skill))
974 enabledSkillCount++;
975 }
976
977
978 CuiHelper.DestroyUi(player, "StatsUI");
979
980 var elements = new CuiElementContainer();
981 var mainName = elements.Add(new CuiPanel
982 {
983 Image =
984 {
985 Color = "0.1 0.1 0.1 0.0"
986 },
987 RectTransform =
988 {
989 AnchorMin = "0.69 0.0140",
990 AnchorMax = "0.83 0.1335"
991 }
992 }, "Hud", "StatsUI");
993
994 int fontSize = 12;
995 string xpBarAnchorMin = "0.16 0.1";
996 string xpBarAnchorMax = "0.88 0.9";
997 int currentSKillIndex = 1;
998
999
1000 foreach (string skill in Skills.ALL)
1001 {
1002 if (!IsSkillDisabled(skill))
1003 {
1004 FillElements(ref elements, mainName, currentSKillIndex, enabledSkillCount, getLevel(player.userID, skill), getExperiencePercentInt(player,
1005 skill), messages[skill + "Skill"].ToString(), skillColors[skill], fontSize, xpBarAnchorMin, xpBarAnchorMax);
1006 currentSKillIndex++;
1007 }
1008 }
1009
1010 CuiHelper.AddUi(player, elements);
1011 }
1012
1013 private string getStatPrint(BasePlayer player, string skill)
1014 {
1015 if (IsSkillDisabled(skill))
1016 return "";
1017
1018 bool skillMaxed = (int)levelCaps[skill] != 0 && getLevel(player.userID, skill) == (int)levelCaps[skill];
1019 string bonusText = "";
1020 if (skill == Skills.CRAFTING)
1021 bonusText =
1022 (getLevel(player.userID, skill) * (int)craftingDetails["PercentFasterPerLevel"]).ToString("0.##");
1023 else
1024 bonusText = ((getGathMult(getLevel(player.userID, skill), skill) - 1) * 100).ToString("0.##");
1025
1026 return string.Format("<color=" + colors[skill] + '>' + (string)messages["StatsText"] + "</color>\n",
1027 (string)messages[skill + "Skill"],
1028 getLevel(player.userID, skill) + (Convert.ToInt32(levelCaps[skill]) > 0 ? ("/" + levelCaps[skill]) : ""),
1029 getPoints(player.userID, skill),
1030 skillMaxed ? "∞" : getLevelPoints(getLevel(player.userID, skill) + 1).ToString(),
1031 bonusText,
1032 getExperiencePercent(player, skill),
1033 getPenaltyPercent(player, skill) + "%");
1034
1035 }
1036
1037 #endregion
1038
1039 #region Main/Other
1040
1041 /// <summary>
1042 /// Converts the given date value to epoch time.
1043 /// </summary>
1044 long ToEpochTime(DateTime dateTime)
1045 {
1046 var date = dateTime.ToUniversalTime();
1047 var ticks = date.Ticks - new DateTime(1970, 1, 1, 0, 0, 0, 0).Ticks;
1048 var ts = ticks / TimeSpan.TicksPerSecond;
1049 return ts;
1050 }
1051
1052 /// <summary>
1053 /// Converts the given epoch time to a <see cref="DateTime"/> with <see cref="DateTimeKind.Utc"/> kind.
1054 /// </summary>
1055 DateTime ToDateTimeFromEpoch(long intDate)
1056 {
1057 var timeInTicks = intDate * TimeSpan.TicksPerSecond;
1058 return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddTicks(timeInTicks);
1059 }
1060
1061 private void Loaded()
1062 {
1063 StartConnection();
1064
1065 if ((_craftData = Interface.GetMod().DataFileSystem.ReadObject<CraftData>("ZLevelsCraftDetails")) == null)
1066 {
1067 _craftData = new CraftData();
1068 }
1069
1070 foreach (BasePlayer player in BasePlayer.activePlayerList)
1071 {
1072 loadUser(player);
1073 }
1074 }
1075
1076 void OnEntityDeath(BaseCombatEntity entity, HitInfo hitInfo)
1077 {
1078 if (entity is BasePlayer)
1079 {
1080 BasePlayer player = (BasePlayer)entity;
1081 var isPlaying = EventManager?.Call("isPlaying", player);
1082 if (!inPlayerList(player.userID) || (isPlaying is bool && (bool)isPlaying)) return;
1083
1084 string penaltyText = "<color=#FF0000>You have lost XP for dying:";
1085 bool penaltyExist = false;
1086 foreach (string skill in Skills.ALL)
1087 {
1088 if (!IsSkillDisabled(skill))
1089 {
1090 int penalty = GetPenalty(player, skill);
1091 if (penalty > 0)
1092 {
1093 penaltyText += "\n* -" + penalty + " " + messages[skill + "Skill"] + " XP.";
1094 removePoints(player.userID, skill, penalty);
1095 penaltyExist = true;
1096 }
1097 }
1098 }
1099 penaltyText += "</color>";
1100
1101 if (penaltyExist)
1102 PrintToChat(player, penaltyText);
1103 SetPlayerLastDeathDate(player.userID);
1104 RenderUI(player);
1105 }
1106
1107 }
1108
1109 void SetPlayerLastDeathDate(ulong userID)
1110 {
1111 setPlayerData(userID, "LastDeath", ToEpochTime(DateTime.UtcNow));
1112 }
1113
1114
1115 void OnDispenserGather(ResourceDispenser dispenser, BaseEntity entity, Item item)
1116 {
1117 BasePlayer player = entity as BasePlayer;
1118 if (player == null) return;
1119
1120 if (!IsSkillDisabled(Skills.WOODCUTTING))
1121 if ((int)dispenser.gatherType == 0) levelHandler(player, item, Skills.WOODCUTTING);
1122 if (!IsSkillDisabled(Skills.MINING))
1123 if ((int)dispenser.gatherType == 1) levelHandler(player, item, Skills.MINING);
1124 if (!IsSkillDisabled(Skills.SKINNING))
1125 if ((int)dispenser.gatherType == 2) levelHandler(player, item, Skills.SKINNING);
1126 }
1127
1128 void OnCollectiblePickup(Item item, BasePlayer player)
1129 {
1130 string skillName = string.Empty;
1131 switch (item.info.shortname.ToLower())
1132 {
1133 case "wood":
1134 skillName = Skills.WOODCUTTING;
1135 break;
1136 case "cloth":
1137 case "mushroom":
1138 case "corn":
1139 case "pumpkin":
1140 case "seed.hemp":
1141 case "seed.pumpkin":
1142 case "seed.corn":
1143 skillName = Skills.SKINNING;
1144 break;
1145 case "metal.ore":
1146 case "sulfur.ore":
1147 case "stones":
1148 skillName = Skills.MINING;
1149 break;
1150 }
1151
1152 if (!string.IsNullOrEmpty(skillName))
1153 levelHandler(player, item, skillName);
1154 else
1155 Puts("Developer missed this item, which can be picked up: [" + item.info.shortname + "]. Let him know on Oxide forums!");
1156 }
1157
1158 void levelHandler(BasePlayer player, Item item, string skill)
1159 {
1160 string xpPercentBefore = getExperiencePercent(player, skill);
1161 long Level = getLevel(player.userID, skill);
1162 long Points = getPoints(player.userID, skill);
1163 item.amount = (int)(item.amount * getGathMult(Level, skill));
1164
1165 int pointsToGet = (int)pointsPerHit[skill];
1166 long xpMultiplier = Convert.ToInt64(playerList[player.userID]["XPMultiplier"]);
1167 Points += Convert.ToInt64(pointsToGet * (xpMultiplier / 100f));
1168 getPointsLevel(Points, skill);
1169 try
1170 {
1171 if (Points >= getLevelPoints(Level + 1))
1172 {
1173 bool maxLevel = (int)levelCaps[skill] > 0 && Level + 1 > (int)levelCaps[skill];
1174 if (!maxLevel)
1175 {
1176 Level = getPointsLevel(Points, skill);
1177 PrintToChat(player, string.Format("<color=" + colors[skill] + '>' + (string)messages["LevelUpText"] + "</color>",
1178 (string)messages[skill + "Skill"],
1179 Level,
1180 Points,
1181 getLevelPoints(Level + 1),
1182 ((getGathMult(Level, skill) - 1) * 100).ToString("0.##")
1183 )
1184 );
1185 }
1186 }
1187 }
1188 catch (Exception ex)
1189 {
1190 Puts(ex.Message);
1191 }
1192
1193 setPointsAndLevel(player.userID, skill, Points, Level);
1194
1195 string xpPercentAfter = getExperiencePercent(player, skill);
1196 if (!xpPercentAfter.Equals(xpPercentBefore))
1197 RenderUI(player);
1198 }
1199 #endregion
1200
1201 #region Utility
1202 private long getLevelPoints(long level)
1203 {
1204 return 110 * level * level - 100 * level;
1205 }
1206
1207 private long getPointsLevel(long points, string skill)
1208 {
1209 int a = 110;
1210 int b = 100;
1211 long c = -points;
1212 double x1 = (-b - Math.Sqrt(b * b - 4 * a * c)) / (2 * a);
1213 if ((int)levelCaps[skill] == 0 || (int)-x1 <= (int)levelCaps[skill])
1214 return (int)-x1;
1215 else
1216 return (int)levelCaps[skill];
1217 }
1218
1219 double getGathMult(long skillLevel, string skill)
1220 {
1221 return 1 + Convert.ToDouble(resourceMultipliers[skill]) * 0.1 * (skillLevel - 1);
1222 }
1223
1224 private bool inPlayerList(UInt64 userID)
1225 {
1226 return playerList.ContainsKey(userID);
1227
1228 }
1229 #endregion
1230
1231 #region Saving
1232 void OnServerSave()
1233 {
1234 SaveUsers();
1235 }
1236
1237 private void Unload()
1238 {
1239 SaveUsers();
1240 if (_mySqlConnection != null)
1241 _mySqlConnection = null;
1242 else
1243 {
1244 _sqLiteConnection = null;
1245 }
1246
1247 foreach (var player in BasePlayer.activePlayerList) // destroy UI when unloading.
1248 {
1249 if (guioff.Contains(player.userID))
1250 return;
1251 CuiHelper.DestroyUi(player, "StatsUI");
1252 }
1253 }
1254 #endregion
1255
1256 #region Config
1257 private Dictionary<string, object> resourceMultipliers;
1258 private Dictionary<string, object> levelCaps;
1259 private Dictionary<string, object> pointsPerHit;
1260 private Dictionary<string, object> craftingDetails;
1261 private Dictionary<string, object> percentLostOnDeath;
1262 private Dictionary<string, object> messages;
1263 private Dictionary<string, object> dbConnection;
1264
1265 protected override void LoadDefaultConfig() { }
1266
1267 void Init()
1268 {
1269 resourceMultipliers = checkCfg<Dictionary<string, object>>("ResourcePerLevelMultiplier", new Dictionary<string, object>{
1270 {Skills.WOODCUTTING, 2.0d},
1271 {Skills.MINING, 2.0d},
1272 {Skills.SKINNING, 2.0d}
1273 });
1274 levelCaps = checkCfg<Dictionary<string, object>>("LevelCaps", new Dictionary<string, object>{
1275 {Skills.WOODCUTTING, 200},
1276 {Skills.MINING, 200},
1277 {Skills.SKINNING, 200},
1278 {Skills.CRAFTING, -1}
1279 });
1280 pointsPerHit = checkCfg<Dictionary<string, object>>("PointsPerHit", new Dictionary<string, object>{
1281 {Skills.WOODCUTTING, 30},
1282 {Skills.MINING, 30},
1283 {Skills.SKINNING, 30}
1284 });
1285 craftingDetails = checkCfg<Dictionary<string, object>>("CraftingDetails", new Dictionary<string, object>{
1286 { "TimeSpent", 1},
1287 { "XPPerTimeSpent", 3},
1288 { "PercentFasterPerLevel", 5 }
1289 });
1290 percentLostOnDeath = checkCfg<Dictionary<string, object>>("PercentLostOnDeath", new Dictionary<string, object>{
1291 {Skills.WOODCUTTING, 50},
1292 {Skills.MINING, 50},
1293 {Skills.SKINNING, 50},
1294 {Skills.CRAFTING, 50}
1295 });
1296
1297 dbConnection = checkCfg<Dictionary<string, object>>("dbConnection", new Dictionary<string, object>{
1298 {"UseMySQL", false },
1299 {"Host", "127.0.0.1"},
1300 {"Port", 3306 },
1301 {"Username", "user" },
1302 {"Password", "password" },
1303 {"Database", "db" },
1304 {"GameProtocol", Protocol.network }
1305 });
1306
1307 messages = checkCfg<Dictionary<string, object>>("Messages", new Dictionary<string, object>{
1308 {"StatsHeadline", "Level stats (/statinfo [statname] - To get more information about skill)"},
1309 {"StatsText", "-{0}"+
1310 "\nLevel: {1} (+{4}% bonus) \nXP: {2}/{3} [{5}].\n<color=red>-{6} XP loose on death.</color>"},
1311 {"LevelUpText", "{0} Level up"+
1312 "\nLevel: {1} (+{4}% bonus) \nXP: {2}/{3}"},
1313 {"WCSkill", "Woodcutting"},
1314 {"MSkill", "Mining"},
1315 {"SSkill", "Skinning"},
1316 {"CSkill", "Crafting" }
1317 });
1318 SaveConfig();
1319 }
1320
1321 private T checkCfg<T>(string conf, T def)
1322 {
1323 if (Config[conf] != null)
1324 {
1325 return (T)Config[conf];
1326 }
1327 else
1328 {
1329 Config[conf] = def;
1330 return def;
1331 }
1332 }
1333 #endregion
1334
1335 #region Adds&Removse
1336
1337 private void removePoints(UInt64 userID, string skill, long points)
1338 {
1339 if (playerList[userID][skill + "Points"] - 10 > points)
1340 playerList[userID][skill + "Points"] -= points;
1341 else
1342 playerList[userID][skill + "Points"] = 10;
1343
1344 setLevel(userID, skill, getPointsLevel(playerList[userID][skill + "Points"], skill));
1345 }
1346
1347 #endregion
1348 #region Gets&Sets
1349
1350 private long getLevel(UInt64 userID, string skill)
1351 {
1352 if (!playerList.ContainsKey(userID))
1353 Puts("Trying to get [" + messages[skill + "Skill"].ToString() + "]. For player who's SteamID: [" + userID + "]. He is not on a user list yet?");
1354 if (!playerList[userID].ContainsKey(skill + "Level"))
1355 playerList[userID].Add(skill + "Level", 1);
1356
1357 return playerList[userID][skill + "Level"];
1358 }
1359
1360 private long getPoints(UInt64 userID, string skill)
1361 {
1362 if (!playerList[userID].ContainsKey(skill + "Points"))
1363 playerList[userID].Add(skill + "Points", 11);
1364
1365 return playerList[userID][skill + "Points"];
1366 }
1367
1368 private void setLevel(UInt64 userID, string skill, long level)
1369 {
1370 setPlayerData(userID, skill + "Level", level);
1371 }
1372
1373 #endregion
1374
1375
1376 #region New stuff
1377
1378 bool IsSkillDisabled(string skill)
1379 {
1380 return levelCaps[skill].ToString() == "-1";
1381 }
1382
1383 bool usingMySQL()
1384 {
1385 return Convert.ToBoolean(dbConnection["UseMySQL"]);
1386 }
1387
1388 int GetPenalty(BasePlayer player, string skill)
1389 {
1390 int penalty = 0;
1391 int penaltyPercent = getPenaltyPercent(player, skill);
1392 penalty = Convert.ToInt32(getPercentAmount(playerList[player.userID][skill + "Level"], penaltyPercent));
1393 return penalty;
1394 }
1395
1396 int getPenaltyPercent(BasePlayer player, string skill)
1397 {
1398 int penaltyPercent = 0;
1399 Dictionary<string, long> details = playerList[player.userID];
1400
1401 if (details.ContainsKey("LastDeath"))
1402 {
1403 DateTime currentTime = DateTime.UtcNow;
1404 DateTime lastDeath = ToDateTimeFromEpoch(details["LastDeath"]);
1405 TimeSpan timeAlive = currentTime - lastDeath;
1406 if (timeAlive.TotalMinutes > 10)
1407 {
1408 penaltyPercent = ((int)percentLostOnDeath[skill] - ((int)timeAlive.TotalHours * (int)percentLostOnDeath[skill] / 10));
1409 if (penaltyPercent < 0)
1410 penaltyPercent = 0;
1411 }
1412 }
1413 return penaltyPercent;
1414 }
1415
1416 [HookMethod("OnItemCraftFinished")]
1417 object OnItemCraftFinished(ItemCraftTask task, Item item)
1418 {
1419 if (IsSkillDisabled(Skills.CRAFTING))
1420 return null;
1421
1422 BasePlayer crafter = task.owner;
1423 string xpPercentBefore = getExperiencePercent(crafter, Skills.CRAFTING);
1424 if (task.blueprint == null)
1425 {
1426 Puts("There is problem obtaining task.blueprint on 'OnItemCraftFinished' hook! This is usually caused by some incompatable plugins.");
1427 return null;
1428 }
1429 int experienceGain = Convert.ToInt32(Math.Floor((task.blueprint.time + 0.99f) / (int)craftingDetails["TimeSpent"]));//(int)task.blueprint.time / 10;
1430 if (experienceGain == 0)
1431 return null;
1432
1433 long Level = 0;
1434 long Points = 0;
1435 try
1436 {
1437 Level = getLevel(crafter.userID, Skills.CRAFTING);
1438 Points = getPoints(crafter.userID, Skills.CRAFTING);
1439 }
1440 catch (Exception ex)
1441 {
1442 Puts("Problem when getting level/points for player. Error:" + ex.StackTrace);
1443 }
1444 Points += experienceGain * (int)craftingDetails["XPPerTimeSpent"];
1445 if (Points >= getLevelPoints(Level + 1))
1446 {
1447 bool maxLevel = (int)levelCaps[Skills.CRAFTING] > 0 && Level + 1 > (int)levelCaps[Skills.CRAFTING];
1448 if (!maxLevel)
1449 {
1450 Level = getPointsLevel(Points, Skills.CRAFTING);
1451 PrintToChat(crafter, string.Format("<color=" + colors[Skills.CRAFTING] + '>' + (string)messages["LevelUpText"] + "</color>",
1452 (string)messages["CSkill"],
1453 Level,
1454 Points,
1455 getLevelPoints(Level + 1),
1456 (getLevel(crafter.userID, Skills.CRAFTING) * Convert.ToDouble(craftingDetails["PercentFasterPerLevel"])).ToString()
1457 )
1458 );
1459 }
1460 }
1461 try
1462 {
1463 if (item.info.shortname != "lantern_a" && item.info.shortname != "lantern_b")
1464 {
1465 setPointsAndLevel(crafter.userID, Skills.CRAFTING, Points, Level);
1466 }
1467 }
1468 catch (Exception ex)
1469 {
1470 Puts("Problem when setting crafting xp/level for player. Error information:" + ex.StackTrace);
1471 }
1472
1473 try
1474 {
1475 string xpPercentAfter = getExperiencePercent(crafter, Skills.CRAFTING);
1476 if (!xpPercentAfter.Equals(xpPercentBefore))
1477 RenderUI(crafter);
1478 }
1479 catch (Exception ex)
1480 {
1481 Puts("Problem when checking if we should RenderUI: " + ex.StackTrace);
1482 }
1483
1484
1485 if (task.amount > 0) return null;
1486 if (task.blueprint != null && task.blueprint.name.Contains("(Clone)"))
1487 {
1488 var behaviours = task.blueprint.GetComponents<MonoBehaviour>();
1489 foreach (var behaviour in behaviours)
1490 {
1491 if (behaviour.name.Contains("(Clone)")) UnityEngine.Object.Destroy(behaviour);
1492 }
1493 task.blueprint = null;
1494 }
1495 return null;
1496 }
1497
1498 private object OnItemCraft(ItemCraftTask task, BasePlayer crafter)
1499 {
1500 if (IsSkillDisabled(Skills.CRAFTING))
1501 return null;
1502
1503 long Level = getLevel(crafter.userID, Skills.CRAFTING);
1504
1505 var craftingTime = task.blueprint.time;
1506 var amountToReduce = task.blueprint.time * ((float)(Level * (int)craftingDetails["PercentFasterPerLevel"]) / 100);
1507 craftingTime -= amountToReduce;
1508 if (craftingTime < 0)
1509 craftingTime = 0;
1510 if (craftingTime == 0)
1511 {
1512 try
1513 {
1514 foreach (var entry in _craftData.CraftList)
1515 {
1516 var itemname = task.blueprint.targetItem.shortname.ToString();
1517 if (entry.Value.shortName == itemname && entry.Value.Enabled)
1518 {
1519 int amount = task.amount;
1520 if (amount >= entry.Value.MinBulkCraft && amount <= entry.Value.MaxBulkCraft)
1521 {
1522 ItemDefinition item = GetItem(itemname);
1523 int final_amount = task.blueprint.amountToCreate * amount;
1524 var newItem = ItemManager.CreateByItemID(item.itemid, (int)final_amount);
1525 crafter.inventory.GiveItem(newItem);
1526
1527 string returnstring = "You have crafted <color=#66FF66>" + amount.ToString() + "</color> <color=#66FFFF>" + item.displayName.english.ToString() + "</color>\n[Batch Amount: <color=#66FF66>" + final_amount.ToString() + "</color>]";
1528 PrintToChat(crafter, returnstring);
1529 return false;
1530 }
1531 }
1532 }
1533 }
1534 catch
1535 {
1536 // Generate only when someone reached instant craft level.
1537 GenerateItems(false);
1538 }
1539 }
1540
1541 if (!task.blueprint.name.Contains("(Clone)"))
1542 task.blueprint = UnityEngine.Object.Instantiate(task.blueprint);
1543 task.blueprint.time = craftingTime;
1544 return null;
1545 }
1546
1547 int MaxB = 999;
1548 int MinB = 10;
1549 int Cooldown = 0;
1550
1551 /*
1552 Thanks Norn for this piece of code!
1553 It was borrowed from his plugin:
1554 http://oxidemod.org/threads/magic-craft.11784/
1555 */
1556 void GenerateItems(bool reset = false)
1557 {
1558 if (!reset)
1559 {
1560 string config_protocol = dbConnection["GameProtocol"].ToString();
1561 if (config_protocol != Protocol.network.ToString())
1562 {
1563 dbConnection["GameProtocol"] = Protocol.network.ToString();
1564 Puts("Updating item list from protocol " + config_protocol.ToString() + " to protocol " + dbConnection["GameProtocol"] + ".");
1565 GenerateItems(true);
1566 SaveConfig();
1567 return;
1568 }
1569 }
1570
1571 if (reset)
1572 {
1573 Interface.GetMod().DataFileSystem.WriteObject("ZLevelsCraftDetails.old", _craftData);
1574 _craftData.CraftList.Clear();
1575 Puts("Generating new item list...");
1576 }
1577 mcITEMS = ItemManager.itemList.ToDictionary(i => i.shortname);
1578 int loaded = 0, enabled = 0;
1579 foreach (var definition in mcITEMS)
1580 {
1581 if (definition.Value.shortname.Length >= 1)
1582 {
1583 CraftInfo p = null;
1584 if (_craftData.CraftList.TryGetValue(definition.Value.shortname, out p))
1585 {
1586 if (p.Enabled) { enabled++; }
1587 loaded++;
1588 }
1589 else
1590 {
1591 CraftInfo z = new CraftInfo();
1592 z.shortName = definition.Value.shortname.ToString();
1593 z.MaxBulkCraft = MaxB;
1594 z.MinBulkCraft = MinB;
1595 z.Enabled = true;
1596 _craftData.CraftList.Add(definition.Value.shortname.ToString(), z);
1597 loaded++;
1598 }
1599 }
1600 }
1601 int inactive = loaded - enabled;
1602 Puts("Loaded " + loaded.ToString() + " items. (Enabled: " + enabled.ToString() + " | Inactive: " + inactive.ToString() + ").");
1603 Interface.GetMod().DataFileSystem.WriteObject("ZLevelsCraftDetails", _craftData);
1604 }
1605
1606 class CraftInfo
1607 {
1608 public int MaxBulkCraft;
1609 public int MinBulkCraft;
1610 public string shortName;
1611 public bool Enabled;
1612 public CraftInfo()
1613 {
1614 }
1615 }
1616
1617 private Dictionary<string, ItemDefinition> mcITEMS;
1618
1619 private ItemDefinition GetItem(string shortname)
1620 {
1621 if (string.IsNullOrEmpty(shortname) || mcITEMS == null) return null;
1622 ItemDefinition item;
1623 if (mcITEMS.TryGetValue(shortname, out item)) return item;
1624 return null;
1625 }
1626
1627 long getPointsNeededForNextLevel(long level)
1628 {
1629 long startingPoints = getLevelPoints(level);
1630 long nextLevelPoints = getLevelPoints(level + 1);
1631 long pointsNeeded = nextLevelPoints - startingPoints;
1632 return pointsNeeded;
1633 }
1634
1635 long getPercentAmount(long level, int percent)
1636 {
1637 long points = getPointsNeededForNextLevel(level);
1638 long percentPoints = (points * percent) / 100;
1639 return percentPoints;
1640 }
1641
1642 int getExperiencePercentInt(BasePlayer player, string skill)
1643 {
1644 long Level = getLevel(player.userID, skill);
1645 long startingPoints = getLevelPoints(Level);
1646 long nextLevelPoints = getLevelPoints(Level + 1) - startingPoints;
1647 long Points = getPoints(player.userID, skill) - startingPoints;
1648 int experienceProc = Convert.ToInt32((Points / (double)nextLevelPoints) * 100);
1649 if (experienceProc >= 100)
1650 experienceProc = 99;
1651 else if (experienceProc == 0)
1652 experienceProc = 1;
1653 return experienceProc;
1654 }
1655
1656 string getExperiencePercent(BasePlayer player, string skill)
1657 {
1658 string percent = getExperiencePercentInt(player, skill).ToString() + "%";
1659 return percent;
1660 }
1661
1662
1663 #endregion
1664 }
1665}