C#:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Text;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using Oxide.Core;
using UnityEngine;
using Building = BuildingManager.Building;
using Pool = Facepunch.Pool;
namespace Oxide.Plugins {
[Info("Limit Entities", "MON@H", "2.1.9")]
[Description("Limiting the number of entities a player can build")]
public class LimitEntities : RustPlugin {
#region Class Fields
[PluginReference]
private readonly Plugin RustTranslationAPI;
private const string PermissionAdmin = "limitentities.admin";
private const string PermissionImmunity = "limitentities.immunity";
private static readonly Static _static = new();
private readonly Cache _cache = new();
public enum LogLevel {
Off = 0, Error = 1, Warning = 2, Info = 3, Debug = 4,
}
public class BuildingEntities {
public uint BuildingID {
get;
set;
}
public readonly Hash<uint, int> EntitiesCount = new();
public readonly HashSet<ulong> EntitiesIds = new();
public void AddEntity(BaseEntity entity) {
EntitiesIds.Add(entity.net.ID.Value);
uint prefabID = _static.Instance.GetPrefabID(entity.prefabID);
EntitiesCount[prefabID]++;
}
public void AddRange(uint prefabID, int count) {
EntitiesCount[prefabID] += count;
}
public void RemoveEntity(BaseEntity entity) {
EntitiesIds.Remove(entity.net.ID.Value);
uint prefabID = _static.Instance.GetPrefabID(entity.prefabID);
EntitiesCount[prefabID]--;
}
public BuildingEntities(uint buildingID) {
BuildingID = buildingID;
}
}
public class Cache {
public readonly Hash<string, Hash<uint, string>> DisplayNames = new();
public readonly Hash<uint, BuildingEntities> Buildings = new();
public readonly Hash<ulong, PlayerData> PlayerData = new();
public readonly List<BuildingBlock> Blocks = new();
public readonly Permissions Permissions = new();
public readonly Prefabs Prefabs = new();
public readonly StringBuilders StringBuilders = new();
}
public class PlayerData {
public readonly string PlayerIdString;
public PermissionEntry Perms;
public bool HasImmunity {
get;
set;
}
public readonly PlayerEntities Entities = new();
public PlayerData(ulong playerId) {
PlayerIdString = playerId.ToString();
UpdatePerms();
}
public void UpdatePerms() {
HasImmunity = _static.Instance.permission.UserHasPermission(PlayerIdString, PermissionImmunity);
Perms = _static.Instance.GetPlayerPermissions(this);
}
public void AddEntity(uint prefabID) {
Entities.AddEntity(prefabID);
}
public void RemoveEntity(uint prefabID) {
Entities.RemoveEntity(prefabID);
}
public bool CanBuild() {
return Perms == null || HasImmunity || Perms.LimitsGlobal.LimitTotal != 0;
}
public bool IsGlobalLimit() {
if (Perms == null) {
return false;
}
LimitsEntry limit = Perms.LimitsGlobal;
return Entities.TotalCount >= limit.LimitTotal;
}
public bool IsGlobalLimit(uint prefabId) {
if (Perms == null) {
return false;
}
Hash<uint, int> limitEntities = Perms.LimitsGlobal.LimitEntitiesCache;
if (!limitEntities.ContainsKey(prefabId)) {
return false;
}
return Entities.PlayersEntities[prefabId] >= limitEntities[prefabId];
}
public bool IsBuildingLimit(BuildingEntities entities) {
if (Perms == null) {
return false;
}
LimitsEntry limit = Perms.LimitsBuilding;
return entities.EntitiesIds.Count >= limit.LimitTotal;
}
public bool IsBuildingLimit(BuildingEntities entities, uint prefabId) {
if (Perms == null) {
return false;
}
Hash<uint, int> limitEntities = Perms.LimitsBuilding.LimitEntitiesCache;
if (!limitEntities.ContainsKey(prefabId)) {
return false;
}
return entities.EntitiesCount[prefabId] >= limitEntities[prefabId];
}
public float GetGlobalPercentage() {
if (Perms == null) {
return 0;
}
LimitsEntry limit = Perms.LimitsGlobal;
return (float)Entities.TotalCount / limit.LimitTotal * 100;
}
public float GetGlobalPercentage(uint prefabId) {
if (Perms == null) {
return 0;
}
Hash<uint, int> limitEntities = Perms.LimitsGlobal.LimitEntitiesCache;
if (!limitEntities.ContainsKey(prefabId)) {
return 0;
}
return (float)Entities.PlayersEntities[prefabId] / limitEntities[prefabId] * 100;
}
public float GetBuildingPercentage(BuildingEntities entities) {
if (Perms == null) {
return 0;
}
LimitsEntry limit = Perms.LimitsBuilding;
return (float)entities.EntitiesIds.Count / limit.LimitTotal * 100;
}
public float GetBuildingPercentage(BuildingEntities entities, uint prefabId) {
if (Perms == null) {
return 0;
}
Hash<uint, int> limitEntities = Perms.LimitsBuilding.LimitEntitiesCache;
if (!limitEntities.ContainsKey(prefabId)) {
return 0;
}
return (float)entities.EntitiesCount[prefabId] / limitEntities[prefabId] * 100;
}
}
public class PlayerEntities {
public int TotalCount;
public readonly Hash<uint, int> PlayersEntities = new();
public void AddEntity(uint prefabID) {
TotalCount++;
PlayersEntities[prefabID]++;
}
public void AddRange(uint prefabID, int count) {
TotalCount += count;
PlayersEntities[prefabID] += count;
}
public void RemoveEntity(uint prefabID) {
TotalCount--;
int count = PlayersEntities[prefabID];
if (count < 1) {
PlayersEntities.Remove(prefabID);
return;
}
PlayersEntities[prefabID] = count - 1;
}
}
public class Prefabs {
public readonly Hash<uint, string> ShortNames = new();
public readonly Hash<uint, uint> Groups = new();
public readonly HashSet<uint> BuildingBlocks = new();
public readonly HashSet<uint> Tracked = new();
}
public class Permissions {
public PermissionEntry[] Descending {
get;
set;
}
public string[] Registered {
get;
set;
}
}
public class Static {
public LimitEntities Instance {
get;
set;
}
public readonly object True = true;
public readonly object False = false;
public readonly Regex Tags = new("<color=.+?>|</color>|<size=.+?>|</size>|<i>|</i>|<b>|</b>", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
}
public class StringBuilders {
public readonly StringBuilder LimitsBuilding = new();
public readonly StringBuilder LimitsGlobal = new();
}
#endregion Class Fields
#region Initialization
private void Init() => HooksUnsubscribe();
private void OnServerInitialized() {
_static.Instance = this;
RegisterPermissions();
AddCommands();
CacheGroupIds();
CachePermissions();
DataLoad();
CachePrefabIds();
CacheEntities();
RegisterMessages();
foreach (BasePlayer activePlayer in BasePlayer.activePlayerList) {
GetPlayerData(activePlayer.userID);
}
HooksSubscribe();
}
private void Unload() {
DataSave();
_static.Instance = null;
}
private void OnNewSave() => DataClear();
#endregion Initialization
#region Configuration
private PluginConfig _pluginConfig;
public class PluginConfig {
[JsonConverter(typeof(StringEnumConverter))]
[DefaultValue(LogLevel.Off)]
[JsonProperty(PropertyName = "Log Level (Debug, Info, Warning, Error, Off)", Order = 4)]
public LogLevel LoggingLevel {
get;
set;
}
[JsonProperty(PropertyName = "Enable GameTip notifications")]
public bool GameTipNotificationsEnabled {
get;
set;
}
[JsonProperty(PropertyName = "Enable notifications in chat")]
public bool ChatNotificationsEnabled {
get;
set;
}
[JsonProperty(PropertyName = "Chat steamID icon")]
public ulong SteamIDIcon {
get;
set;
}
[JsonProperty(PropertyName = "Commands list")]
public string[] Commands {
get;
set;
}
[JsonProperty(PropertyName = "Warn when more than %")]
[DefaultValue(80f)]
public float WarnPercent {
get;
set;
}
[JsonProperty(PropertyName = "Excluded list")]
public string[] Excluded {
get;
set;
}
[JsonProperty(PropertyName = "Entity Groups")]
public List<EntityGroup> EntityGroups {
get;
set;
}
[JsonProperty(PropertyName = "Permissions")]
public PermissionEntry[] Permissions {
get;
set;
}
}
public class LimitsEntry {
[JsonProperty(PropertyName = "Limit Total")]
public int LimitTotal {
get;
set;
}
[JsonProperty(PropertyName = "Limits Entities")]
public SortedDictionary<string, int> LimitsEntities {
get;
set;
}
[JsonIgnore]
public readonly Hash<uint, int> LimitEntitiesCache = new();
}
public class PermissionEntry {
[JsonProperty(PropertyName = "Permission")]
public string Permission {
get;
set;
}
[JsonProperty(PropertyName = "Priority")]
public int Priority {
get;
set;
}
[JsonProperty(PropertyName = "Limits Global")]
public LimitsEntry LimitsGlobal {
get;
set;
}
[JsonProperty(PropertyName = "Limits Building")]
public LimitsEntry LimitsBuilding {
get;
set;
}
[JsonProperty(PropertyName = "Prevent excessive merging of buildings")]
public bool MergingCheck {
get;
set;
}
}
public class EntityGroup {
[JsonProperty(PropertyName = "Group name")]
public string Name {
get;
set;
}
[JsonIgnore]
public uint ID {
get;
set;
}
[JsonProperty(PropertyName = "Group Entities list")]
public List<string> ListEntities {
get;
set;
}
[JsonIgnore]
public readonly List<uint> ListEntitiesCache = new();
}
protected override void LoadDefaultConfig() => PrintWarning("Loading Default Config");
protected override void LoadConfig() {
base.LoadConfig();
Config.Settings.DefaultValueHandling = DefaultValueHandling.Populate;
_pluginConfig = AdditionalConfig(Config.ReadObject<PluginConfig>());
Config.WriteObject(_pluginConfig);
}
public PluginConfig AdditionalConfig(PluginConfig config) {
config.Commands ??= new[] {
"limits", "limit"
}
;
config.Excluded ??= new[] {
"assets/prefabs/building/ladder.wall.wood/ladder.wooden.wall.prefab"
}
;
config.EntityGroups ??= new() {
new() {
Name = "Foundations", ListEntities = new() {
"assets/prefabs/building core/foundation.triangle/foundation.triangle.prefab", "assets/prefabs/building core/foundation/foundation.prefab",
}
}
, new() {
Name = "Furnace", ListEntities = new() {
"assets/prefabs/deployable/furnace/furnace.prefab", "assets/prefabs/deployable/legacyfurnace/legacy_furnace.prefab", "assets/prefabs/deployable/playerioents/electricfurnace/electricfurnace.deployed.prefab",
}
}
, new() {
Name = "PlanterBoxes", ListEntities = new() {
"assets/prefabs/deployable/planters/planter.large.deployed.prefab", "assets/prefabs/deployable/planters/planter.small.deployed.prefab", "assets/prefabs/misc/decor_dlc/bath tub planter/bathtub.planter.deployed.prefab", "assets/prefabs/misc/decor_dlc/minecart planter/minecart.planter.deployed.prefab", "assets/prefabs/misc/decor_dlc/rail road planter/railroadplanter.deployed.prefab",
}
}
, new() {
Name = "Quarries", ListEntities = new() {
"assets/prefabs/deployable/oil jack/mining.pumpjack.prefab", "assets/prefabs/deployable/quarry/mining_quarry.prefab",
}
}
, new() {
Name = "Roof", ListEntities = new() {
"assets/prefabs/building core/roof.triangle/roof.triangle.prefab", "assets/prefabs/building core/roof/roof.prefab",
}
}
, new() {
Name = "TC", ListEntities = new() {
"assets/prefabs/deployable/tool cupboard/cupboard.tool.deployed.prefab", "assets/prefabs/deployable/tool cupboard/retro/cupboard.tool.retro.deployed.prefab",
}
}
,
}
;
config.Permissions ??= new PermissionEntry[] {
new() {
Permission = nameof(LimitEntities).ToLower() + ".default", Priority = 10, LimitsGlobal = new() {
LimitTotal = 2000, LimitsEntities = new() {
{
"assets/content/props/strobe light/strobelight.prefab", 10
}
, {
"assets/prefabs/deployable/campfire/campfire.prefab", 20
}
, {
"assets/prefabs/deployable/ceiling light/ceilinglight.deployed.prefab", 50
}
, {
"assets/prefabs/deployable/furnace.large/furnace.large.prefab", 5
}
, {
"assets/prefabs/deployable/oil refinery/refinery_small_deployed.prefab", 5
}
, {
"assets/prefabs/deployable/search light/searchlight.deployed.prefab", 10
}
, {
"assets/prefabs/deployable/windmill/electric.windmill.small.prefab", 10
}
, {
"assets/prefabs/npc/sam_site_turret/sam_site_turret_deployed.prefab", 0
}
, {
"Foundations", 300
}
, {
"Furnace", 10
}
, {
"PlanterBoxes", 50
}
, {
"Quarries", 2
}
, {
"Roof", 200
}
, {
"TC", 10
}
,
}
}
, LimitsBuilding = new() {
LimitTotal = 1000, LimitsEntities = new() {
{
"assets/content/props/strobe light/strobelight.prefab", 5
}
, {
"assets/prefabs/deployable/search light/searchlight.deployed.prefab", 5
}
, {
"assets/prefabs/deployable/windmill/electric.windmill.small.prefab", 5
}
,
}
}
}
, new() {
Permission = nameof(LimitEntities).ToLower() + ".vip", Priority = 20, LimitsGlobal = new() {
LimitTotal = 5000, LimitsEntities = new() {
{
"Foundations", 500
}
, {
"Roof", 400
}
,
}
,
}
, LimitsBuilding = new() {
LimitTotal = 2000, LimitsEntities = new() {
{
"assets/content/props/strobe light/strobelight.prefab", 15
}
, {
"assets/prefabs/deployable/search light/searchlight.deployed.prefab", 15
}
, {
"assets/prefabs/deployable/windmill/electric.windmill.small.prefab", 15
}
,
}
}
}
, new() {
Permission = nameof(LimitEntities).ToLower() + ".elite", Priority = 30, LimitsGlobal = new() {
LimitTotal = 10000, LimitsEntities = new() {
{
"Foundations", 2000
}
, {
"Roof", 1000
}
,
}
,
}
, LimitsBuilding = new() {
LimitTotal = 5000, LimitsEntities = new() {
{
"assets/content/props/strobe light/strobelight.prefab", 20
}
, {
"assets/prefabs/deployable/search light/searchlight.deployed.prefab", 20
}
, {
"assets/prefabs/deployable/windmill/electric.windmill.small.prefab", 20
}
,
}
}
}
,
}
;
return config;
}
#endregion Configuration
#region Stored Data
private StoredData _storedData;
private class StoredData {
public readonly Hash<uint, ulong> BuildingsOwners = new();
}
public void DataLoad() {
try {
_storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
}
catch {
DataClear();
DataLoad();
}
if (_storedData.BuildingsOwners.Count > 0) {
ListDictionary<uint, Building> serverBuildings = BuildingManager.server.buildingDictionary;
List<uint> buildingsToRemove = Pool.Get<List<uint>>();
foreach (uint buildingId in _storedData.BuildingsOwners.Keys) {
if (!serverBuildings.Contains(buildingId)) {
buildingsToRemove.Add(buildingId);
}
}
for (int index = 0; index < buildingsToRemove.Count; index++) {
uint buildingId = buildingsToRemove[index];
_storedData.BuildingsOwners.Remove(buildingId);
}
Pool.FreeUnsafe(ref buildingsToRemove);
Log($"{_storedData.BuildingsOwners.Count} buildings with owners loaded from data file", LogLevel.Debug);
return;
}
Log("Collecting all buildings owners on the server", LogLevel.Debug);
for (int i = 0; i < BuildingManager.server.buildingDictionary.Values.Count; i++) {
Building building = BuildingManager.server.buildingDictionary.Values[i];
if (!building.HasDecayEntities() || _storedData.BuildingsOwners.ContainsKey(building.ID)) {
continue;
}
ulong netID = uint.MaxValue;
ulong ownerId = 0;
for (int index = 0; index < building.decayEntities.Count; index++) {
DecayEntity decayEntity = building.decayEntities[index];
if (decayEntity.IsValid() && decayEntity.OwnerID.IsSteamId() && decayEntity.net.ID.Value < netID) {
netID = decayEntity.net.ID.Value;
ownerId = decayEntity.OwnerID;
}
}
if (ownerId != 0) {
Log($"Adding missing owner ID: {ownerId} for building ID: {building.ID}", LogLevel.Debug);
_storedData.BuildingsOwners[building.ID] = ownerId;
}
}
if (_storedData.BuildingsOwners.Count > 0) {
Log($"{_storedData.BuildingsOwners.Count} buildings with owners added to data file", LogLevel.Debug);
DataSave();
return;
}
Log("No buildings with owners found on the server!", LogLevel.Warning);
}
public void DataClear() {
PrintWarning("Creating a new data file");
_storedData = new();
DataSave();
}
public void DataSave() {
if (_storedData != null) {
Interface.Oxide.DataFileSystem.WriteObject(Name, _storedData);
}
}
#endregion Stored Data
#region Localization
public string Lang(string key, string userIDString = null, params object[] args) {
try {
return string.Format(lang.GetMessage(key, this, userIDString), args);
}
catch (Exception ex) {
PrintError($"Lang Key '{key}' threw exception:\n{ex}");
throw;
}
}
private static class LangKeys {
public static class Error {
private const string Base = nameof(Error) + ".";
public const string EntityIsNotAllowed = Base + nameof(EntityIsNotAllowed);
public const string NoPermission = Base + nameof(NoPermission);
public const string PlayerNotFound = Base + nameof(PlayerNotFound);
public static class LimitBuilding {
private const string Base = Error.Base + nameof(LimitBuilding) + ".";
public const string EntityMergeBlocked = Base + nameof(EntityMergeBlocked);
public const string EntityReached = Base + nameof(EntityReached);
public const string MergeBlocked = Base + nameof(MergeBlocked);
public const string Reached = Base + nameof(Reached);
}
public static class LimitGlobal {
private const string Base = Error.Base + nameof(LimitGlobal) + ".";
public const string EntityReached = Base + nameof(EntityReached);
public const string Reached = Base + nameof(Reached);
}
}
public static class Format {
private const string Base = nameof(Format) + ".";
public const string Prefix = Base + nameof(Prefix);
}
public static class Info {
private const string Base = nameof(Info) + ".";
public const string Help = Base + nameof(Help);
public const string LimitBuilding = Base + nameof(LimitBuilding);
public const string LimitBuildingEntity = Base + nameof(LimitBuildingEntity);
public const string LimitGlobal = Base + nameof(LimitGlobal);
public const string LimitGlobalEntity = Base + nameof(LimitGlobalEntity);
public const string Limits = Base + nameof(Limits);
public const string TotalAmount = Base + nameof(TotalAmount);
public const string Unlimited = Base + nameof(Unlimited);
}
}
private readonly Dictionary<string, string> _langEN = new() {
[LangKeys.Error.EntityIsNotAllowed]
= "You are not allowed to build <color=#FFA500>{0}</color>", [LangKeys.Error.PlayerNotFound] = "Player <color=#FFA500>{0}</color> not found!", [LangKeys.Error.LimitBuilding.EntityMergeBlocked] = "You can't merge these buildings because the limit of <color=#FFA500>{1}</color> will be exceeded by <color=#FFA500>{0}</color>", [LangKeys.Error.LimitBuilding.EntityReached] = "You have reached the limit <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> <color=#FFA500>{2}</color> in this building", [LangKeys.Error.LimitBuilding.MergeBlocked] = "You can't merge these buildings because the limit of entities will be exceeded by <color=#FFA500>{0}</color>", [LangKeys.Error.LimitBuilding.Reached] = "You have reached the limit <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> entities in this building", [LangKeys.Error.LimitGlobal.EntityReached] = "You have reached the limit <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> <color=#FFA500>{2}</color>", [LangKeys.Error.LimitGlobal.Reached] = "You have reached the global limit <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> entities", [LangKeys.Error.NoPermission] = "You do not have permission to use this command!", [LangKeys.Format.Prefix] = "<color=#00FF00>[Limit Entities]</color>: ", [LangKeys.Info.Help] = "Get current limits: <color=#FFFF00>/{0}</color>", [LangKeys.Info.LimitBuilding] = "You have built <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> entities in this building", [LangKeys.Info.LimitBuildingEntity] = "You have built <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> <color=#FFA500>{2}</color> in this building", [LangKeys.Info.LimitGlobal] = "You have built <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> entities", [LangKeys.Info.LimitGlobalEntity] = "You have built <color=#FFA500>{0}</color> of <color=#FFA500>{1}</color> <color=#FFA500>{2}</color>", [LangKeys.Info.Limits] = "\nYour global limits are:\n<color=#FFA500>{0}</color>\nYour limits per building are:\n<color=#FFA500>{1}</color>", [LangKeys.Info.TotalAmount] = "Total amount: <color=#FFA500>{0}</color>", [LangKeys.Info.Unlimited] = "Your ability to build is unlimited", ["Foundations"] = "Foundations", ["Furnace"] = "Furnace", ["PlanterBoxes"] = "PlanterBoxes", ["Quarries"] = "Quarries", ["Roof"] = "Roof", ["TC"] = "TC",
}
;
private readonly Dictionary<string, string> _langRU = new() {
[LangKeys.Error.EntityIsNotAllowed]
= "Вам не разрешено строить <color=#FFA500>{0}</color>", [LangKeys.Error.PlayerNotFound] = "Игрок <color=#FFA500>{0}</color> не найден!", [LangKeys.Error.LimitBuilding.EntityMergeBlocked] = "Вы не можете объединить эти здания, поскольку лимит <color=#FFA500>{1}</color> будет превышен на <color=#FFA500>{0}</color>", [LangKeys.Error.LimitBuilding.EntityReached] = "Вы достигли лимита <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> для <color=#FFA500>{2}</color> в этом здании", [LangKeys.Error.LimitBuilding.MergeBlocked] = "Вы не можете объединить эти здания, поскольку лимит объектов будет превышен на <color=#FFA500>{0}</color>", [LangKeys.Error.LimitBuilding.Reached] = "Вы достигли лимита <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> объектов в этом здании", [LangKeys.Error.LimitGlobal.EntityReached] = "Вы достигли лимита <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> для <color=#FFA500>{2}</color>", [LangKeys.Error.LimitGlobal.Reached] = "Вы достигли глобального лимита <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> объектов", [LangKeys.Error.NoPermission] = "У вас нет разрешения на использование этой команды!", [LangKeys.Format.Prefix] = "<color=#00FF00>[Лимит построек]</color>: ", [LangKeys.Info.Help] = "Отобразить текущие лимиты: <color=#FFFF00>/{0}</color>", [LangKeys.Info.LimitBuilding] = "Вы построили <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> объектов в этом здании", [LangKeys.Info.LimitBuildingEntity] = "Вы построили <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> <color=#FFA500>{2}</color> в этом здании", [LangKeys.Info.LimitGlobal] = "Вы построили <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> объектов", [LangKeys.Info.LimitGlobalEntity] = "Вы построили <color=#FFA500>{0}</color> из <color=#FFA500>{1}</color> <color=#FFA500>{2}</color>", [LangKeys.Info.Limits] = "\nВаши глобальные лимиты:\n<color=#FFA500>{0}</color>\nВаши лимиты для постройки:\n<color=#FFA500>{1}</color>", [LangKeys.Info.TotalAmount] = "Общее количество: <color=#FFA500>{0}</color>", [LangKeys.Info.Unlimited] = "Ваши возможности строительства не ограничены", ["Foundations"] = "Фундамент", ["Furnace"] = "Печь", ["PlanterBoxes"] = "Плантация", ["Quarries"] = "Карьер", ["Roof"] = "Крыша", ["TC"] = "Шкаф с инструментами",
}
;
private readonly Dictionary<string, string> _langUK = new() {
[LangKeys.Error.EntityIsNotAllowed]
= "Вам не дозволено будувати <color=#FFA500>{0}</color>", [LangKeys.Error.PlayerNotFound] = "Гравеця <color=#FFA500>{0}</color> не знайдено!", [LangKeys.Error.LimitBuilding.EntityMergeBlocked] = "Ви не можете об'єднати ці будівлі, оскільки ліміт <color=#FFA500>{1}</color> буде перевищено на <color=#FFA500>{0}</color>", [LangKeys.Error.LimitBuilding.EntityReached] = "Ви досягли ліміту <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> для <color=#FFA500>{2}</color> у цій будівлі", [LangKeys.Error.LimitBuilding.MergeBlocked] = "Ви не можете об'єднати ці будівлі, оскільки ліміт об'єктів буде перевищено на <color=#FFA500>{0}</color>", [LangKeys.Error.LimitBuilding.Reached] = "Ви досягли ліміту <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> об'єктів в цьому будинку", [LangKeys.Error.LimitGlobal.EntityReached] = "Ви досягли ліміту <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> для <color=#FFA500>{2}</color>", [LangKeys.Error.LimitGlobal.Reached] = "Ви досягли глобального ліміту <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> об'єктів", [LangKeys.Error.NoPermission] = "У вас немає дозволу використовувати цю команду!", [LangKeys.Format.Prefix] = "<color=#00FF00>[Ліміт об'єктів]</color>: ", [LangKeys.Info.Help] = "Відобразити поточні ліміти: <color=#FFFF00>/{0}</color>", [LangKeys.Info.LimitBuilding] = "Ви побудували <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> об'єктів в цьому будинку", [LangKeys.Info.LimitBuildingEntity] = "Ви побудували <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> <color=#FFA500>{2}</color> в цьому будинку", [LangKeys.Info.LimitGlobal] = "Ви побудували <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> об'єктів", [LangKeys.Info.LimitGlobalEntity] = "Ви побудували <color=#FFA500>{0}</color> із <color=#FFA500>{1}</color> <color=#FFA500>{2}</color>", [LangKeys.Info.Limits] = "\nВаші глобальні ліміти:\n<color=#FFA500>{0}</color>\nВаші ліміти для будівництва:\n<color=#FFA500>{1}</color>", [LangKeys.Info.TotalAmount] = "Загальна кількість: <color=#FFA500>{0}</color>", [LangKeys.Info.Unlimited] = "Ваша можливість будувати необмежена", ["Foundations"] = "Фундамент", ["Furnace"] = "Піч", ["PlanterBoxes"] = "Плантація", ["Quarries"] = "Кар'єр", ["Roof"] = "Дах", ["TC"] = "Шафа з інструментами",
}
;
public void RegisterMessages() {
lang.RegisterMessages(_langEN, this);
lang.RegisterMessages(_langRU, this, "ru");
lang.RegisterMessages(_langUK, this, "uk");
}
#endregion Localization
#region Commands
private void CmdLimitEntities(BasePlayer player, string cmd, string[] args) {
if (!player.IsValid() || !player.userID.IsSteamId()) {
return;
}
BasePlayer target = player;
if (args != null && args.Length > 0) {
string strNameOrIDOrIP = args[0];
target = BasePlayer.FindAwakeOrSleeping(strNameOrIDOrIP);
if (target == null) {
PlayerSendMessage(player, Lang(LangKeys.Error.PlayerNotFound, player.UserIDString, strNameOrIDOrIP));
return;
}
}
PlayerSendMessage(player, GetPlayerLimitString(player, target));
}
[ConsoleCommand("limitentities.list")]
private void CmdLimitEntitiesList(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Player();
if (player.IsValid() && !IsPlayerAdmin(player)) {
return;
}
StringBuilder sb = new();
sb.AppendLine();
sb.Append("All tracked entities list start");
sb.AppendLine();
foreach (uint trackedPrefabID in _cache.Prefabs.Tracked) {
sb.AppendLine();
sb.Append("PrefabID: ");
sb.Append(trackedPrefabID);
sb.AppendLine();
sb.Append("PrefabShortName: ");
sb.Append(_cache.Prefabs.ShortNames[trackedPrefabID]);
sb.AppendLine();
sb.Append("Prefab: ");
sb.Append(StringPool.Get(trackedPrefabID));
sb.AppendLine();
}
sb.AppendLine();
sb.Append("All tracked entities list finish");
sb.AppendLine();
Log(sb.ToString(), LogLevel.Off);
Puts($"Successfully listed {_cache.Prefabs.Tracked.Count} entities in the log file.You can find the log at: {Interface.Oxide.LogDirectory}");
}
#endregion Commands
#region Oxide Hooks
private object CanBuild(Planner planner, Construction entity, Construction.Target target) => HandleCanBuild(planner?.GetOwnerPlayer(), entity, target);
private object OnPoweredLightsPointAdd(PoweredLightsDeployer deployer, BasePlayer player, Vector3 vector, Vector3 vector2) => HandlePoweredLightsAddPoint(deployer, player);
private void OnBuildingMerge(ServerBuildingManager manager, Building to, Building from) {
uint oldId = from.ID;
uint newId = to.ID;
NextTick(() => {
HandleBuildingСhange(oldId, newId, false);
}
);
}
private void OnBuildingSplit(Building building, uint newId) {
uint oldId = building.ID;
NextTick(() => {
HandleBuildingСhange(oldId, newId, true);
}
);
}
private void OnPlayerConnected(BasePlayer player) => GetPlayerData(player.userID);
private void OnEntitySpawned(BaseEntity entity) {
if (!IsValidEntity(entity)) {
return;
}
Vector3 position = entity.transform.position;
ulong ownerID = entity.OwnerID;
uint prefabID = GetPrefabID(entity.prefabID);
uint buildingID = AddBuildingEntity(entity);
if (buildingID > 0 && !_storedData.BuildingsOwners.ContainsKey(buildingID)) {
_storedData.BuildingsOwners[buildingID] = ownerID;
Log($"{position} {ownerID} Added new building {buildingID}", LogLevel.Debug);
}
PlayerData playerData = GetPlayerData(ownerID);
playerData.AddEntity(GetPrefabID(entity.prefabID));
if (_pluginConfig.LoggingLevel >= LogLevel.Debug) {
PlayerEntities entities = playerData.Entities;
Log($"{position} {ownerID} Increased player entity {GetShortName(prefabID)} count {entities.PlayersEntities[prefabID]}", LogLevel.Debug);
Log($"{position} {ownerID} Increased player total entities count {entities.TotalCount}", LogLevel.Debug);
}
if ((_pluginConfig.ChatNotificationsEnabled || _pluginConfig.GameTipNotificationsEnabled) && _pluginConfig.WarnPercent > 0 && !playerData.HasImmunity) {
HandleEntityNotification(BasePlayer.FindByID(entity.OwnerID), playerData, _cache.Buildings[buildingID], prefabID);
}
}
private void OnEntityKill(BaseEntity entity) {
if (!IsValidEntity(entity)) {
return;
}
uint prefabID = GetPrefabID(entity.prefabID);
ulong ownerID = entity.OwnerID;
uint buildingID = RemoveBuildingEntity(entity);
Vector3 position = entity.transform.position;
if (entity is BuildingBlock) {
if (BuildingManager.server.buildingDictionary.TryGetValue(buildingID, out Building building) && building.decayEntities.Count == 1 && building.decayEntities.Contains(entity as DecayEntity)) {
_storedData.BuildingsOwners.Remove(buildingID);
Log($"{position} {ownerID} Removed building {buildingID}", LogLevel.Debug);
}
}
PlayerData playerData = GetPlayerData(ownerID);
PlayerEntities entities = playerData.Entities;
playerData.RemoveEntity(GetPrefabID(entity.prefabID));
if (_pluginConfig.LoggingLevel >= LogLevel.Debug) {
Log($"{position} {ownerID} Reduced player entity {GetShortName(prefabID)} count {entities.PlayersEntities[prefabID]}", LogLevel.Debug);
Log($"{position} {ownerID} Reduced player total entities count {entities.TotalCount}", LogLevel.Debug);
}
}
#endregion Oxide Hooks
#region Permissions Hooks
private void OnGroupCreated(string groupName) => OnGroupDeleted(groupName);
private void OnGroupDeleted(string groupName) {
foreach (string perm in _cache.Permissions.Registered) {
if (permission.GroupHasPermission(groupName, perm)) {
HandleGroup(groupName);
break;
}
}
}
private void OnGroupPermissionGranted(string groupName, string perm) => OnGroupPermissionRevoked(groupName, perm);
private void OnGroupPermissionRevoked(string groupName, string perm) {
if (!_cache.Permissions.Registered.Contains(perm)) {
return;
}
HandleGroup(groupName);
}
private void OnUserGroupAdded(string userIDString, string groupName) => OnUserGroupRemoved(userIDString, groupName);
private void OnUserGroupRemoved(string userIDString, string groupName) {
foreach (string perm in _cache.Permissions.Registered) {
if (permission.GroupHasPermission(groupName, perm)) {
HandleUserByString(userIDString);
break;
}
}
}
private void OnUserPermissionGranted(string userIDString, string perm) => OnUserPermissionRevoked(userIDString, perm);
private void OnUserPermissionRevoked(string userIDString, string perm) {
if (!_cache.Permissions.Registered.Contains(perm)) {
return;
}
HandleUserByString(userIDString);
}
#endregion Permissions Hooks
#region Permissions Methods
public void HandleGroup(string groupName) {
foreach (string userIDString in permission.GetUsersInGroup(groupName)) {
HandleUserByString(userIDString.Substring(0, 17));
}
}
public void HandleUserByString(string userIDString) {
if (!ulong.TryParse(userIDString, out ulong userID)) {
return;
}
_cache.PlayerData[userID]?.UpdatePerms();
}
#endregion Permissions Methods
#region Core Methods
public void CacheGroupIds() {
Log($"Cache creation for entities groups started", LogLevel.Debug);
uint ID = StringPool.closest + 10000;
foreach (EntityGroup entityGroup in _pluginConfig.EntityGroups) {
foreach (string prefab in entityGroup.ListEntities) {
uint prefabID = StringPool.Get(prefab);
if (prefabID > 0) {
entityGroup.ListEntitiesCache.Add(prefabID);
}
}
if (entityGroup.ListEntitiesCache.Count == 0) {
Log($"You have 0 valid entities in {entityGroup.Name} group in your config file! To get a list of all supported prefabs use console command limitentities.list", LogLevel.Error);
continue;
}
do {
ID++;
}
while (StringPool.toString.ContainsKey(ID));
entityGroup.ID = ID;
foreach (uint prefabID in entityGroup.ListEntitiesCache) {
_cache.Prefabs.Groups[prefabID] = ID;
}
_cache.Prefabs.ShortNames[ID] = entityGroup.Name;
if (!_langEN.ContainsKey(entityGroup.Name)) {
_langEN[entityGroup.Name] = entityGroup.Name;
}
if (!_langRU.ContainsKey(entityGroup.Name)) {
_langRU[entityGroup.Name] = entityGroup.Name;
}
if (!_langUK.ContainsKey(entityGroup.Name)) {
_langUK[entityGroup.Name] = entityGroup.Name;
}
}
Log($"Cache creation for entities groups finished", LogLevel.Debug);
}
public void CachePermissions() {
Dictionary<string, uint> groupStringPool = new();
foreach (EntityGroup entityGroup in _pluginConfig.EntityGroups) {
if (entityGroup.ID == 0) {
continue;
}
groupStringPool[entityGroup.Name] = entityGroup.ID;
}
foreach (PermissionEntry entry in _pluginConfig.Permissions) {
foreach (KeyValuePair<string, int> entity in entry.LimitsBuilding.LimitsEntities) {
if (!groupStringPool.TryGetValue(entity.Key, out uint prefabID)) {
prefabID = StringPool.Get(entity.Key);
}
entry.LimitsBuilding.LimitEntitiesCache[prefabID] = entity.Value;
}
foreach (KeyValuePair<string, int> entity in entry.LimitsGlobal.LimitsEntities) {
if (!groupStringPool.TryGetValue(entity.Key, out uint prefabID)) {
prefabID = StringPool.Get(entity.Key);
}
entry.LimitsGlobal.LimitEntitiesCache[prefabID] = entity.Value;
}
}
}
public void CachePrefabIds() {
Log("Cache creation started for deployables to be tracked", LogLevel.Debug);
uint prefabID;
for (int index = 0; index < ItemManager.itemList.Count; index++) {
ItemDefinition def = ItemManager.itemList[index];
BaseEntity entity = def.GetComponent<ItemModDeployable>()?.entityPrefab.Get().GetComponent<BaseEntity>();
entity ??= def.GetComponent<ItemModEntity>()?.entityPrefab.Get().GetComponent<PoweredLightsDeployer>()?.poweredLightsPrefab.GetEntity();
if (!entity || entity is GrowableEntity || _pluginConfig.Excluded.Contains(entity.PrefabName)) {
continue;
}
_cache.Prefabs.Tracked.Add(entity.prefabID);
if (!_cache.Prefabs.ShortNames.ContainsKey(entity.prefabID)) {
_cache.Prefabs.ShortNames[entity.prefabID] = entity.ShortPrefabName;
}
}
BaseEntity[] planner = ItemManager.FindItemDefinition("building.planner").GetComponent<ItemModEntity>().entityPrefab.GetEntity().GetComponent<Planner>()?.buildableList;
foreach (BaseEntity entity in planner) {
_cache.Prefabs.Tracked.Add(entity.prefabID);
if (!_cache.Prefabs.ShortNames.ContainsKey(entity.prefabID)) {
_cache.Prefabs.ShortNames[entity.prefabID] = entity.ShortPrefa
// ... [Code preview truncated due to length. Full source in attached file] ...