using System; using System.Collections.Generic; using Oxide.Core.Plugins; /* * MajestatGather 1.0.0 * ==================== * Moduł dla MajestatCore 3.0.0 — zlicza wydobyte surowce. * * WYMAGA: MajestatCore 3.0.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.0")] [Description("Gather module for MajestatTopCore — tracks resources collected.")] class MajestatTopGather : RustPlugin { [PluginReference] Plugin MajestatTopCore; private bool _registered = false; // 0 = tylko przy starcie, np. 720 = co 12h private int _updateIntervalMinutes = 0; // ── Update checker ──────────────────────────────────────────── private const string PluginVersion = "1.0.0"; private const string PluginUpdateUrl = "http://download.koldrix.com/rust/plugins/MajestatTopGather/version.json"; private HashSet _droppedByPlayer = new HashSet(); // Snapshot złomu gdy gracz otwiera skrzynkę — do zliczania złomu ze skrzynki private Dictionary _scrapSnapshot = new Dictionary(); // ═════════════════════════════════════════════════════════════ // INIT // ═════════════════════════════════════════════════════════════ void OnServerInitialized() { if (MajestatTopCore == null) { PrintWarning("MajestatCore nie jest załadowany! Moduł nieaktywny."); return; } RegisterTab(); } // Jeśli Core załaduje się po module void OnPluginLoaded(Plugin plugin) { if (plugin?.Name == "MajestatTopCore" && !_registered) 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" }, new Dictionary { ["key"] = "Gather.Wood", ["header"] = "DREWNO" }, new Dictionary { ["key"] = "Gather.Stone", ["header"] = "KAMIEŃ" }, new Dictionary { ["key"] = "Gather.Metal", ["header"] = "METAL" }, new Dictionary { ["key"] = "Gather.Sulfur", ["header"] = "SIARKA" }, new Dictionary { ["key"] = "Gather.Scrap", ["header"] = "ZŁOM" }, new Dictionary { ["key"] = "Gather.Food", ["header"] = "JEDZENIE"}, }; var tabDef = new Dictionary { ["id"] = "gather", ["label"] = "GATHER", ["sort"] = "Gather.Total", ["fame_sort"] = "Gather.Total", ["order"] = 10, ["columns"] = columns }; bool ok = (bool)(MajestatTopCore.Call("API_RegisterTab", tabDef) ?? false); if (!ok) { PrintError("Rejestracja zakładki nie powiodła się."); 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); MajestatTopCore.Call("API_RegisterXpSource", "Gather.Total", 0.0); _registered = true; // Pobierz interwał z konfiguracji MajestatTopCore var intervalObj = MajestatTopCore.Call("API_GetUpdateInterval"); _updateIntervalMinutes = intervalObj is int iv ? iv : 0; timer.Once(5f, CheckForUpdate); if (_updateIntervalMinutes > 0) timer.Every(_updateIntervalMinutes * 60f, CheckForUpdate); Puts("Zakładka GATHER zarejestrowana. Komendy: /topgather /famegather"); } // ═════════════════════════════════════════════════════════════ // 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 (klawisz F) ────────────────────── 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; // Dodaj do konkretnej kategorii (z XP jeśli skonfigurowane w Core) MajestatTopCore.Call("API_AddStat", steamId, key, amount, true, true); // Dodaj do sumy (bez XP — żeby nie liczyć podwójnie) 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 — niezależny od MajestatTopCore // ═════════════════════════════════════════════════════════════ private void CheckForUpdate() { Puts($"[Update] Sprawdzam aktualizacje... (aktualna wersja: {PluginVersion})"); webrequest.Enqueue(PluginUpdateUrl, null, OnUpdateResponse, this); } private void OnUpdateResponse(int code, string response) { if (code == 0 || code >= 400 || string.IsNullOrEmpty(response)) { Puts($"[Update] Nie można sprawdzić aktualizacji — serwer niedostępny (HTTP {code})."); return; } try { var data = JsonConvert.DeserializeObject>(response); if (data == null || !data.ContainsKey("version")) { Puts("[Update] Nieprawidłowy plik version.json."); return; } string latest = data["version"].Trim(); if (IsNewerVersion(latest, PluginVersion)) { string url = data.ContainsKey("url") ? data["url"] : PluginUpdateUrl; PrintWarning($"[Update] Dostępna nowa wersja: {latest} (zainstalowana: {PluginVersion})"); PrintWarning($"[Update] Pobierz: {url}"); } else { Puts($"[Update] Wersja aktualna ({PluginVersion}) — brak aktualizacji."); } } catch (Exception ex) { Puts($"[Update] Błąd odczytu odpowiedzi: {ex.Message}"); } } private bool IsNewerVersion(string latest, string current) { try { var l = System.Array.ConvertAll(latest.Split('.'), int.Parse); var c = System.Array.ConvertAll(current.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; } } } }