using Newtonsoft.Json; using System; using System.Collections.Generic; using Oxide.Core.Plugins; /* * MajestatGather 1.0.0 * ==================== * Moduł dla MajestatCore 3.2.0 — zlicza wydobyte surowce. * * WYMAGA: MajestatCore 3.2.0+ * * Rejestruje zakładkę "GATHER" w UI MajestatCore przez API_RegisterTab. * Format przekazywany przez słownik — nie wymaga współdzielonych klas. * * Śledzone statystyki: * Gather.Wood, Gather.Stone, Gather.Metal, * Gather.Sulfur, Gather.Scrap, Gather.Food, Gather.Total * Złom: z ziemi (OnItemPickup) + z węzłów (OnDispenserGather) */ namespace Oxide.Plugins { [Info("MajestatTopGather", "Wo0t", "1.0.1")] [Description("Gather module for MajestatTopCore — tracks resources collected.")] class MajestatTopGather : RustPlugin { [PluginReference] Plugin MajestatTopCore; private bool _registered = false; // Poziom logowania pobierany z Core (0=all 1=system 2=alert 3=none) private int _logLevel = 1; private void MLog(int level, string msg) { int effective = MajestatTopCore != null ? (int)(MajestatTopCore.Call("API_GetLogLevel") ?? 1) : _logLevel; if (level < effective) return; // Podczas AutoReload wycisz komunikaty systemowe (1) — Core drukuje summary if (level == 1 && MajestatTopCore != null) { var ar = MajestatTopCore.Call("API_IsAutoReloading"); if (ar is bool b && b) return; } if (level >= 2) PrintWarning(msg); else Puts(msg); } // ── Update checker ──────────────────────────────────────────── private const string PluginVersion = "1.0.1"; private static readonly string PluginUpdateUrl = System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String("aHR0cDovL2Rvd25sb2FkLmtvbGRyaXguY29tL3J1c3QvcGx1Z2lucy9NYWplc3RhdFRvcEdhdGhlci92ZXJzaW9uLmpzb24=")); private HashSet _droppedByPlayer = new HashSet(); private Dictionary _scrapSnapshot = new Dictionary(); // ═════════════════════════════════════════════════════════════ // INIT // ═════════════════════════════════════════════════════════════ void OnServerInitialized() { if (MajestatTopCore == null) { PrintWarning("[MajestatTopGather] MajestatTopCore is not loaded! Module inactive."); return; } if (!CheckMinVersion(MajestatTopCore.Version, 3, 2, 0)) { PrintError($"[MajestatTopGather] Requires MajestatTopCore 3.2.0+. Installed: {MajestatTopCore.Version}. Module inactive."); return; } RegisterTab(); } private static bool CheckMinVersion(Oxide.Core.VersionNumber v, int major, int minor, int patch) => v.Major > major || (v.Major == major && (v.Minor > minor || (v.Minor == minor && v.Patch >= patch))); // Jeśli Core załaduje się po module void OnPluginLoaded(Plugin plugin) { if (plugin?.Name == "MajestatTopCore" && !_registered) { if (!CheckMinVersion(plugin.Version, 3, 2, 0)) { PrintError($"[MajestatTopGather] Requires MajestatTopCore 3.2.0+. Installed: {plugin.Version}."); return; } RegisterTab(); } } void OnPluginUnloaded(Plugin plugin) { if (plugin?.Name == "MajestatTopCore") _registered = false; } void Unload() { if (MajestatTopCore != null && _registered) MajestatTopCore.Call("API_UnregisterTab", "gather"); _droppedByPlayer.Clear(); _scrapSnapshot.Clear(); } // ═════════════════════════════════════════════════════════════ // REJESTRACJA ZAKŁADKI // Przekazywana jako Dictionary — Core parsuje // sam. Kolumny jako List>. // ═════════════════════════════════════════════════════════════ private void RegisterTab() { if (MajestatTopCore == null) return; var columns = new List> { new Dictionary { ["key"] = "Gather.Total", ["header"] = "TOTAL", ["lang_key"] = "Gather.Total" }, new Dictionary { ["key"] = "Gather.Wood", ["header"] = "DREWNO", ["lang_key"] = "Gather.Wood" }, new Dictionary { ["key"] = "Gather.Stone", ["header"] = "KAMIEŃ", ["lang_key"] = "Gather.Stone" }, new Dictionary { ["key"] = "Gather.Metal", ["header"] = "METAL", ["lang_key"] = "Gather.Metal" }, new Dictionary { ["key"] = "Gather.Sulfur", ["header"] = "SIARKA", ["lang_key"] = "Gather.Sulfur" }, new Dictionary { ["key"] = "Gather.Scrap", ["header"] = "ZŁOM", ["lang_key"] = "Gather.Scrap" }, new Dictionary { ["key"] = "Gather.Food", ["header"] = "JEDZENIE", ["lang_key"] = "Gather.Food" }, }; var tabDef = new Dictionary { ["id"] = "gather", ["label"] = "GATHER", ["sort"] = "Gather.Total", ["fame_sort"] = "Gather.Total", ["order"] = 10, ["columns"] = columns }; lang.RegisterMessages(new Dictionary { ["Gather.Total"] = "TOTAL", ["Gather.Wood"] = "WOOD", ["Gather.Stone"] = "STONE", ["Gather.Metal"] = "METAL", ["Gather.Sulfur"] = "SULFUR", ["Gather.Scrap"] = "SCRAP", ["Gather.Food"] = "FOOD", }, this); lang.RegisterMessages(new Dictionary { ["Gather.Total"] = "TOTAL", ["Gather.Wood"] = "DREWNO", ["Gather.Stone"] = "KAMIEŃ", ["Gather.Metal"] = "METAL", ["Gather.Sulfur"] = "SIARKA", ["Gather.Scrap"] = "ZŁOM", ["Gather.Food"] = "JEDZENIE", }, this, "pl"); bool ok = (bool)(MajestatTopCore.Call("API_RegisterTab", tabDef) ?? false); if (!ok) { PrintError("Tab registration failed."); return; } // Zarejestruj źródła XP — Core doda je do swojego cfg jeśli nie istnieją. // Admin zmienia wartości w pliku konfiguracyjnym MajestatCore, nie tutaj. MajestatTopCore.Call("API_RegisterXpSource", "Gather.Wood", 0.0); MajestatTopCore.Call("API_RegisterXpSource", "Gather.Stone", 0.0); MajestatTopCore.Call("API_RegisterXpSource", "Gather.Metal", 0.0); MajestatTopCore.Call("API_RegisterXpSource", "Gather.Sulfur", 0.0); MajestatTopCore.Call("API_RegisterXpSource", "Gather.Food", 0.0); MajestatTopCore.Call("API_RegisterXpSource", "Gather.Scrap", 0.0); _registered = true; MajestatTopCore.Call("API_RegisterModule", new Dictionary { ["name"] = "MajestatTopGather", ["version"] = PluginVersion, ["author"] = "Wo0t", ["description"] = "Gather stats module" }); // startup log handled by Core } // ═════════════════════════════════════════════════════════════ // HOOKI ZBIERANIA SUROWCÓW // ═════════════════════════════════════════════════════════════ void OnDispenserGather(ResourceDispenser dispenser, BasePlayer player, Item item) { if (player == null || item == null || MajestatTopCore == null) return; TrackItem(player.UserIDString, item.info.shortname, item.amount); } void OnDispenserBonus(ResourceDispenser dispenser, BasePlayer player, Item item) { if (player == null || item == null || MajestatTopCore == null) return; TrackItem(player.UserIDString, item.info.shortname, item.amount); } void OnCollectiblePickup(CollectibleEntity collectible, BasePlayer player) { if (player == null || collectible?.itemList == null || MajestatTopCore == null) return; foreach (var ia in collectible.itemList) { // Złom pomijamy — obsługuje OnItemPickup (żeby nie liczyć podwójnie) if (ia.itemDef.shortname == "scrap") continue; TrackItem(player.UserIDString, ia.itemDef.shortname, (int)ia.amount); } } void OnGrowableGathered(GrowableEntity plant, Item item, BasePlayer player) { if (player == null || item == null || MajestatTopCore == null) return; TrackItem(player.UserIDString, item.info.shortname, item.amount); } // ── Złom ze skrzynki — snapshot przy otwarciu/zamknięciu ──── void OnLootEntity(BasePlayer player, BaseEntity entity) { if (player == null || entity == null || MajestatTopCore == null) return; // Interesują nas tylko kontenery encji (skrzynki, beczki) — nie gracze if (entity is BasePlayer) return; _scrapSnapshot[player.userID] = CountScrapInInventory(player); } void OnLootEntityEnd(BasePlayer player, BaseCombatEntity entity) { if (player == null) return; if (!_scrapSnapshot.TryGetValue(player.userID, out int before)) return; _scrapSnapshot.Remove(player.userID); int after = CountScrapInInventory(player); int gained = after - before; if (gained > 0) { MajestatTopCore.Call("API_AddStat", player.UserIDString, "Gather.Scrap", (double)gained, true, true); MajestatTopCore.Call("API_AddStat", player.UserIDString, "Gather.Total", (double)gained, true, false); } } // ── Złom podnoszony z ziemi ────────────────────── void OnItemPickup(Item item, BasePlayer player) { if (player == null || item == null || MajestatTopCore == null) return; if (item.info.shortname != "scrap") return; // Złom ze skrzynki obsługuje OnLootEntityEnd (snapshot) if (_scrapSnapshot.ContainsKey(player.userID)) return; if (_droppedByPlayer.Contains(item.uid.Value)) { _droppedByPlayer.Remove(item.uid.Value); return; // własny wyrzucony złom — nie licz } MajestatTopCore.Call("API_AddStat", player.UserIDString, "Gather.Scrap", (double)item.amount, true, true); MajestatTopCore.Call("API_AddStat", player.UserIDString, "Gather.Total", (double)item.amount, true, false); } // ── Złom wyrzucony przez gracza — zapamiętaj żeby nie liczyć ─ void OnItemDropped(Item item, BaseEntity entity) { if (item == null || item.info.shortname != "scrap") return; _droppedByPlayer.Add(item.uid.Value); timer.Once(300f, () => _droppedByPlayer.Remove(item.uid.Value)); } // ═════════════════════════════════════════════════════════════ // LOGIKA ŚLEDZENIA // ═════════════════════════════════════════════════════════════ private void TrackItem(string steamId, string shortname, double amount) { string key = ResolveKey(shortname); if (key == null) return; MajestatTopCore.Call("API_AddStat", steamId, key, amount, true, true); MajestatTopCore.Call("API_AddStat", steamId, "Gather.Total", amount, true, false); } private int CountScrapInInventory(BasePlayer player) { if (player?.inventory == null) return 0; int total = 0; foreach (var container in new[] { player.inventory.containerMain, player.inventory.containerBelt, player.inventory.containerWear }) { if (container?.itemList == null) continue; foreach (var it in container.itemList) if (it?.info?.shortname == "scrap") total += it.amount; } return total; } private string ResolveKey(string shortname) { if (shortname == null) return null; if (shortname.Contains("wood")) return "Gather.Wood"; if (shortname == "stones" || shortname.Contains("stone") || shortname == "rock") return "Gather.Stone"; if (shortname == "metal.ore" || shortname.Contains("metal.ore")) return "Gather.Metal"; if (shortname == "sulfur.ore" || shortname.Contains("sulfur.ore")) return "Gather.Sulfur"; if (shortname == "scrap") return "Gather.Scrap"; if (shortname.Contains("corn") || shortname.Contains("berry") || shortname.Contains("mushroom") || shortname.Contains("pumpkin") || shortname.Contains("potato") || shortname.Contains("hemp")) return "Gather.Food"; return null; } // ═════════════════════════════════════════════════════════════ // UPDATE CHECKER — synchronizowany przez MajestatTopCore // Core wywołuje hook OnMajestatUpdateCheck na wszystkich modułach // ═════════════════════════════════════════════════════════════ void OnMajestatUpdateCheck() { webrequest.Enqueue(PluginUpdateUrl, null, OnUpdateResponse, this); } private void OnUpdateResponse(int code, string response) { string pluginName = "MajestatTopGather"; if (code == 0 || code >= 400 || string.IsNullOrEmpty(response)) { MLog(2, $"[Update] Update check failed - server unavailable (HTTP {code})."); MajestatTopCore?.Call("API_ReportUpdateResult", pluginName, null, null); return; } try { var data = Newtonsoft.Json.JsonConvert.DeserializeObject>(response); string latest = data != null && data.ContainsKey("version") ? data["version"].Trim() : null; if (latest != null && IsNewerVersion(latest, PluginVersion)) { string url = data.ContainsKey("url") ? data["url"] : PluginUpdateUrl; PrintWarning($"[Update] Newer version available ({latest}) - your version ({PluginVersion})"); MajestatTopCore?.Call("API_ReportUpdateResult", pluginName, latest, url); } else { MLog(1, $"[Update] Up to date ({PluginVersion}) - no updates."); MajestatTopCore?.Call("API_ReportUpdateResult", pluginName, null, null); } } catch (Exception ex) { MLog(2, $"[Update] Error: {ex.Message}"); MajestatTopCore?.Call("API_ReportUpdateResult", pluginName, null, null); } } private bool IsNewerVersion(string latest, string current) { try { string lc = latest != null && latest.Contains("-") ? latest.Substring(0, latest.IndexOf('-')) : latest; string cc = current != null && current.Contains("-") ? current.Substring(0, current.IndexOf('-')) : current; var l = System.Array.ConvertAll(lc.Split('.'), int.Parse); var c = System.Array.ConvertAll(cc.Split('.'), int.Parse); int len = System.Math.Max(l.Length, c.Length); for (int i = 0; i < len; i++) { int lv = i < l.Length ? l[i] : 0; int cv = i < c.Length ? c[i] : 0; if (lv > cv) return true; if (lv < cv) return false; } return false; } catch { return false; } } } }