From 29ed11d6c2b63cad9228ab255674cf473cee6002 Mon Sep 17 00:00:00 2001 From: Patrick Ulrich Date: Wed, 29 Jan 2025 10:43:52 -0500 Subject: [PATCH] Initial Deployment --- DepositBox.cs | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++ README.md | 88 +++++++++++++++ 3 files changed, 398 insertions(+) create mode 100644 DepositBox.cs create mode 100644 LICENSE create mode 100644 README.md diff --git a/DepositBox.cs b/DepositBox.cs new file mode 100644 index 0000000..0b94659 --- /dev/null +++ b/DepositBox.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Globalization; // For CultureInfo +using System.Linq; +using Newtonsoft.Json; +using Oxide.Core; +using Oxide.Core.Libraries.Covalence; + +namespace Oxide.Plugins +{ + [Info("DepositBox", "rustysats", "0.2.0")] + [Description("Drop box that registers drops for admin while removing items from the game.")] + internal class DepositBox : RustPlugin + { + private static DepositBox instance; + // Configuration variables + private int DepositItemID; + private ulong DepositBoxSkinID; + // Permission constants + private const string permPlace = "depositbox.place"; + private const string permCheck = "depositbox.check"; + private const string permAdminCheck = "depositbox.admincheck"; + private DepositLog depositLog; + private Dictionary depositTrack = new Dictionary(); // Track deposits + + #region Oxide Hooks + void Init() + { + instance = this; + LoadConfiguration(); + LoadDepositLog(); + permission.RegisterPermission(permPlace, this); + permission.RegisterPermission(permCheck, this); + permission.RegisterPermission(permAdminCheck, this); + } + + protected override void LoadDefaultConfig() + { + PrintWarning("Creating a new configuration file."); + Config["DepositItemID"] = -1779183908; // Default Item ID for deposits (paper) + Config["DepositBoxSkinID"] = 1641384897; // Default skin ID for the deposit box + SaveConfig(); + } + + void OnServerInitialized(bool initial) + { + foreach (var entity in BaseNetworkable.serverEntities) + { + if (entity is StorageContainer storageContainer) + { + OnEntitySpawned(storageContainer); + } + } + } + + void Unload() + { + foreach (var entity in BaseNetworkable.serverEntities) + { + if (entity is StorageContainer storageContainer && storageContainer.TryGetComponent(out DepositBoxRestriction restriction)) + { + restriction.Destroy(); + } + } + instance = null; + } + + void OnEntitySpawned(StorageContainer container) + { + if (container == null || container.skinID != DepositBoxSkinID) return; // Early return for non-matching containers + if (!container.TryGetComponent(out DepositBoxRestriction mono)) + { + mono = container.gameObject.AddComponent(); + mono.container = container.inventory; // Assign inventory upon component addition + mono.InitDepositBox(); + } + } + #endregion + + #region Commands + [ChatCommand("depositbox")] + private void GiveDepositBox(BasePlayer player, string command, string[] args) + { + if (!permission.UserHasPermission(player.UserIDString, permPlace)) + { + player.ChatMessage(lang.GetMessage("NoPermission", this, player.UserIDString)); + return; + } + player.inventory.containerMain.GiveItem(ItemManager.CreateByItemID(833533164, 1, DepositBoxSkinID)); + player.ChatMessage(lang.GetMessage("BoxGiven", this, player.UserIDString)); + } + + [ChatCommand("checkdeposits")] + private void CheckDepositsCommand(BasePlayer player, string command, string[] args) + { + if (!permission.UserHasPermission(player.UserIDString, permCheck)) + { + player.ChatMessage(lang.GetMessage("NoCheckPermission", this, player.UserIDString)); + return; + } + + if (depositLog == null || depositLog.Deposits.Count == 0) + { + player.ChatMessage(lang.GetMessage("NoDepositData", this, player.UserIDString)); + return; + } + + // Group the deposits by SteamID and calculate the total amount deposited for each player + var depositSummary = depositLog.Deposits + .GroupBy(entry => entry.SteamId) + .Select(group => new + { + SteamId = group.Key, + TotalAmount = group.Sum(entry => entry.AmountDeposited) + }) + .ToList(); + + // Calculate the total amount deposited by all players + int totalDeposited = depositSummary.Sum(summary => summary.TotalAmount); + + // Find the current player's total deposits + var playerSummary = depositSummary.FirstOrDefault(summary => summary.SteamId == player.UserIDString); + + if (playerSummary != null) + { + // Calculate the percentage of total deposits for the current player + double percentageOfTotal = ((double)playerSummary.TotalAmount / totalDeposited) * 100; + player.ChatMessage(lang.GetMessage("PlayerDepositSummary", this, player.UserIDString) + .Replace("{amount}", playerSummary.TotalAmount.ToString(CultureInfo.InvariantCulture)) + .Replace("{percentage}", percentageOfTotal.ToString("F2", CultureInfo.InvariantCulture))); + } + else + { + player.ChatMessage(lang.GetMessage("NoPlayerDeposits", this, player.UserIDString)); + } + + // Admin view if player has both permissions + if (permission.UserHasPermission(player.UserIDString, permAdminCheck)) + { + player.ChatMessage(lang.GetMessage("DepositTotals", this, player.UserIDString)); + foreach (var summary in depositSummary) + { + double percentage = ((double)summary.TotalAmount / totalDeposited) * 100; + player.ChatMessage(lang.GetMessage("DepositEntrySummary", this, player.UserIDString) + .Replace("{steamid}", summary.SteamId) + .Replace("{amount}", summary.TotalAmount.ToString(CultureInfo.InvariantCulture)) + .Replace("{percentage}", percentage.ToString("F2", CultureInfo.InvariantCulture))); + } + } + } + #endregion + + #region DepositBoxRestriction Class + public class DepositBoxRestriction : FacepunchBehaviour + { + public ItemContainer container; + public void InitDepositBox() + { + container.canAcceptItem += CanAcceptItem; + container.onItemAddedRemoved += OnItemAddedRemoved; + } + private bool CanAcceptItem(Item item, int targetPos) + { + // Only allow the configured deposit item to be deposited + if (item == null || item.info == null || item.info.itemid != DepositBox.instance.DepositItemID) + { + return false; + } + if (item.GetOwnerPlayer() is BasePlayer player) + { + DepositBox.instance.TrackDeposit(item, player); // Track the item with player reference + } + return true; + } + private void OnItemAddedRemoved(Item item, bool added) + { + // Early exit if item isn't added or isn't the correct deposit item + if (!added || item.info.itemid != DepositBox.instance.DepositItemID) return; + // Try to get the player who deposited the item + if (DepositBox.instance.depositTrack.TryGetValue(item, out BasePlayer player)) + { + DepositBox.instance.LogDeposit(player, item.amount); // Log the deposit first + DepositBox.instance.depositTrack.Remove(item); // Remove from tracking + // Now remove the deposited item from the box, after logging is complete + item.Remove(); + } + } + public void Destroy() + { + container.canAcceptItem -= CanAcceptItem; + container.onItemAddedRemoved -= OnItemAddedRemoved; + Destroy(this); + } + } + #endregion + + #region Logging + private class DepositLog + { + [JsonProperty("deposits")] + public List Deposits { get; set; } = new List(); + } + + private class DepositEntry + { + [JsonProperty("steamid")] + public string SteamId { get; set; } + [JsonProperty("timestamp")] + public string Timestamp { get; set; } + [JsonProperty("amount_deposited")] + public int AmountDeposited { get; set; } + } + + public void LogDeposit(BasePlayer player, int amount) + { + // Record this deposit + depositLog.Deposits.Add(new DepositEntry + { + SteamId = player.UserIDString, + Timestamp = DateTime.UtcNow.ToString("o"), + AmountDeposited = amount + }); + SaveDepositLog(); // Save the log after recording the deposit + + // Calculate the player's total deposits + int playerTotalDeposits = depositLog.Deposits + .Where(entry => entry.SteamId == player.UserIDString) + .Sum(entry => entry.AmountDeposited); + + // Calculate the total deposits of all players + int totalDepositedByAllPlayers = depositLog.Deposits.Sum(entry => entry.AmountDeposited); + + // Calculate the player's percentage of all deposits + double playerPercentageOfTotal = ((double)playerTotalDeposits / totalDepositedByAllPlayers) * 100; + + // Send the updated deposit message to the player + player.ChatMessage(lang.GetMessage("DepositRecorded", this, player.UserIDString) + .Replace("{amount}", amount.ToString(CultureInfo.InvariantCulture)) + .Replace("{total_amount}", playerTotalDeposits.ToString(CultureInfo.InvariantCulture)) + .Replace("{percentage}", playerPercentageOfTotal.ToString("F2", CultureInfo.InvariantCulture))); + } + + public void TrackDeposit(Item item, BasePlayer player) + { + if (item != null && player != null) + { + depositTrack[item] = player; // Track the item with its owner + } + } + + private void LoadDepositLog() + { + depositLog = Interface.Oxide.DataFileSystem.ReadObject("DepositBoxLog") ?? new DepositLog(); + } + + private void SaveDepositLog() + { + Interface.Oxide.DataFileSystem.WriteObject("DepositBoxLog", depositLog); + } + #endregion + + #region Configuration + private void LoadConfiguration() + { + DepositItemID = Convert.ToInt32(Config["DepositItemID"], CultureInfo.InvariantCulture); // Specified CultureInfo + DepositBoxSkinID = Convert.ToUInt64(Config["DepositBoxSkinID"], CultureInfo.InvariantCulture); // Specified CultureInfo + } + #endregion + + #region Localization + protected override void LoadDefaultMessages() + { + lang.RegisterMessages(new Dictionary + { + ["NoPermission"] = "You do not have permission to place this box.", + ["BoxGiven"] = "You have received a Deposit Box.", + ["DepositRecorded"] = "Your deposit of {amount} has been recorded. You have deposited a total of {total_amount} items, which is {percentage}% of all deposits.", + ["PlacedNoPerm"] = "You have placed a deposit box but lack permission to place it.", + ["NoCheckPermission"] = "You do not have permission to check deposits.", + ["NoDepositData"] = "No deposit data found.", + ["PlayerDepositSummary"] = "You have deposited a total of {amount} items, which is {percentage}% of all deposits.", + ["NoPlayerDeposits"] = "You have not made any deposits.", + ["DepositTotals"] = "Deposit Totals:", + ["DepositEntrySummary"] = "SteamID: {steamid}, Total Deposited: {amount}, {percentage}% of total." + }, this); + } + #endregion + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9ea602 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Patrick Ulrich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..38c1e09 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +## Overview + +The **DepositBox** plugin allows players to deposit specific items (e.g., paper) into a dropbox, logging the deposits and removing the items from the game. This can be used to create events or competitions, such as a system where players compete to turn in the most items (similar to the Twitch Rust event where players compete for dog tags). + +### Features +- Only designated items can be deposited. +- Logs all deposits for tracking. +- Configurable deposit box skin and item type. +- Prevents non-whitelisted items from being deposited. +- Can be used to run events or competitions where players turn in items for rewards. + +Once installed players with permission will be able to create a deposit box using /depositbox as a chat command. They can then place this box down allowing players to make deposits into it. All deposits are logged to the data folder. + +## Installation + +1. Upload the `DepositBox.cs` file to your Rust server under the `/oxide/plugins/` directory. +2. Restart or reload the server for the plugin to initialize: + ```bash + oxide.reload DepositBox + ``` +3. Ensure you grant users permission to use the deposit box: + ```bash + oxide.grant user depositbox.place + ``` + +## Configuration + +Upon the first run, a default configuration file will be generated at `/oxide/config/DepositBox.json`. The configuration contains the following settings: + +```json +{ + "DepositItemID": -1779183908, // The item ID for your deposit unit (default: -1779183908) + "DepositBoxSkinID": 1641384897 // The skin ID for the deposit box +} +``` + +You can edit these values directly in the configuration file if needed. + +- **DepositItemID**: The Rust item ID of the item that can be deposited (default: paper with ID `-1779183908`). +- **DepositBoxSkinID**: The Rust skin ID applied to the deposit box (default: `1641384897`). + +### Customizing the Configuration: + +1. Navigate to `/oxide/config/DepositBox.json`. +2. Modify the values as needed. +3. Save the file and reload the plugin: + ```bash + oxide.reload DepositBox + ``` + +### Important Note on Skin IDs: +- The DepositBoxSkinID is used to differentiate between regular storage containers and deposit boxes. Admins should ensure that they select a skin ID that is not actively available in the skin box to avoid confusion or accidental misuse by players. Using a skin that is easily accessible to players could result in unintended behavior where non-deposit boxes are treated as deposit boxes. + +## Permissions + +- `depositbox.place`: Grants a player permission to place a deposit box. + +To assign this permission, use the following command: +```bash +oxide.grant user depositbox.place +``` + +## Functionality + +### Hooks + +- **Init()**: Initializes the plugin, loads the configuration, and registers permissions. +- **OnServerInitialized()**: Scans the server for `StorageContainer` entities and applies the plugin's functionality to deposit boxes. +- **Unload()**: Cleans up and removes deposit box restrictions when the plugin is unloaded. +- **OnEntitySpawned()**: When a `StorageContainer` is spawned, the plugin checks if it’s a deposit box and ensures it follows the required rules. + +### Item Handling + +- The plugin restricts item deposits to a hardcoded whitelist (currently, only paper can be deposited). +- If an item is not on the whitelist, it will remain in the player's inventory, and only paper will be removed and logged. +- **Logging**: Every time a player deposits paper into the box, the action is logged for administrative tracking. + +### Custom Logging + +- All paper deposits are logged in the `oxide/data/DepositBoxLog.json` file, structured as follows: + ```json + { + "SteamID": "player_steam_id", + "amount_deposited": "amount_deposited", + "Timestamp": "2024-09-22T12:00:00" + } + ``` + This allows server administrators to keep track of deposits and monitor player activity.