C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using Random = System.Random;
namespace Oxide.Plugins
{
[Info("Land Claim System", "0killrest", "1.0.0")]
[Description("Players can buy land plots with bonuses and passive income")]
internal class LandClaimSystem : RustPlugin
{
#region Configuration
private static ConfigData config;
private class ConfigData
{
public bool Enabled { get; set; } = true;
public float PlotSize { get; set; } = 50f;
public int PlotCost { get; set; } = 5000;
public float DamageReductionPercent { get; set; } = 50f;
public float PassiveIncomeScrapPerHour { get; set; } = 100f;
public int MaxPlotsPerPlayer { get; set; } = 1;
public int MaxPlotsPerClan { get; set; } = 3;
public bool AllowPlotTrading { get; set; } = true;
public bool ShowPlotBoundaries { get; set; } = true;
public ConfigData() { }
}
protected override void LoadConfig()
{
base.LoadConfig();
config = Config.ReadObject<ConfigData>();
if (config == null)
{
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig()
{
config = new ConfigData();
PrintWarning("Default configuration created.Edit it in oxide/config/LandClaimSystem.json");
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Data Storage
private Dictionary<ulong, List<LandPlot>> playerPlots = new Dictionary<ulong, List<LandPlot>>();
private Dictionary<string, LandPlot> plotsByPosition = new Dictionary<string, LandPlot>();
private class LandPlot
{
public ulong OwnerId { get; set; }
public string OwnerName { get; set; }
public Vector3 Center { get; set; }
public float Radius { get; set; }
public DateTime PurchaseDate { get; set; }
public float LastIncomeTime { get; set; }
public List<ulong> SharedWith { get; set; } = new List<ulong>();
}
private void LoadData()
{
playerPlots = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, List<LandPlot>>>("LandClaim_Plots") ?? new Dictionary<ulong, List<LandPlot>>();
// Rebuild position index
plotsByPosition.Clear();
foreach (var plots in playerPlots.Values)
{
foreach (var plot in plots)
{
string key = GetPlotKey(plot.Center);
plotsByPosition[key] = plot;
}
}
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject("LandClaim_Plots", playerPlots);
}
private string GetPlotKey(Vector3 position)
{
int gridX = Mathf.FloorToInt(position.x / config.PlotSize);
int gridZ = Mathf.FloorToInt(position.z / config.PlotSize);
return $"{gridX}_{gridZ}";
}
#endregion
#region Hooks
private void Init()
{
LoadData();
permission.RegisterPermission("landclaim.admin", this);
permission.RegisterPermission("landclaim.extraplot", this); // Allows extra plot
}
private void OnServerInitialized()
{
if (config.Enabled)
{
// Start passive income timer
timer.Repeat(3600f, 0, () => DistributePassiveIncome());
// Show boundaries to players (if enabled)
if (config.ShowPlotBoundaries)
{
timer.Repeat(30f, 0, () => UpdateBoundariesForAll());
}
}
}
private void Unload()
{
SaveData();
}
private void OnServerSave() => SaveData();
private void OnEntityTakeDamage(BuildingBlock block, HitInfo info)
{
if (!config.Enabled) return;
if (info == null) return;
// Get plot at block position
var plot = GetPlotAtPosition(block.transform.position);
if (plot == null) return;
// Check if attacker is owner or shared
bool isOwner = info.InitiatorPlayer != null && (plot.OwnerId == info.InitiatorPlayer.userID || plot.SharedWith.Contains(info.InitiatorPlayer.userID));
if (!isOwner)
{
// Apply damage reduction for non-owners
info.damageTypes.ScaleAll(1f - (config.DamageReductionPercent / 100f));
// Send notification to owner
var owner = BasePlayer.FindByID(plot.OwnerId);
if (owner != null && owner.IsConnected)
{
SendReply(owner, $"<color=red>⚠️ Your land at {GetGridPosition(plot.Center)} is under attack!</color>");
}
}
}
private void DistributePassiveIncome()
{
if (!config.Enabled || config.PassiveIncomeScrapPerHour <= 0) return;
foreach (var plot in GetAllPlots())
{
var owner = BasePlayer.FindByID(plot.OwnerId);
if (owner != null && owner.IsConnected)
{
int scrapAmount = (int)config.PassiveIncomeScrapPerHour;
Item scrap = ItemManager.Create(ItemManager.FindItemDefinition("scrap"), scrapAmount);
if (scrap != null && owner.inventory.GiveItem(scrap))
{
SendReply(owner, $"<color=green>🏆 Land income! You received {scrapAmount} scrap from your territory.</color>");
}
}
}
}
private void UpdateBoundariesForAll()
{
if (!config.ShowPlotBoundaries) return;
foreach (var player in BasePlayer.activePlayerList)
{
// Show boundaries of plots near player
foreach (var plot in GetAllPlots())
{
if (Vector3.Distance(player.transform.position, plot.Center) < config.PlotSize + 50f)
{
ShowPlotBoundary(player, plot);
}
}
}
}
private void ShowPlotBoundary(BasePlayer player, LandPlot plot)
{
// Simple boundary visualization using effect
Color color = plot.OwnerId == player.userID ? Color.green : Color.red;
// Draw circle effect (visual only)
// Note: Full boundary visualization would require more complex rendering
}
#endregion
#region Core Logic
private string GetGridPosition(Vector3 position)
{
int gridSize = 10;
float mapSize = TerrainMeta.Size.x;
float cellSize = mapSize / gridSize;
char xChar = (char)('A' + Mathf.FloorToInt(position.x / cellSize));
int zNum = Mathf.FloorToInt(position.z / cellSize) + 1;
return $"{xChar}{zNum}";
}
private LandPlot GetPlotAtPosition(Vector3 position)
{
string key = GetPlotKey(position);
return plotsByPosition.ContainsKey(key) ? plotsByPosition[key] : null;
}
private IEnumerable<LandPlot> GetAllPlots()
{
return playerPlots.Values.SelectMany(p => p);
}
private int GetPlayerPlotCount(ulong userId)
{
return playerPlots.ContainsKey(userId) ? playerPlots[userId].Count : 0;
}
private bool CanBuyPlot(BasePlayer player, Vector3 position)
{
// Check max plots
int maxPlots = config.MaxPlotsPerPlayer;
if (permission.UserHasPermission(player.UserIDString, "landclaim.extraplot"))
{
maxPlots += 1;
}
if (GetPlayerPlotCount(player.userID) >= maxPlots)
{
SendReply(player, $"<color=red>You already own the maximum number of plots ({maxPlots})!</color>");
return false;
}
// Check if position is already claimed
var existingPlot = GetPlotAtPosition(position);
if (existingPlot != null)
{
SendReply(player, $"<color=red>This area is already claimed by {existingPlot.OwnerName}!</color>");
return false;
}
// Check if player has enough scrap
if (player.inventory.GetAmount(ItemManager.FindItemDefinition("scrap")) < config.PlotCost)
{
SendReply(player, $"<color=red>You need {config.PlotCost} scrap to buy a land plot!</color>");
return false;
}
return true;
}
private bool BuyPlot(BasePlayer player, Vector3 position)
{
if (!CanBuyPlot(player, position)) return false;
// Deduct scrap
player.inventory.Take(null, ItemManager.FindItemDefinition("scrap").itemid, config.PlotCost);
// Create plot
var plot = new LandPlot
{
OwnerId = player.userID,
OwnerName = player.displayName,
Center = position,
Radius = config.PlotSize,
PurchaseDate = DateTime.UtcNow,
LastIncomeTime = Time.realtimeSinceStartup
};
if (!playerPlots.ContainsKey(player.userID))
{
playerPlots[player.userID] = new List<LandPlot>();
}
playerPlots[player.userID].Add(plot);
string key = GetPlotKey(position);
plotsByPosition[key] = plot;
SaveData();
SendReply(player, $"<color=green>✅ You purchased a land plot at {GetGridPosition(position)} for {config.PlotCost} scrap!</color>");
SendReply(player, $"<color=yellow>Benefits: {config.DamageReductionPercent}% damage reduction to your buildings, {config.PassiveIncomeScrapPerHour} scrap/hour passive income.</color>");
// Visual effect
Effect.server.Run("assets/prefabs/locks/keylock/effects/lock.code.updated.prefab", position, Vector3.zero);
return true;
}
#endregion
#region Commands
[ChatCommand("land")]
private void cmdLand(BasePlayer player, string command, string[] args)
{
if (args.Length == 0)
{
SendReply(player, "<color=yellow>=== Land Claim System ===</color>");
SendReply(player, "/land buy - Buy the land you're standing on");
SendReply(player, "/land info - Show info about current land");
SendReply(player, "/land my - List your land plots");
SendReply(player, "/land share <name> - Share land with another player");
SendReply(player, "/land unshare <name> - Remove player from shared access");
return;
}
switch (args[0].ToLower())
{
case "buy":
BuyPlot(player, player.transform.position);
break;
case "info":
var plot = GetPlotAtPosition(player.transform.position);
if (plot == null)
{
SendReply(player, "<color=yellow>This land is unclaimed.Use /land buy to claim it!</color>");
SendReply(player, $"Cost: {config.PlotCost} scrap | Size: {config.PlotSize}m");
}
else
{
SendReply(player, $"<color=green>=== Land Info ===</color>");
SendReply(player, $"Owner: {plot.OwnerName}");
SendReply(player, $"Location: {GetGridPosition(plot.Center)}");
SendReply(player, $"Purchased: {plot.PurchaseDate:yyyy-MM-dd}");
SendReply(player, $"Shared with: {(plot.SharedWith.Count > 0 ? plot.SharedWith.Count.ToString() : "None")}");
}
break;
case "my":
if (playerPlots.ContainsKey(player.userID) && playerPlots[player.userID].Count > 0)
{
SendReply(player, "<color=green>=== Your Land Plots ===</color>");
foreach (var p in playerPlots[player.userID])
{
SendReply(player, $"📍 {GetGridPosition(p.Center)} - {p.Radius}m radius");
}
}
else
{
SendReply(player, "You don't own any land yet.Use /land buy");
}
break;
case "share":
if (args.Length < 2)
{
SendReply(player, "Usage: /land share <playerName>");
return;
}
var targetPlayer = BasePlayer.Find(args[1]);
if (targetPlayer == null)
{
SendReply(player, "Player not found");
return;
}
var currentPlot = GetPlotAtPosition(player.transform.position);
if (currentPlot == null || currentPlot.OwnerId != player.userID)
{
SendReply(player, "You don't own this land!");
return;
}
if (!currentPlot.SharedWith.Contains(targetPlayer.userID))
{
currentPlot.SharedWith.Add(targetPlayer.userID);
SaveData();
SendReply(player, $"<color=green>Added {targetPlayer.displayName} to your land!</color>");
SendReply(targetPlayer, $"<color=green>{player.displayName} shared their land with you!</color>");
}
break;
case "unshare":
if (args.Length < 2)
{
SendReply(player, "Usage: /land unshare <playerName>");
return;
}
var removePlayer = BasePlayer.Find(args[1]);
if (removePlayer == null)
{
SendReply(player, "Player not found");
return;
}
var plotToUnshare = GetPlotAtPosition(player.transform.position);
if (plotToUnshare == null || plotToUnshare.OwnerId != player.userID)
{
SendReply(player, "You don't own this land!");
return;
}
if (plotToUnshare.SharedWith.Remove(removePlayer.userID))
{
SaveData();
SendReply(player, $"<color=yellow>Removed {removePlayer.displayName} from your land.</color>");
}
break;
}
}
[ConsoleCommand("land.give")]
private void cmdGiveLand(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "landclaim.admin"))
{
args.ReplyWith("No permission");
return;
}
if (args.Args == null || args.Args.Length < 3)
{
args.ReplyWith("Usage: land.give <steamID> <x> <z>");
return;
}
ulong steamId = Convert.ToUInt64(args.Args[0]);
float x = Convert.ToSingle(args.Args[1]);
float z = Convert.ToSingle(args.Args[2]);
float y = TerrainMeta.HeightMap.GetHeight(new Vector3(x, 0, z));
var player = BasePlayer.FindByID(steamId);
if (player == null)
{
args.ReplyWith("Player not found");
return;
}
var plot = new LandPlot
{
OwnerId = steamId,
OwnerName = player.displayName,
Center = new Vector3(x, y, z),
Radius = config.PlotSize,
PurchaseDate = DateTime.UtcNow,
LastIncomeTime = Time.realtimeSinceStartup
};
if (!playerPlots.ContainsKey(steamId))
{
playerPlots[steamId] = new List<LandPlot>();
}
playerPlots[steamId].Add(plot);
string key = GetPlotKey(plot.Center);
plotsByPosition[key] = plot;
SaveData();
args.ReplyWith($"Gave land plot at ({x}, {z}) to {player.displayName}");
}
[ConsoleCommand("land.reload")]
private void cmdReload(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "landclaim.admin"))
{
args.ReplyWith("No permission");
return;
}
LoadConfig();
LoadData();
args.ReplyWith("LandClaimSystem reloaded");
}
#endregion
}
}