C#:
using System.Collections.Generic;
using System.Collections;
using System.Globalization;
using System.Linq;
using System.IO;
using System;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
namespace Oxide.Plugins {
[Info("Building Health Manager", "Miho", "1.0.6")]
[Description("Managing the Health of Construction")]
public class BuildingHealthManager : RustPlugin {
[PluginReference]
private Plugin RaidableBases;
#region Fields
private const string PermDefault = "buildinghealthmanager.default";
private const string PermVIP = "buildinghealthmanager.vip";
private const string PermVVIP = "buildinghealthmanager.vvip";
private const string PermAdmin = "buildinghealthmanager.admin";
private const string PanelName = "bhm_panel";
private const string ConfigVersion = "1.0.6";
private const float LegacyHealthMarker = 100f;
private const string NameKeyPrefix = "name.";
private const int Columns = 4;
private const int Rows = 2;
private const int PerPage = Columns * Rows;
private const double MenuLeft = 0.02;
private const double MenuRight = 0.2;
private const double MenuBottom = 0.715;
private const double MenuHeight = 0.085;
private const double MenuPitch = 0.115;
private const double ContentLeft = 0.225;
private const double ColPitch = 0.19;
private const double TileWidth = 0.175;
private const double RowTop = 0.855;
private const double RowPitch = 0.42;
private const double TileHeight = 0.39;
private const double NameHeight = 0.038;
private const double ImageTop = 0.046;
private const double ImageHeight = 0.145;
private const double ImageWidth = 0.086;
private const double TierTop = 0.199;
private const double TierHeight = 0.033;
private const double SaveTop = 0.339;
private const double SaveHeight = 0.045;
private const double SaveWidth = 0.09;
private ConfigData config;
private readonly Dictionary<string, string> prefabCategory = new Dictionary<string, string>();
private readonly Dictionary<string, List<string>> categoryPrefabs = new Dictionary<string, List<string>>();
private readonly Dictionary<ulong, float> originalMaxHealth = new Dictionary<ulong, float>();
private readonly HashSet<string> multiplierApplied = new HashSet<string>();
private readonly Dictionary<string, string> imageIds = new Dictionary<string, string>();
private readonly Dictionary<ulong, int> playerPage = new Dictionary<ulong, int>();
private readonly Dictionary<ulong, Dictionary<string, string>> inputValues = new Dictionary<ulong, Dictionary<string, string>>();
private Coroutine imageRoutine;
private int statChanged;
private int statNoPermission;
private int statNoTarget;
private int statBlocksScaled;
private int statBlocksBroken;
private int statRestored;
#endregion
#region Category tables
private static readonly string[] BuildingBlocks = {
"foundation", "foundation.triangle", "wall", "wall.frame", "wall.window", "wall.doorway", "floor", "floor.frame", "floor.triangle", "floor.triangle.frame", "roof", "roof.triangle", "wall.half", "wall.low", "ramp", "block.stair.ushape", "block.stair.lshape", "foundation.steps", "block.stair.spiral.triangle", "block.stair.spiral"
}
;
private static readonly Dictionary<string, int> DoorDefaults = new Dictionary<string, int> {
{
"door.hinged.wood", 200
}
, {
"door.hinged.metal", 250
}
, {
"door.hinged.toptier", 1000
}
, {
"door.double.hinged.wood", 200
}
, {
"door.double.hinged.metal", 250
}
, {
"door.double.hinged.toptier", 1000
}
, {
"wall.frame.garagedoor", 600
}
, {
"door.hinged.industrial.d", 250
}
, {
"gates.external.high.wood", 500
}
, {
"gates.external.high.stone", 500
}
, {
"shutter.wood.a", 200
}
, {
"floor.ladder.hatch", 250
}
, {
"floor.ladder.hatch.toptier", 1000
}
, {
"wall.frame.cell.gate", 300
}
, {
"floor.triangle.ladder.hatch", 250
}
, {
"floor.triangle.ladder.hatch.toptier", 1000
}
, {
"wall.frame.fence.gate", 75
}
, {
"wall.frame.shopfront", 500
}
}
;
private static readonly Dictionary<string, int> ExternalDefaults = new Dictionary<string, int> {
{
"wall.external.high.wood", 500
}
, {
"wall.external.high.stone", 500
}
, {
"wall.external.high.ice", 500
}
, {
"icewall", 200
}
}
;
private static readonly Dictionary<string, int> BarricadeDefaults = new Dictionary<string, int> {
{
"barricade.woodwire", 400
}
, {
"barricade.concrete", 500
}
, {
"barricade.metal", 600
}
, {
"barricade.sandbags", 200
}
, {
"barricade.stone", 100
}
, {
"barricade.wood", 250
}
, {
"barricade.cover.wood", 120
}
}
;
private static readonly Dictionary<string, int> WindowDefaults = new Dictionary<string, int> {
{
"shutter.metal.embrasure.b", 500
}
, {
"wall.window.bars.metal", 500
}
, {
"shutter.metal.embrasure.a", 500
}
, {
"wall.window.bars.toptier", 500
}
, {
"wall.window.glass.reinforced", 350
}
, {
"wall.window.bars.wood", 250
}
}
;
private static readonly Dictionary<string, int> FrameDefaults = new Dictionary<string, int> {
{
"wall.frame.fence", 100
}
, {
"floor.grill", 250
}
, {
"floor.triangle.grill", 250
}
, {
"wall.frame.shopfront.metal", 750
}
, {
"wall.frame.netting", 100
}
, {
"wall.frame.cell", 300
}
, {
"watchtower.wood", 200
}
, {
"ladder.wooden.wall", 100
}
}
;
private static readonly KeyValuePair<string, string>[] Tabs = {
new KeyValuePair<string, string>("Building", "Building Block"), new KeyValuePair<string, string>("Door", "Door"), new KeyValuePair<string, string>("External", "External Wall"), new KeyValuePair<string, string>("Barricade", "Barricade"), new KeyValuePair<string, string>("Window", "Window"), new KeyValuePair<string, string>("Frame", "Frame")
}
;
private static readonly string[] TierNames = {
"Default", "VIP", "VVIP", "RaidableBase"
}
;
private static readonly Dictionary<string, string> DisplayNames = new Dictionary<string, string> {
{
"foundation", "Foundation"
}
, {
"foundation.triangle", "Triangle Foundation"
}
, {
"wall", "Wall"
}
, {
"wall.frame", "Wall Frame"
}
, {
"wall.window", "Window Wall"
}
, {
"wall.doorway", "Doorway"
}
, {
"floor", "Floor"
}
, {
"floor.frame", "Floor Frame"
}
, {
"floor.triangle", "Triangle Floor"
}
, {
"floor.triangle.frame", "Triangle Floor Frame"
}
, {
"roof", "Roof"
}
, {
"roof.triangle", "Triangle Roof"
}
, {
"wall.half", "Half Wall"
}
, {
"wall.low", "Low Wall"
}
, {
"ramp", "Ramp"
}
, {
"block.stair.ushape", "U Shaped Stairs"
}
, {
"block.stair.lshape", "L Shaped Stairs"
}
, {
"foundation.steps", "Foundation Steps"
}
, {
"block.stair.spiral.triangle", "Triangle Spiral Stairs"
}
, {
"block.stair.spiral", "Spiral Stairs"
}
, {
"door.hinged.wood", "Wooden Door"
}
, {
"door.hinged.metal", "Sheet Metal Door"
}
, {
"door.hinged.toptier", "Armored Door"
}
, {
"door.double.hinged.wood", "Wood Double Door"
}
, {
"door.double.hinged.metal", "Sheet Metal Double Door"
}
, {
"door.double.hinged.toptier", "Armored Double Door"
}
, {
"wall.frame.garagedoor", "Garage Door"
}
, {
"door.hinged.industrial.d", "Industrial Door"
}
, {
"gates.external.high.wood", "High External Wooden Gate"
}
, {
"gates.external.high.stone", "High External Stone Gate"
}
, {
"shutter.wood.a", "Wooden Shutters"
}
, {
"floor.ladder.hatch", "Ladder Hatch"
}
, {
"floor.ladder.hatch.toptier", "Armored Ladder Hatch"
}
, {
"wall.frame.cell.gate", "Prison Cell Gate"
}
, {
"floor.triangle.ladder.hatch", "Triangle Ladder Hatch"
}
, {
"floor.triangle.ladder.hatch.toptier", "Armored Triangle Ladder Hatch"
}
, {
"wall.frame.fence.gate", "Chainlink Fence Gate"
}
, {
"wall.frame.shopfront", "Shop Front"
}
, {
"wall.external.high.wood", "High External Wooden Wall"
}
, {
"wall.external.high.stone", "High External Stone Wall"
}
, {
"wall.external.high.ice", "High External Ice Wall"
}
, {
"icewall", "Ice Wall"
}
, {
"barricade.woodwire", "Barbed Wooden Barricade"
}
, {
"barricade.concrete", "Concrete Barricade"
}
, {
"barricade.metal", "Metal Barricade"
}
, {
"barricade.sandbags", "Sandbag Barricade"
}
, {
"barricade.stone", "Stone Barricade"
}
, {
"barricade.wood", "Wooden Barricade"
}
, {
"barricade.cover.wood", "Wood Barricade Cover"
}
, {
"shutter.metal.embrasure.a", "Metal Horizontal Embrasure"
}
, {
"shutter.metal.embrasure.b", "Metal Vertical Embrasure"
}
, {
"wall.window.bars.metal", "Metal Window Bars"
}
, {
"wall.window.bars.toptier", "Reinforced Window Bars"
}
, {
"wall.window.bars.wood", "Wooden Window Bars"
}
, {
"wall.window.glass.reinforced", "Reinforced Glass Window"
}
, {
"wall.frame.fence", "Chainlink Fence"
}
, {
"floor.grill", "Floor Grill"
}
, {
"floor.triangle.grill", "Triangle Floor Grill"
}
, {
"wall.frame.shopfront.metal", "Metal Shop Front"
}
, {
"wall.frame.netting", "Netting"
}
, {
"wall.frame.cell", "Prison Cell Wall"
}
, {
"watchtower.wood", "Watch Tower"
}
, {
"ladder.wooden.wall", "Wooden Ladder"
}
}
;
private static readonly Dictionary<string, string> BlockImageSlugs = new Dictionary<string, string> {
{
"foundation", "foundation0"
}
, {
"foundation.triangle", "tr-foundation0"
}
, {
"wall", "wall0"
}
, {
"wall.frame", "wall-frame0"
}
, {
"wall.window", "window0"
}
, {
"wall.doorway", "doorway0"
}
, {
"floor", "floor0"
}
, {
"floor.frame", "floor-frame0"
}
, {
"floor.triangle", "tr-floor0"
}
, {
"floor.triangle.frame", "triangle-frame1"
}
, {
"roof", "roof0"
}
, {
"roof.triangle", "tr-roof0"
}
, {
"wall.half", "half-wall0"
}
, {
"wall.low", "low-wall0"
}
, {
"ramp", "ramp1"
}
, {
"block.stair.ushape", "u-stair0"
}
, {
"block.stair.lshape", "l-stair0"
}
, {
"foundation.steps", "stairs0"
}
, {
"block.stair.spiral.triangle", "triangle-spiral1"
}
, {
"block.stair.spiral", "block-spiral1"
}
}
;
private static List<string> DefaultPrefabOrder(string category) {
switch (category) {
case "Building" : return BuildingBlocks.ToList();
case "Door" : return DoorDefaults.Keys.ToList();
case "External" : return ExternalDefaults.Keys.ToList();
case "Barricade" : return BarricadeDefaults.Keys.ToList();
case "Window" : return WindowDefaults.Keys.ToList();
case "Frame" : return FrameDefaults.Keys.ToList();
default : return null;
}
}
private List<string> CategoryPrefabs(string category) {
List<string> prefabs;
return categoryPrefabs.TryGetValue(category, out prefabs) ? prefabs : null;
}
private PermissionSetting<Dictionary<string, int>> CategorySetting(string category) {
switch (category) {
case "Door" : return config.DoorHealth;
case "External" : return config.ExternalWallHealth;
case "Barricade" : return config.BarricadeHealth;
case "Window" : return config.WindowHealth;
case "Frame" : return config.FrameHealth;
default : return null;
}
}
private static Dictionary<string, int> Tier(PermissionSetting<Dictionary<string, int>> setting, int tier) {
if (setting == null) return null;
switch (tier) {
case 0 : return setting.DefaultSettings;
case 1 : return setting.VIPSettings;
case 2 : return setting.VVIPSettings;
case 3 : return setting.RaidableBasesSettings;
default : return null;
}
}
#endregion
#region Configuration
public class ConfigData {
[JsonProperty(PropertyName = "Building Block Health")]
public Dictionary<string, float> BuildingBlockHealth;
[JsonProperty(PropertyName = "Door Health")]
public PermissionSetting<Dictionary<string, int>> DoorHealth;
[JsonProperty(PropertyName = "External Wall Health")]
public PermissionSetting<Dictionary<string, int>> ExternalWallHealth;
[JsonProperty(PropertyName = "Barricade Health")]
public PermissionSetting<Dictionary<string, int>> BarricadeHealth;
[JsonProperty(PropertyName = "Window Health")]
public PermissionSetting<Dictionary<string, int>> WindowHealth;
[JsonProperty(PropertyName = "Frame Health")]
public PermissionSetting<Dictionary<string, int>> FrameHealth;
[JsonProperty(PropertyName = "Image Settings")]
public ImageSettings Images;
[JsonProperty(PropertyName = "Version")]
public string Version;
}
public class PermissionSetting<T> {
[JsonProperty(PropertyName = "Default settings")]
public T DefaultSettings;
[JsonProperty(PropertyName = "VIP settings")]
public T VIPSettings;
[JsonProperty(PropertyName = "VVIP settings")]
public T VVIPSettings;
[JsonProperty(PropertyName = "Raidable Bases settings")]
public T RaidableBasesSettings;
}
public class ImageSettings {
[JsonProperty(PropertyName = "Item icon URL format ({0} = item shortname)")]
public string ItemUrlFormat;
[JsonProperty(PropertyName = "Building block screenshot URL format ({0} = screenshot slug)")]
public string BlockUrlFormat;
[JsonProperty(PropertyName = "Overrides (prefab name -> full URL, or just the item shortname)")]
public Dictionary<string, string> Overrides;
[JsonProperty(PropertyName = "Re-download images already cached on disk")]
public bool ForceRedownload;
[JsonProperty(PropertyName = "Seconds to wait between downloads")]
public float DownloadDelay;
[JsonProperty(PropertyName = "Download timeout (seconds)")]
public int Timeout;
}
private ConfigData GetDefaultConfig() {
return new ConfigData {
BuildingBlockHealth = BuildingBlocks.ToDictionary(name => name, name => 100f), DoorHealth = NewPermissionSetting(DoorDefaults), ExternalWallHealth = NewPermissionSetting(ExternalDefaults), BarricadeHealth = NewPermissionSetting(BarricadeDefaults), WindowHealth = NewPermissionSetting(WindowDefaults), FrameHealth = NewPermissionSetting(FrameDefaults), Images = GetDefaultImageSettings(), Version = ConfigVersion
}
;
}
private static ImageSettings GetDefaultImageSettings() {
return new ImageSettings {
ItemUrlFormat = "https://wiki.rustclash.com/img/items180/{0}.png", BlockUrlFormat = "https://wiki.rustclash.com/img/screenshots/{0}.png", Overrides = new Dictionary<string, string> {
{
"wall.external.high.wood", "wall.external.high"
}
, {
"icewall", "wall.ice.wall"
}
, {
"barricade.cover.wood", "barricade.wood.cover"
}
, {
"door.hinged.industrial.d", "https://wiki.rustclash.com/img/skins/324/10189.png"
}
}
, ForceRedownload = false, DownloadDelay = 0.15f, Timeout = 20
}
;
}
private static PermissionSetting<Dictionary<string, int>> NewPermissionSetting(Dictionary<string, int> defaults) {
return new PermissionSetting<Dictionary<string, int>> {
DefaultSettings = new Dictionary<string, int>(defaults), VIPSettings = new Dictionary<string, int>(defaults), VVIPSettings = new Dictionary<string, int>(defaults), RaidableBasesSettings = new Dictionary<string, int>(defaults)
}
;
}
protected override void LoadConfig() {
base.LoadConfig();
var defaults = GetDefaultConfig();
try {
config = Config.ReadObject<ConfigData>();
}
catch (Exception ex) {
PrintError("Could not read the configuration file: {0}", ex.Message);
config = null;
}
if (config == null) {
PrintWarning("Creating a new configuration file");
config = defaults;
}
else {
if (config.Version != ConfigVersion) {
PrintWarning("Configuration file is version {0}, updating it to {1}...", string.IsNullOrEmpty(config.Version) ? "unknown" : config.Version, ConfigVersion);
}
MergeConfig(config, defaults);
}
SaveConfig();
}
protected override void LoadDefaultConfig() {
config = GetDefaultConfig();
}
protected override void SaveConfig() {
Config.WriteObject(config);
}
private static void MergeConfig(ConfigData current, ConfigData defaults) {
current.BuildingBlockHealth = Merge(current.BuildingBlockHealth, defaults.BuildingBlockHealth);
current.DoorHealth = Merge(current.DoorHealth, defaults.DoorHealth);
current.ExternalWallHealth = Merge(current.ExternalWallHealth, defaults.ExternalWallHealth);
current.BarricadeHealth = Merge(current.BarricadeHealth, defaults.BarricadeHealth);
current.WindowHealth = Merge(current.WindowHealth, defaults.WindowHealth);
current.FrameHealth = Merge(current.FrameHealth, defaults.FrameHealth);
current.Images = MergeImages(current.Images, defaults.Images);
current.Version = defaults.Version;
}
private static ImageSettings MergeImages(ImageSettings current, ImageSettings defaults) {
if (current == null) return defaults;
if (string.IsNullOrEmpty(current.ItemUrlFormat)) current.ItemUrlFormat = defaults.ItemUrlFormat;
if (string.IsNullOrEmpty(current.BlockUrlFormat)) current.BlockUrlFormat = defaults.BlockUrlFormat;
if (current.Overrides == null) current.Overrides = defaults.Overrides;
if (current.DownloadDelay <= 0f) current.DownloadDelay = defaults.DownloadDelay;
if (current.Timeout <= 0) current.Timeout = defaults.Timeout;
return current;
}
private static Dictionary<string, TValue> Merge<TValue>(Dictionary<string, TValue> current, Dictionary<string, TValue> defaults) {
var merged = new Dictionary<string, TValue>(defaults);
if (current == null) return merged;
foreach (var entry in current) {
merged[entry.Key] = entry.Value;
}
return merged;
}
private static PermissionSetting<Dictionary<string, int>> Merge(PermissionSetting<Dictionary<string, int>> current, PermissionSetting<Dictionary<string, int>> defaults) {
if (current == null) return defaults;
current.DefaultSettings = Merge(current.DefaultSettings, defaults.DefaultSettings);
current.VIPSettings = Merge(current.VIPSettings, defaults.VIPSettings);
current.VVIPSettings = Merge(current.VVIPSettings, defaults.VVIPSettings);
current.RaidableBasesSettings = Merge(current.RaidableBasesSettings, defaults.RaidableBasesSettings);
return current;
}
#endregion
#region Hooks
private void Init() {
permission.RegisterPermission(PermDefault, this);
permission.RegisterPermission(PermVIP, this);
permission.RegisterPermission(PermVVIP, this);
permission.RegisterPermission(PermAdmin, this);
BuildPrefabIndex();
}
private void OnServerInitialized() {
ApplyAll(true);
LoadImages(config.Images.ForceRedownload);
}
private void Unload() {
StopImageRoutine();
ApplyAll(false);
foreach (var player in BasePlayer.activePlayerList) {
CuiHelper.DestroyUi(player, PanelName);
}
}
private void OnPlayerDisconnected(BasePlayer player) {
if (player == null) return;
playerPage.Remove(player.userID);
inputValues.Remove(player.userID);
}
private void OnEntityBuilt(Planner plan, GameObject go) {
var block = go == null ? null : go.ToBaseEntity() as BuildingBlock;
if (block == null || !block.IsValid()) return;
ApplySingleBlock(block, true);
}
private void OnEntitySpawned(BaseNetworkable networkable) {
var entity = networkable as BaseCombatEntity;
if (entity == null || entity is BuildingBlock) return;
string category;
if (!prefabCategory.TryGetValue(entity.ShortPrefabName, out category)) return;
ApplyEntityHealth(entity, category);
}
private void OnEntityKill(BaseNetworkable networkable) {
var entity = networkable as BaseCombatEntity;
if (entity == null || entity.net == null) return;
originalMaxHealth.Remove(entity.net.ID.Value);
}
#endregion
#region Health application
private void BuildPrefabIndex() {
prefabCategory.Clear();
categoryPrefabs.Clear();
foreach (var tab in Tabs) {
var prefabs = DefaultPrefabOrder(tab.Key);
categoryPrefabs[tab.Key] = prefabs;
if (tab.Key == "Building") continue;
foreach (var prefab in prefabs) {
prefabCategory[prefab] = tab.Key;
}
}
}
private void ApplyAll(bool load) {
multiplierApplied.Clear();
statChanged = 0;
statNoPermission = 0;
statNoTarget = 0;
statBlocksScaled = 0;
statBlocksBroken = 0;
statRestored = 0;
var blocks = new List<BuildingBlock>();
foreach (var networkable in BaseNetworkable.serverEntities) {
var entity = networkable as BaseCombatEntity;
if (entity == null || entity.IsDestroyed) continue;
var block = entity as BuildingBlock;
if (block != null) {
blocks.Add(block);
continue;
}
if (!load) {
RestoreEntityHealth(entity);
continue;
}
string category;
if (!prefabCategory.TryGetValue(entity.ShortPrefabName, out category)) continue;
ApplyEntityHealth(entity, category);
}
ApplyBlocks(blocks, load);
if (!load) {
Puts("Restored {0} structure(s) to their original health.", statRestored);
return;
}
Puts("Health applied to {0} structure(s) and {1} building block(s).", statChanged, statBlocksScaled);
if (statNoPermission > 0) {
PrintWarning("{0} structure(s) were left at vanilla health because their owner holds none of {1} / {2} / {3}. Grant one, e.g.: oxide.grant group default {1}", statNoPermission, PermDefault, PermVIP, PermVVIP);
}
if (statBlocksBroken > 0) {
PrintWarning("{0} building block(s) have no usable grade definition and were left untouched.", statBlocksBroken);
}
}
private void ApplyBlocks(List<BuildingBlock> blocks, bool load) {
var scales = new Dictionary<string, float>();
foreach (var block in blocks) {
if (!IsConfiguredBlock(block)) continue;
string prefab = block.ShortPrefabName;
if (scales.ContainsKey(prefab)) continue;
scales[prefab] = ApplyBlockMultiplier(block, load);
}
foreach (var block in blocks) {
if (!IsConfiguredBlock(block)) continue;
float scale;
if (!scales.TryGetValue(block.ShortPrefabName, out scale)) continue;
ScaleBlockHealth(block, scale);
}
}
private void ScaleBlockHealth(BuildingBlock block, float scale) {
try {
if (Math.Abs(scale - 1f) > 0.0001f) block.health *= scale;
block.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
statBlocksScaled++;
}
catch (Exception) {
statBlocksBroken++;
}
}
private float ApplyBlockMultiplier(BuildingBlock block, bool load) {
if (!IsConfiguredBlock(block)) return 1f;
string prefab = block.ShortPrefabName;
if (load && !multiplierApplied.Add(prefab)) return 1f;
if (!load) multiplierApplied.Remove(prefab);
var definition = block.blockDefinition;
float previous = definition.healthMultiplier;
float current = load ? config.BuildingBlockHealth[prefab] / 100f : 1f;
definition.healthMultiplier = current;
return previous > 0f ? current / previous : 1f;
}
private void ApplySingleBlock(BuildingBlock block, bool load) {
if (!IsConfiguredBlock(block)) return;
ScaleBlockHealth(block, ApplyBlockMultiplier(block, load));
}
private bool IsConfiguredBlock(BuildingBlock block) {
if (block == null || block.IsDestroyed || block.net == null) return false;
if (block.blockDefinition == null) return false;
float percent;
return config.BuildingBlockHealth.TryGetValue(block.ShortPrefabName, out percent) && percent > 0f;
}
private void ApplyEntityHealth(BaseCombatEntity entity, string category) {
if (entity == null || entity.IsDestroyed || entity.net == null) return;
var setting = CategorySetting(category);
if (setting == null) return;
Dictionary<string, int> tier;
if (entity.OwnerID != 0) {
tier = TierForOwner(setting, entity.OwnerID);
if (tier == null) {
statNoPermission++;
return;
}
}
else {
if (!IsRaidableTerritory(entity)) return;
tier = setting.RaidableBasesSettings;
}
int target;
if (!tier.TryGetValue(entity.ShortPrefabName, out target) || target <= 0) {
statNoTarget++;
return;
}
ulong id = entity.net.ID.Value;
float before = entity._maxHealth;
if (!SetMaxHealth(entity, target)) return;
statChanged++;
if (!originalMaxHealth.ContainsKey(id)) originalMaxHealth[id] = before;
}
private void RestoreEntityHealth(BaseCombatEntity entity) {
if (entity == null || entity.net == null) return;
ulong id = entity.net.ID.Value;
float original;
if (!originalMaxHealth.TryGetValue(id, out original)) return;
SetMaxHealth(entity, original);
originalMaxHealth.Remove(id);
statRestored++;
}
private Dictionary<string, int> TierForOwner(PermissionSetting<Dictionary<string, int>> setting, ulong ownerId) {
string owner = ownerId.ToString();
if (permission.UserHasPermission(owner, PermVVIP)) return setting.VVIPSettings;
if (permission.UserHasPermission(owner, PermVIP)) return setting.VIPSettings;
if (permission.UserHasPermission(owner, PermDefault)) return setting.DefaultSettings;
return null;
}
private static bool SetMaxHealth(BaseCombatEntity entity, float max) {
if (max <= 0f) return false;
float previous = entity._maxHealth;
if (Math.Abs(previous - max) < 0.01f) {
if (entity.health <= max + 0.01f) return false;
entity.health = max;
entity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
return true;
}
float ratio = previous > 0f ? Mathf.Clamp01(entity.health / previous) : 1f;
entity._maxHealth = max;
entity.health = ratio * max;
entity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
return true;
}
private bool IsRaidableTerritory(BaseEntity entity) {
if (RaidableBases == null || entity == null || entity.transform == null) return false;
var result = RaidableBases.Call("EventTerritory", entity.transform.position);
return result is bool && (bool)result;
}
#endregion
#region Images
private string ImageFolder {
get {
return Path.Combine(Interface.Oxide.DataDirectory, "BuildingHealthManager", "images");
}
}
private string ImageUrl(string prefab) {
string over;
if (config.Images.Overrides.TryGetValue(prefab, out over) && !string.IsNullOrEmpty(over)) {
return over.StartsWith("http") ? over : string.Format(config.Images.ItemUrlFormat, over);
}
string slug;
if (BlockImageSlugs.TryGetValue(prefab, out slug)) {
return string.Format(config.Images.BlockUrlFormat, slug);
}
return string.Format(config.Images.ItemUrlFormat, prefab);
}
private void LoadImages(bool force) {
StopImageRoutine();
if (ServerMgr.Instance == null) return;
imageRoutine = ServerMgr.Instance.StartCoroutine(ImageRoutine(force));
}
private void StopImageRoutine() {
if (imageRoutine == null || ServerMgr.Instance == null) return;
ServerMgr.Instance.StopCoroutine(imageRoutine);
imageRoutine = null;
}
private IEnumerator ImageRoutine(bool force) {
string folder = ImageFolder;
if (!EnsureFolder(folder)) {
imageRoutine = null;
yield break;
}
var wanted = new List<string>();
foreach (var tab in Tabs) {
foreach (var prefab in CategoryPrefabs(tab.Key)) {
if (!wanted.Contains(prefab)) wanted.Add(prefab);
}
}
int cached = 0, downloaded = 0, failed = 0;
var wait = new WaitForSeconds(config.Images.DownloadDelay);
foreach (var prefab in wanted) {
string path = Path.Combine(folder, prefab + ".png");
byte[] bytes = force ? null : ReadFile(path);
if (bytes == null) {
string url = ImageUrl(prefab);
var request = UnityWebRequest.Get(url);
request.timeout = config.Images.Timeout;
yield return request.SendWebRequest();
bytes = ResponseBytes(request, prefab, url);
request.Dispose();
if (bytes == null) {
failed++;
yield return wait;
continue;
}
WriteFile(path, bytes);
downloaded++;
yield return wait;
}
else {
cached++;
}
StoreImage(prefab, bytes);
}
imageRoutine = null;
Puts("Images ready: {0} from cache, {1} downloaded, {2} failed (cache: {3})", cached, downloaded, failed, folder);
if (failed > 0) {
PrintWarning("Some images could not be downloaded. Check 'Image Settings' in the config file if the wiki changed its URLs.");
}
}
private byte[] ResponseBytes(UnityWebRequest request, string prefab, string url) {
if (!string.IsNullOrEmpty(request.error)) {
PrintWarning("Image download failed [{0}]: {1} ({2})", prefab, request.error, url);
return null;
}
var data = request.downloadHandler == null ? null : request.downloadHandler.data;
if (data == null || data.Length == 0) {
PrintWarning("Image download was empty [{0}]: {1}", prefab, url);
return null;
}
return data;
}
private void StoreImage(string prefab, byte[] bytes) {
if (CommunityEntity.ServerInstance == null || CommunityEntity.ServerInstance.net == null) return;
imageIds[prefab] = FileStorage.server.Store(bytes, FileStorage.Type.png, CommunityEntity.ServerInstance.net.ID).ToString();
}
private bool EnsureFolder(string folder) {
try {
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
return true;
}
catch (Exception ex) {
PrintError("Could not create the image folder '{0}': {1}", folder, ex.Message);
return false;
}
}
private byte[] ReadFile(string path) {
try {
return File.Exists(path) ? File.ReadAllBytes(path) : null;
}
catch (Exception ex) {
PrintWarning("Could not read cached image '{0}': {1}", path, ex.Message);
return null;
}
}
private void WriteFile(string path, byte[] bytes) {
try {
File.WriteAllBytes(path, bytes);
}
catch (Exception ex) {
PrintWarning("Could not cache image '{0}': {1}", path, ex.Message);
}
}
#endregion
#region CUI
private static string Anchor(double x, double y) {
return x.ToString("0.####", CultureInfo.InvariantCulture) + " " + y.ToString("0.####", CultureInfo.InvariantCulture);
}
private void OpenPanel(BasePlayer player, string activeCategory) {
var container = new CuiElementContainer();
var panel = container.Add(new CuiPanel {
Image = {
Color = "0.07 0.07 0.08 0.99"
}
, RectTransform = {
AnchorMin = "0.13 0.11", AnchorMax = "0.87 0.89"
}
, CursorEnabled = true
}
, "Overlay", PanelName);
container.Add(new CuiLabel {
Text = {
Text = "Building Health Manager", Align = TextAnchor.MiddleLeft, FontSize = 22, Color = "0.92 0.92 0.92 1"
}
, RectTransform = {
AnchorMin = "0.02 0.9", AnchorMax = "0.7 0.98"
}
}
, panel);
container.Add(new CuiPanel {
Image = {
Color = "0.35 0.55 0.35 0.9"
}
, RectTransform = {
AnchorMin = "0.02 0.885", AnchorMax = "0.98 0.888"
}
}
, panel);
container.Add(new CuiButton {
Button = {
Command = "bhc", Color = "0.62 0.22 0.22 0.95"
}
, RectTransform = {
AnchorMin = "0.94 0.905", AnchorMax = "0.98 0.972"
}
, Text = {
Text = "X", FontSize = 18, Align = TextAnchor.MiddleCenter, Color = "0.95 0.9 0.9 1"
}
}
, panel);
container.Add(new CuiLabel {
Text = {
Text = "Save, then reload the plugin to apply", Align = TextAnchor.MiddleLeft, FontSize = 11, Color = "0.6 0.6 0.62 1"
}
, RectTransform = {
AnchorMin = "0.02 0.83", AnchorMax = "0.22 0.868"
}
}
, panel);
for (int i = 0; i < Tabs.Length; i++) {
bool active = Tabs[i].Key == activeCategory;
container.Add(new CuiButton {
Button = {
Command = string.Format("bh.showdetails {0} 0", Tabs[i].Key), Color = active ? "0.35 0.55 0.35 0.95" : "0.17 0.17 0.19 0.9"
}
, RectTransform = {
AnchorMin = Anchor(MenuLeft, MenuBottom - i * MenuPitch), AnchorMax = Anchor(MenuRight, MenuBottom + MenuHeight - i * MenuPitch)
}
, Text = {
Text = Tabs[i].Value, FontSize = 14, Align = TextAnchor.MiddleCenter, Color = active ? "1 1 1 1" : "0.75 0.75 0.78 1"
}
}
, panel);
}
CuiHelper.AddUi(player, container);
}
private void ShowCategory(BasePlayer player, string category, int page) {
var prefabs = CategoryPrefabs(category);
if (prefabs == null) return;
if (page < 0) page = 0;
int lastPage = Math.Max(0, (prefabs.Count - 1) / PerPage);
if (page > lastPage) page = lastPage;
var container = new CuiElementContainer();
int startIndex = page * PerPage;
for (int i = 0; i < PerPage && startIndex + i < prefabs.Count; i++) {
AddTile(container, category, prefabs[startIndex + i], page, i);
}
container.Add(new CuiButton {
Button = {
Command = page > 0 ? string.Format("bh.showdetails {0} {1}", category, page - 1) : string.Empty, Color = page > 0 ? "0.2 0.2 0.22 0.95" : "0.13 0.13 0.14 0.6"
}
, RectTransform = {
AnchorMin = "0.02 0.035", AnchorMax = "0.075 0.095"
}
, Text = {
Text = "<", FontSize = 16, Align = TextAnchor.MiddleCenter, Color = page > 0 ? "0.9 0.9 0.9 1" : "0.4 0.4 0.4 1"
}
}
, PanelName);
container.Add(new CuiLabel {
Text = {
Text = string.Format("{0} / {1}", page + 1, lastPage + 1), Align = TextAnchor.MiddleCenter, FontSize = 12, Color = "0.7 0.7 0.72 1"
}
, RectTransform = {
AnchorMin = "0.075 0.035", AnchorMax = "0.145 0.095"
}
}
, PanelName);
container.Add(new CuiButton {
Button = {
Command = page < lastPage ? string.Format("bh.showdetails {0} {1}", category, page + 1) : string.Empty, Color = page < lastPage ? "0.2 0.2 0.22 0.95" : "0.13 0.13 0.14 0.6"
}
, RectTransform = {
AnchorMin = "0.145 0.035", AnchorMax = "0.2 0.095"
}
, Text = {
Text = ">", FontSize = 16, Align = TextAnchor.MiddleCenter, Color = page < lastPage ? "0.9 0.9 0.9 1" : "0.4 0.4 0.4 1"
}
}
, PanelName);
CuiHelper.AddUi(player, container);
}
private void AddTile(CuiElementContainer container, string category, string prefab, int page, int slot) {
int row = slot / Columns;
int column = slot % Columns;
double left = ContentLeft + column * ColPitch;
double right = left + TileWidth;
double top = RowTop - row * RowPitch;
container.Add(new CuiPanel {
Image = {
Color = "0.145 0.145 0.165 0.9"
}
, RectTransform = {
AnchorMin = Anchor(left, top - TileHeight), AnchorMax = Anchor(right, top)
}
}
, PanelName);
container.Add(new CuiLabel {
Text = {
Text = DisplayName(prefab), Align = TextAnchor.MiddleCenter, FontSize = 11, Color = "0.95 0.95 0.95 1"
}
, RectTransform = {
AnchorMin = Anchor(left + 0.004, top - NameHeight), AnchorMax = Anchor(right - 0.004, top)
}
}
, PanelName);
string imageId;
if (imageIds.TryGetValue(prefab, out imageId)) {
double imageLeft = left + (TileWidth - ImageWidth) / 2.0;
container.Add(new CuiElement {
Parent = PanelName, Components = {
new CuiRawImageComponent {
Png = imageId, Sprite = "assets/content/textures/generic/fulltransparent.tga"
}
, new CuiRectTransformComponent {
AnchorMin = Anchor(imageLeft, top - ImageTop - ImageHeight), AnchorMax = Anchor(imageLeft + ImageWidth, top - ImageTop)
}
}
}
);
}
AddTierInputs(container, category, prefab, page, slot, left, right, top);
double saveLeft = left + (TileWidth - SaveWidth) / 2.0;
container.Add(new CuiButton {
Button = {
Command = stri
// ... [Code preview truncated due to length. Full source in attached file] ...