C#:
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Newtonsoft.Json;
using System.IO;
namespace Oxide.Plugins
{
[Info("ChtoZaPrefab", "Semnavmeleon", "1.2.0")]
[Description("Entity inspector with ESP visualization (RU/EN)")]
public class ChtoZaPrefab : RustPlugin
{
#region Configuration
private Configuration _config;
private Timer _updatePlayersTimer;
private Timer _updateRaysTimer;
public class Configuration
{
[JsonProperty("Permission to use")]
public string Permission { get; set; } = "chtozaprefab.use";
[JsonProperty("Auto-enable for admins")]
public bool AutoEnableForAdmins { get; set; } = false;
[JsonProperty("Max distance (m)")]
public float MaxDistance { get; set; } = 100f;
[JsonProperty("Enable ESP highlighting")]
public bool EnableESP { get; set; } = true;
[JsonProperty("ESP color")]
public string ESPColor { get; set; } = "1 0 0 1";
[JsonProperty("ESP line thickness")]
public float ESPThickness { get; set; } = 0.07f;
[JsonProperty("Draw look ray")]
public bool DrawLookRay { get; set; } = true;
[JsonProperty("Ray color")]
public string RayColor { get; set; } = "0 1 1 0.8";
[JsonProperty("Ray thickness")]
public float RayThickness { get; set; } = 0.015f;
[JsonProperty("Highlight target prefab")]
public bool HighlightPrefab { get; set; } = true;
[JsonProperty("Highlight color")]
public string HighlightColor { get; set; } = "1 0 0.5 1";
[JsonProperty("Highlight line thickness")]
public float HighlightLineThickness { get; set; } = 0.03f;
[JsonProperty("Highlight pulse period (seconds)")]
public float HighlightPulsePeriod { get; set; } = 2.5f;
[JsonProperty("Highlight draw duration (seconds)")]
public float HighlightDrawDuration { get; set; } = 0.25f;
[JsonProperty("Fixed box size")]
public float FixedBoxSize { get; set; } = 1.5f;
[JsonProperty("Update moving objects")]
public bool UpdateMovingObjects { get; set; } = true;
[JsonProperty("Precise framing threshold (m)")]
public float PreciseFramingThreshold { get; set; } = 3f;
[JsonProperty("Simplify distance (m)")]
public float SimplifyDistance { get; set; } = 40f;
[JsonProperty("Text color")]
public string TextColor { get; set; } = "1 1 1 1";
[JsonProperty("Header color")]
public string HeaderColor { get; set; } = "1 1 0 1";
[JsonProperty("Basic properties color")]
public string BasicColor { get; set; } = "1 1 1 1";
[JsonProperty("Entity properties color")]
public string EntityColor { get; set; } = "0 1 1 1";
[JsonProperty("Player properties color")]
public string PlayerColor { get; set; } = "0 1 0 1";
[JsonProperty("Building properties color")]
public string BuildingColor { get; set; } = "1 0.5 0 1";
[JsonProperty("Technical properties color")]
public string TechnicalColor { get; set; } = "1 1 1 1";
[JsonProperty("Font size")]
public int FontSize { get; set; } = 13;
[JsonProperty("Position X (pixels)")]
public int PositionX { get; set; } = 25;
[JsonProperty("Position Y (pixels)")]
public int PositionY { get; set; } = 0;
[JsonProperty("UI background color")]
public string UIColor { get; set; } = "0.15 0.15 0.2 1";
[JsonProperty("UI opacity")]
public float UIOpacity { get; set; } = 0.88f;
[JsonProperty("Default language")]
public string DefaultLanguage { get; set; } = "auto";
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null) throw new Exception();
if (_config.PositionY == 0) _config.PositionY = 140;
}
catch
{
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig()
{
_config = new Configuration();
}
protected override void SaveConfig()
{
Config.WriteObject(_config, true);
}
#endregion
#region Localization
private enum Language { Russian, English, Auto }
private class Localization
{
public Language Lang { get; private set; }
private Dictionary<string, string> _strings = new Dictionary<string, string>();
public Localization(Language lang)
{
Lang = lang;
LoadTranslations();
}
private void LoadTranslations()
{
_strings.Clear();
Language effectiveLang = (Lang == Language.Auto) ? Language.English : Lang;
if (effectiveLang == Language.Russian)
{
_strings["panel_no_target"] = "Нет цели";
_strings["panel_target_static"] = "Цель: Статический объект";
_strings["panel_target_player"] = "Игрок";
_strings["panel_distance"] = "Дистанция";
_strings["panel_distance_unit"] = "м";
_strings["panel_prefab_id"] = "ID префаба";
_strings["panel_prefab"] = "Префаб";
_strings["panel_owner_id"] = "ID владельца";
_strings["panel_owner_name"] = "Владелец";
_strings["panel_position"] = "Позиция";
_strings["panel_rotation"] = "Поворот";
_strings["panel_collider"] = "Коллайдер";
_strings["panel_object"] = "Объект";
_strings["panel_health"] = "Здоровье";
_strings["panel_steam_id"] = "Steam ID";
_strings["panel_display_name"] = "Имя";
_strings["panel_ip"] = "IP";
_strings["panel_admin"] = "Админ";
_strings["panel_sleeping"] = "Спит";
_strings["panel_team"] = "Команда";
_strings["panel_grade"] = "Уровень";
_strings["panel_online"] = "Онлайн";
_strings["panel_authorized"] = "Авторизовано";
_strings["panel_authorized_suffix"] = " игроков";
_strings["panel_driver"] = "Водитель";
_strings["panel_items"] = "Предметы";
_strings["panel_loot_items"] = "Лут";
_strings["panel_door_password"] = "Пароль";
_strings["panel_door_keylock"] = "Замок (ключ)";
_strings["panel_button_on"] = "ВКЛ";
_strings["panel_button_off"] = "ВЫКЛ";
_strings["cmd_toggle_on"] = "Плагин включен";
_strings["cmd_toggle_off"] = "Плагин выключен";
_strings["cmd_panel_opened"] = "Панель открыта";
_strings["cmd_panel_closed"] = "Панель закрыта";
_strings["cmd_no_permission"] = "У вас нет доступа к этой команде";
_strings["cmd_lang_set_ru"] = "Язык изменён на Русский";
_strings["cmd_lang_set_en"] = "Язык изменён на English";
_strings["cmd_lang_auto"] = "Язык установлен автоматически: {0}";
_strings["lang_russian"] = "Русский";
_strings["lang_english"] = "English";
_strings["esp_triangle"] = "Треугольный ESP";
_strings["esp_complex_box"] = "Сложный бокс";
_strings["section_entity"] = "Сущность";
_strings["section_transform"] = "Трансформ";
_strings["section_owner"] = "Владелец";
_strings["section_health"] = "Здоровье";
_strings["section_player"] = "Игрок";
_strings["section_building"] = "Строительство";
_strings["section_turret"] = "Турель";
_strings["section_vehicle"] = "Транспорт";
_strings["section_inventory"] = "Инвентарь";
_strings["section_loot"] = "Лут";
_strings["section_door_lock"] = "Дверь / Замок";
_strings["section_components"] = "Компоненты";
_strings["section_bounds"] = "Границы";
_strings["section_identity"] = "Идентификация";
_strings["section_static_object"] = "Статический объект";
_strings["label_distance"] = "Дистанция";
_strings["label_prefab_id"] = "ID префаба";
_strings["label_full_path"] = "Полный путь";
_strings["label_short_name"] = "Краткое имя";
_strings["label_net_id"] = "Net ID";
_strings["label_destroyed"] = "Уничтожен";
_strings["label_position"] = "Позиция";
_strings["label_rotation_euler"] = "Поворот (эйлер)";
_strings["label_local_position"] = "Лок. позиция";
_strings["label_local_rotation"] = "Лок. поворот";
_strings["label_local_scale"] = "Лок. масштаб";
_strings["label_colliders"] = "Коллайдеры";
_strings["label_renderers"] = "Рендереры";
_strings["label_rigidbody"] = "Rigidbody";
_strings["label_steam_id"] = "Steam ID";
_strings["label_display_name"] = "Имя";
_strings["label_online"] = "Онлайн";
_strings["label_current"] = "Текущее";
_strings["label_max"] = "Макс.";
_strings["label_building_id"] = "ID постройки";
_strings["label_modules"] = "Модули";
_strings["label_contents"] = "Содержимое";
_strings["label_open"] = "Открыто";
_strings["label_object_name"] = "Имя объекта";
_strings["label_center"] = "Центр";
_strings["label_size"] = "Размер";
_strings["label_colliders_total"] = "Коллайдеров всего";
_strings["label_yes"] = "Да";
_strings["label_no"] = "Нет";
_strings["target_unknown"] = "Цель: Неизвестный объект";
_strings["target_none"] = "Цель: Нет";
_strings["player_not_active"] = "Игрок не активен";
_strings["target_hit_point"] = "Цель: Точка попадания";
_strings["target_type_player"] = "Игрок";
_strings["target_type_building_block"] = "Строительный блок";
_strings["target_type_turret"] = "Турель";
_strings["target_type_vehicle"] = "Транспорт";
_strings["target_type_container"] = "Контейнер";
_strings["target_type_loot_container"] = "Контейнер с лутом";
_strings["target_type_combat_entity"] = "Боевая сущность";
_strings["target_type_entity"] = "Сущность";
_strings["target_type_object"] = "Объект";
}
else
{
_strings["panel_no_target"] = "No target";
_strings["panel_target_static"] = "Target: Static Object";
_strings["panel_target_player"] = "Player";
_strings["panel_distance"] = "Distance";
_strings["panel_distance_unit"] = "m";
_strings["panel_prefab_id"] = "Prefab ID";
_strings["panel_prefab"] = "Prefab";
_strings["panel_owner_id"] = "Owner ID";
_strings["panel_owner_name"] = "Owner";
_strings["panel_position"] = "Position";
_strings["panel_rotation"] = "Rotation";
_strings["panel_collider"] = "Collider";
_strings["panel_object"] = "Object";
_strings["panel_health"] = "Health";
_strings["panel_steam_id"] = "Steam ID";
_strings["panel_display_name"] = "Display Name";
_strings["panel_ip"] = "IP";
_strings["panel_admin"] = "Admin";
_strings["panel_sleeping"] = "Sleeping";
_strings["panel_team"] = "Team";
_strings["panel_grade"] = "Grade";
_strings["panel_online"] = "Online";
_strings["panel_authorized"] = "Authorized";
_strings["panel_authorized_suffix"] = " players";
_strings["panel_driver"] = "Driver";
_strings["panel_items"] = "Items";
_strings["panel_loot_items"] = "Loot Items";
_strings["panel_door_password"] = "Password";
_strings["panel_door_keylock"] = "Key lock";
_strings["panel_button_on"] = "ON";
_strings["panel_button_off"] = "OFF";
_strings["cmd_toggle_on"] = "Plugin enabled";
_strings["cmd_toggle_off"] = "Plugin disabled";
_strings["cmd_panel_opened"] = "Panel opened";
_strings["cmd_panel_closed"] = "Panel closed";
_strings["cmd_no_permission"] = "You don't have permission for this command";
_strings["cmd_lang_set_ru"] = "Language changed to Русский";
_strings["cmd_lang_set_en"] = "Language changed to English";
_strings["cmd_lang_auto"] = "Language set automatically: {0}";
_strings["lang_russian"] = "Русский";
_strings["lang_english"] = "English";
_strings["esp_triangle"] = "Triangle ESP";
_strings["esp_complex_box"] = "Complex Box";
_strings["section_entity"] = "Entity";
_strings["section_transform"] = "Transform";
_strings["section_owner"] = "Owner";
_strings["section_health"] = "Health";
_strings["section_player"] = "Player";
_strings["section_building"] = "Building";
_strings["section_turret"] = "Turret";
_strings["section_vehicle"] = "Vehicle";
_strings["section_inventory"] = "Inventory";
_strings["section_loot"] = "Loot";
_strings["section_door_lock"] = "Door / Lock";
_strings["section_components"] = "Components";
_strings["section_bounds"] = "Bounds";
_strings["section_identity"] = "Identity";
_strings["section_static_object"] = "Static Object";
_strings["label_distance"] = "Distance";
_strings["label_prefab_id"] = "Prefab ID";
_strings["label_full_path"] = "Full path";
_strings["label_short_name"] = "Short name";
_strings["label_net_id"] = "Net ID";
_strings["label_destroyed"] = "Destroyed";
_strings["label_position"] = "Position";
_strings["label_rotation_euler"] = "Rotation (euler)";
_strings["label_local_position"] = "Local position";
_strings["label_local_rotation"] = "Local rotation";
_strings["label_local_scale"] = "Local scale";
_strings["label_colliders"] = "Colliders";
_strings["label_renderers"] = "Renderers";
_strings["label_rigidbody"] = "Rigidbody";
_strings["label_steam_id"] = "Steam ID";
_strings["label_display_name"] = "Display name";
_strings["label_online"] = "Online";
_strings["label_current"] = "Current";
_strings["label_max"] = "Max";
_strings["label_building_id"] = "Building ID";
_strings["label_modules"] = "Modules";
_strings["label_contents"] = "Contents";
_strings["label_open"] = "Open";
_strings["label_object_name"] = "Object name";
_strings["label_center"] = "Center";
_strings["label_size"] = "Size";
_strings["label_colliders_total"] = "Colliders (total)";
_strings["label_yes"] = "Yes";
_strings["label_no"] = "No";
_strings["target_unknown"] = "Target: Unknown Object";
_strings["target_none"] = "Target: None";
_strings["player_not_active"] = "Player not active";
_strings["target_hit_point"] = "Target: Hit Point";
_strings["target_type_player"] = "Player";
_strings["target_type_building_block"] = "Building Block";
_strings["target_type_turret"] = "Turret";
_strings["target_type_vehicle"] = "Vehicle";
_strings["target_type_container"] = "Container";
_strings["target_type_loot_container"] = "Loot Container";
_strings["target_type_combat_entity"] = "Combat Entity";
_strings["target_type_entity"] = "Entity";
_strings["target_type_object"] = "Object";
}
}
public string Get(string key, params object[] args)
{
if (_strings.TryGetValue(key, out string value))
{
return args.Length > 0 ? string.Format(value, args) : value;
}
return $"[{key}]";
}
}
private Dictionary<ulong, Language> _playerLanguages = new Dictionary<ulong, Language>();
private string _langsFilePath;
private void LoadLanguages()
{
_langsFilePath = Interface.Oxide.DataDirectory + Path.DirectorySeparatorChar + "ChtoZaPrefab" + Path.DirectorySeparatorChar + "langs.json";
try
{
if (File.Exists(_langsFilePath))
{
var json = File.ReadAllText(_langsFilePath);
_playerLanguages = JsonConvert.DeserializeObject<Dictionary<ulong, Language>>(json) ?? new Dictionary<ulong, Language>();
}
}
catch
{
_playerLanguages = new Dictionary<ulong, Language>();
}
}
private void SaveLanguages()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_langsFilePath));
var json = JsonConvert.SerializeObject(_playerLanguages, Formatting.Indented);
File.WriteAllText(_langsFilePath, json);
}
catch (Exception ex)
{
Puts($"Error saving languages: {ex.Message}");
}
}
private Language GetPlayerLanguage(BasePlayer player)
{
if (player == null) return Language.English;
if (_playerLanguages.TryGetValue(player.userID, out Language lang))
{
if (lang == Language.Auto)
{
string ip = player.IPlayer?.Address ?? "";
return ip.EndsWith(".ru") || ip.EndsWith(".su") || ip.EndsWith(".by") ||
ip.EndsWith(".kz") || ip.EndsWith(".uz") || ip.StartsWith("10.") ||
ip.StartsWith("172.16.") || ip.StartsWith("192.168.") ? Language.Russian : Language.English;
}
return lang;
}
if (_config.DefaultLanguage == "auto")
{
string ip = player.IPlayer?.Address ?? "";
return ip.EndsWith(".ru") || ip.EndsWith(".su") || ip.EndsWith(".by") ||
ip.EndsWith(".kz") || ip.EndsWith(".uz") || ip.StartsWith("10.") ||
ip.StartsWith("172.16.") || ip.StartsWith("192.168.") ?
Language.Russian : Language.English;
}
lang = _config.DefaultLanguage.ToLower().StartsWith("ru") ? Language.Russian : Language.English;
_playerLanguages[player.userID] = lang;
SaveLanguages();
return lang;
}
private void SetPlayerLanguage(BasePlayer player, Language lang)
{
if (player == null) return;
_playerLanguages[player.userID] = lang;
SaveLanguages();
}
private Language GetStoredPlayerLanguage(BasePlayer player)
{
if (player == null) return Language.Auto;
return _playerLanguages.TryGetValue(player.userID, out Language lang) ? lang : Language.Auto;
}
private Localization GetLocalization(BasePlayer player)
{
return new Localization(GetPlayerLanguage(player));
}
#endregion
#region Data Structures
[Serializable]
private class PlayerData
{
public bool IsEnabled { get; set; }
public string TargetUI { get; set; }
public string LastTargetKey { get; set; }
public float LastTargetTime { get; set; }
public Dictionary<string, Vector3> ObjectPositions { get; set; } = new Dictionary<string, Vector3>();
public Dictionary<string, float> ObjectUpdateTimes { get; set; } = new Dictionary<string, float>();
[NonSerialized] public RaycastHit[] RaycastHitsCache = new RaycastHit[32];
[NonSerialized] public Collider LastCollider;
[NonSerialized] public BaseEntity LastEntity;
[NonSerialized] public float LastRaycastTime;
[NonSerialized] public Vector3 LastRaycastOrigin;
[NonSerialized] public Vector3 LastRaycastDirection;
}
private Dictionary<ulong, PlayerData> _playerData = new Dictionary<ulong, PlayerData>();
private readonly HashSet<string> _complexPrefabs = new HashSet<string>
{
"door", "box", "locker", "fridge", "cupboard", "vending",
"turret", "sam.site", "trap", "workbench", "research",
"tool", "computer", "switch", "button", "lever", "hatch",
"shutter", "gate", "computerstation", "rf.detector"
};
private readonly HashSet<string> _simplePrefabs = new HashSet<string>
{
"wall", "floor", "foundation", "ceiling", "stair", "ramp",
"rock", "tree", "ore", "stone", "barrel", "crate",
"pillar", "window", "hedge", "fence", "sign"
};
private class HitInfo
{
public Vector3? HitPoint { get; set; }
public string DisplayText { get; set; }
public Collider Collider { get; set; }
public string PrefabName { get; set; }
public float Distance { get; set; }
}
#endregion
#region Lifecycle
void Init()
{
permission.RegisterPermission(_config.Permission, this);
LoadLanguages();
_updatePlayersTimer = timer.Every(0.2f, UpdateAllPlayers);
_updateRaysTimer = timer.Every(2.0f, UpdateAllRays);
}
void OnServerInitialized()
{
foreach (var player in BasePlayer.activePlayerList)
{
OnPlayerConnected(player);
}
}
void Unload()
{
if (_updatePlayersTimer != null && !_updatePlayersTimer.Destroyed)
{
_updatePlayersTimer.Destroy();
_updatePlayersTimer = null;
}
if (_updateRaysTimer != null && !_updateRaysTimer.Destroyed)
{
_updateRaysTimer.Destroy();
_updateRaysTimer = null;
}
foreach (var player in BasePlayer.activePlayerList)
{
DestroyTargetUI(player);
ClearPlayerDrawings(player);
}
_playerData.Clear();
_playerLanguages.Clear();
}
void OnPlayerConnected(BasePlayer player)
{
if (player == null) return;
timer.In(2f, () =>
{
if (player == null || !player.IsConnected) return;
if (permission.UserHasPermission(player.UserIDString, _config.Permission))
{
bool shouldEnable = _config.AutoEnableForAdmins && player.IsAdmin;
if (!_playerData.ContainsKey(player.userID))
{
_playerData[player.userID] = new PlayerData { IsEnabled = shouldEnable };
}
}
});
}
void OnPlayerDisconnected(BasePlayer player)
{
if (player == null) return;
DestroyTargetUI(player);
ClearPlayerDrawings(player);
_playerData.Remove(player.userID);
}
#endregion
#region Commands
[ChatCommand("pref")]
void CommandLookTarget(BasePlayer player, string command, string[] args)
{
if (!HasPermission(player))
return;
var playerData = GetPlayerData(player.userID);
if (args.Length > 0 && args[0].ToLower() == "lang")
{
if (args.Length > 1)
{
Language newLang = args[1].ToLower().StartsWith("ru") ? Language.Russian : (args[1].ToLower().StartsWith("en") ? Language.English : Language.Auto);
SetPlayerLanguage(player, newLang);
if (!string.IsNullOrEmpty(playerData.TargetUI))
{
DestroyTargetUI(player);
CreateTargetUI(player);
}
}
return;
}
if (!string.IsNullOrEmpty(playerData.TargetUI))
{
DestroyTargetUI(player);
}
else
{
CreateTargetUI(player);
}
}
[ConsoleCommand("pref.closeui")]
void ConsoleCloseUI(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null) DestroyTargetUI(player);
}
[ConsoleCommand("pref.toggle")]
void ConsoleToggle(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !HasPermission(player)) return;
ToggleLookTarget(player);
}
[ConsoleCommand("pref.lang.cycle")]
void ConsoleLangCycle(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !HasPermission(player)) return;
Language current = GetStoredPlayerLanguage(player);
Language next = current == Language.Russian ? Language.English : (current == Language.English ? Language.Auto : Language.Russian);
SetPlayerLanguage(player, next);
var playerData = GetPlayerData(player.userID);
if (!string.IsNullOrEmpty(playerData.TargetUI))
{
DestroyTargetUI(player);
CreateTargetUI(player);
}
}
#endregion
#region Core Update Logic
void UpdateAllPlayers()
{
float currentTime = Time.realtimeSinceStartup;
foreach (var kvp in _playerData)
{
var player = BasePlayer.FindByID(kvp.Key);
if (player == null || !player.IsConnected || player.IsDead() || !kvp.Value.IsEnabled) continue;
var playerData = kvp.Value;
var hitInfo = GetRaycastHitInfo(player, playerData, currentTime);
string currentTargetKey = hitInfo.Collider != null
? $"{hitInfo.Collider.GetInstanceID()}_{hitInfo.PrefabName}"
: null;
if (playerData.LastTargetKey != null && currentTargetKey != playerData.LastTargetKey)
{
ClearOldESPDrawings(player);
}
UpdateTargetInfo(player, hitInfo, playerData);
if (_config.EnableESP && _config.HighlightPrefab && _config.UpdateMovingObjects && hitInfo.Collider != null)
{
UpdateESPForMovingObjects(player, playerData, hitInfo.Collider, currentTime);
}
}
}
void UpdateAllRays()
{
foreach (var kvp in _playerData)
{
if (!kvp.Value.IsEnabled) continue;
var player = BasePlayer.FindByID(kvp.Key);
if (player == null || !player.IsConnected || player.IsDead()) continue;
if (_config.DrawLookRay)
{
var hitInfo = GetCachedRaycastHitInfo(player, kvp.Value);
Vector3 rayEnd = hitInfo.HitPoint.HasValue
? hitInfo.HitPoint.Value
: player.eyes.position + player.eyes.BodyForward() * _config.MaxDistance;
DrawLookRay(player, rayEnd);
}
}
}
void UpdateTargetInfo(BasePlayer player, HitInfo hitInfo, PlayerData playerData)
{
playerData.LastTargetTime = Time.realtimeSinceStartup;
if (hitInfo.HitPoint.HasValue)
{
UpdateTargetUI(player, hitInfo.DisplayText);
string targetKey = hitInfo.Collider != null
? $"{hitInfo.Collider.GetInstanceID()}_{hitInfo.PrefabName}"
: null;
if (targetKey != null)
{
bool targetChanged = playerData.LastTargetKey != targetKey;
if (targetChanged)
{
playerData.LastTargetKey = targetKey;
playerData.ObjectPositions[targetKey] = Vector3.zero;
playerData.ObjectUpdateTimes[targetKey] = 0f;
}
if (_config.EnableESP && _config.HighlightPrefab)
{
Vector3 objectCenter = hitInfo.Collider.bounds.center;
HighlightObject(player, hitInfo.Collider, objectCenter, hitInfo.Distance, targetChanged);
}
}
}
else
{
var loc = GetLocalization(player);
if (!string.IsNullOrEmpty(playerData.TargetUI))
{
UpdateTargetUI(player, CreateColoredLine(loc.Get("panel_no_target"), "#FF0000", _config.HeaderColor));
}
if (!string.IsNullOrEmpty(playerData.LastTargetKey))
{
ClearOldESPDrawings(player);
playerData.LastTargetKey = null;
}
}
}
void UpdateESPForMovingObjects(BasePlayer player, PlayerData playerData, Collider currentCollider, float currentTime)
{
if (string.IsNullOrEmpty(playerData.LastTargetKey)) return;
string targetKey = playerData.LastTargetKey;
if (playerData.ObjectUpdateTimes.TryGetValue(targetKey, out float lastUpdate))
{
if (currentTime - lastUpdate < 0.1f) return;
playerData.ObjectUpdateTimes[targetKey] = currentTime;
string currentTargetKey = $"{currentCollider.GetInstanceID()}_{GetPrefabName(currentCollider)}";
if (currentTargetKey == targetKey)
{
Vector3 currentPosition = GetObjectPosition(currentCollider.gameObject);
if (playerData.ObjectPositions.TryGetValue(targetKey, out Vector3 lastPosition))
{
if (Vector3.Distance(lastPosition, currentPosition) > 0.1f)
{
playerData.ObjectPositions[targetKey] = currentPosition;
ClearPlayerDrawings(player);
Vector3 objectCenter = currentCollider.bounds.center;
HighlightObject(player, currentCollider, objectCenter, Vector3.Distance(player.eyes.position, objectCenter), true);
}
}
else
{
playerData.ObjectPositions[targetKey] = currentPosition;
}
}
}
}
private string GetPrefabName(Collider collider)
{
if (collider == null) return "";
var entity = collider.GetComponent<BaseEntity>();
return entity != null ? entity.ShortPrefabName : collider.name.Replace("(Clone)", "").Trim();
}
#endregion
#region Raycast & Hit Detection
HitInfo GetRaycastHitInfo(BasePlayer player, PlayerData playerData, float currentTime)
{
if (player == null || player.IsDead()) return new HitInfo();
Vector3 eyePos = player.eyes.position;
Vector3 forward = player.eyes.BodyForward();
if (playerData.LastRaycastTime > 0 &&
currentTime - playerData.LastRaycastTime < 0.1f &&
Vector3.Distance(playerData.LastRaycastOrigin, eyePos) < 0.01f &&
Vector3.Distance(playerData.LastRaycastDirection, forward) < 0.01f &&
playerData.LastCollider != null)
{
return new HitInfo
{
HitPoint = playerData.LastCollider.bounds.center,
DisplayText = GetCachedDisplayText(player, playerData.LastCollider, playerData.LastEntity),
Collider = playerData.LastCollider,
PrefabName = playerData.LastEntity != null ? playerData.LastEntity.ShortPrefabName : "",
Distance = Vector3.Distance(eyePos, playerData.LastCollider.bounds.center)
};
}
int layerMask = ~((1 << 23) | (1 << 4));
RaycastHit hit;
if (!Physics.Raycast(eyePos, forward, out hit, _config.MaxDistance, layerMask, QueryTriggerInteraction.Ignore))
return new HitInfo();
if (hit.collider.gameObject.layer == 23 || hit.collider.gameObject.layer == 4)
return new HitInfo();
Collider hitCollider = hit.collider;
if (hitCollider == null)
{
var loc = GetLocalization(player);
float distanceToHitPoint = Vector3.Distance(eyePos, hit.point);
var lines = new List<string>();
lines.Add($"<color={_config.HeaderColor}>——— {loc.Get("target_hit_point")} ———</color>");
lines.Add(ColorizeText(loc.Get("label_distance"), $"{distanceToHitPoint:F1}{loc.Get("panel_distance_unit")}", _config.EntityColor));
lines.Add(ColorizeText(loc.Get("label_position"), $"{hit.point.x:F1}, {hit.point.y:F1}, {hit.point.z:F1}", _config.EntityColor));
return new HitInfo
{
HitPoint = hit.point,
DisplayText = string.Join("\n", lines),
Distance = distanceToHitPoint
};
}
playerData.LastRaycastTime = currentTime;
playerData.LastRaycastOrigin = eyePos;
playerData.LastRaycastDirection = forward;
playerData.LastCollider = hitCollider;
playerData.LastEntity = hitCollider.GetComponent<BaseEntity>() ?? hitCollider.GetComponentInParent<BaseEntity>();
string displayText = GetEntityDisplayInfo(player, hitCollider, playerData.LastEntity);
float distance = playerData.LastEntity != null
? Vector3.Distance(eyePos, playerData.LastEntity.transform.position)
: Vector3.Distance(eyePos, hit.point);
return new HitInfo
{
HitPoint = hit.point,
DisplayText = displayText,
Collider = hitCollider,
PrefabName = playerData.LastEntity != null ? playerData.LastEntity.ShortPrefabName : "",
Distance = distance
};
}
HitInfo GetRaycastHitInfo(BasePlayer player)
{
return GetRaycastHitInfo(player, GetPlayerData(player.userID), Time.realtimeSinceStartup);
}
HitInfo GetCachedRaycastHitInfo(BasePlayer player, PlayerData playerData)
{
if (playerData.LastCollider != null &&
Time.realtimeSinceStartup - playerData.LastRaycastTime < 0.5f)
{
return new HitInfo
{
HitPoint = playerData.LastCollider.bounds.center,
DisplayText = GetCachedDisplayText(player, playerData.LastCollider, playerData.LastEntity),
Collider = playerData.LastCollider,
PrefabName = playerData.LastEntity != null ? playerData.LastEntity.ShortPrefabName : "",
Distance = Vector3.Distance(player.eyes.position, playerData.LastCollider.bounds.center)
};
}
return GetRaycastHitInfo(player, playerData, Time.realtimeSinceStartup);
}
private string GetEntityDisplayInfo(BasePlayer player, Collider collider, BaseEntity entity)
{
if (entity != null)
{
Vector3 entityPosition = entity.transform.position;
float entityDistance = Vector3.Distance(player.eyes.position, entityPosition);
return GetEntityInfo(entity, entityDistance, entityPosition, player);
}
else
{
return GetStaticObjectFullInfo(player, collider);
}
}
string GetStaticObjectFullInfo(BasePlayer player, Collider collider)
{
var loc = GetLocalization(player);
if (collider == null) return CreateColoredLine(loc.Get("target_none"), AlertColor, _config.HeaderColor);
List<string> lines = new List<string>();
AddSection(lines, loc.Get("section_static_object"), GetSectionColor("Static Object"));
float dist = Vector3.Distance(player.eyes.position, collider.bounds.center);
AddLine(lines, loc.Get("label_distance"), $"{dist:F1}{loc.Get("panel_distance_unit")}", _config.EntityColor);
string objectName = collider.gameObject.name.Replace("(Clone)", "").Trim();
AddSection(lines, loc.Get("section_identity"), GetSectionColor("Identity"));
AddLine(lines, loc.Get("label_object_name"), objectName, _config.TechnicalColor);
string fullPrefabPath = GetStaticPrefabPath(collider.gameObject);
if (!string.IsNullOrEmpty(fullPrefabPath)) AddLine(lines, loc.Get("label_full_path"), fullPrefabPath, _config.EntityColor);
Transform tr = collider.transform;
AddSection(lines, loc.Get("section_transform"), GetSectionColor("Transform"));
AddLine(lines, loc.Get("label_position"), $"{tr.position.x:F2}, {tr.position.y:F2}, {tr.position.z:F2}", _config.BasicColor);
AddLine(lines, loc.Get("label_rotation_euler"), $"{tr.rotation.eulerAngles.x:F0}, {tr.rotation.eulerAngles.y:F0}, {tr.rotation.eulerAngles.z:F0}", _config.TechnicalColor);
AddLine(lines, loc.Get("label_local_position"), $"{tr.localPosition.x:F2}, {tr.localPosition.y:F2}, {tr.localPosition.z:F2}", _config.TechnicalColor);
AddLine(lines, loc.Get("label_local_rotation"), $"{tr.localRotation.eulerAngles.x:F0}, {tr.localRotation.eulerAngles.y:F0}, {tr.localRotation.eulerAngles.z:F0}", _config.TechnicalColor);
AddLine(lines, loc.Get("label_local_scale"), $"{tr.localScale.x:F2}, {tr.localScale.y:F2}, {tr.localScale.z:F2}", _config.TechnicalColor);
AddSection(lines, loc.Get("section_components"), GetSectionColor("Components"));
AddLine(lines, loc.Get("panel_collider"), collider.GetType().Name, _config.TechnicalColor);
var allColliders = collider.gameObject.GetComponentsInChildren<Collider>(true);
if (allColliders != null && allColliders.Length > 1)
AddLine(lines, loc.Get("label_colliders_total"), allColliders.Length.ToString(), _config.TechnicalColor);
var renderers = collider.gameObject.GetComponentsInChildren<Renderer>(true);
AddLine(lines, loc.Get("label_renderers"), renderers != null ? renderers.Length.ToString() : "0", _config.TechnicalColor);
var rb = collider.GetComponent<Rigidbody>();
AddLine(lines, loc.Get("label_rigidbody"), rb != null ? loc.Get("label_yes") : loc.Get("label_no"), _config.TechnicalColor);
AddSection(lines, loc.Get("section_bounds"), GetSectionColor("Bounds"));
Bounds b = collider.bounds;
AddLine(lines, loc.Get("label_center"), $"{b.center.x:F2}, {b.center.y:F2}, {b.center.z:F2}", _config.BasicColor);
AddLine(lines, loc.Get("label_size"), $"{b.size.x:F2}, {b.size.y:F2}, {b.size.z:F2}", _config.BasicColor);
return string.Join("\n", lines);
}
private string GetCachedDisplayText(BasePlayer player, Collider collider, BaseEntity entity)
{
if (entity != null)
{
return GetEntityInfo(entity, Vector3.Distance(player.eyes.position, entity.transform.position),
entity.transform.position, player);
}
else
{
var loc = GetLocalization(player);
return CreateColoredLine(loc.Get("panel_target_static"), "#FF0000", _config.HeaderColor);
}
}
Vector3 GetObjectPosition(GameObject obj)
{
if (obj == null) return Vector3.zero;
BaseEntity entity = obj.GetComponent<BaseEntity>();
if (entity != null) return entity.transform.position;
Rigidbody rb = obj.GetComponent<Rigidbody>();
if (rb != null) return rb.position;
return obj.transform.position;
}
private string GetStaticPrefabPath(GameObject obj)
{
if (obj == null) return "";
string name = obj.name.Replace("(Clone)", "").Trim();
if (name.Contains("/") || name.Contains(".prefab"))
return name;
var resource = obj.GetComponent<ResourceEntity>();
if (resource != null)
{
string prefabName = resource.PrefabName;
if (!string.IsNullOrEmpty(prefabName) && (prefabName.Contains("/") || prefabName.Contains(".prefab")))
return prefabName;
}
Transform parent = obj.transform.parent;
while (parent != null)
{
string parentName = parent.name.Replace("(Clone)", "").Trim();
if (parentName.Contains("/") || parentName.Contains(".prefab"))
return parentName;
parent = parent.parent;
}
string lowerName = name.ToLower();
if (lowerName.Contains("tree") || lowerName.Contains("stump") || lowerName.Contains("log") ||
lowerName.Contains("pine") || lowerName.Contains("oak") || lowerName.Contains("birch") ||
lowerName.Contains("hemp") || lowerName.Contains("ore") || lowerName.Contains("stone") ||
lowerName.Contains("rock") || lowerName.Contains("metal") || lowerName.Contains("sulfur") ||
lowerName.Contains("hqm") || lowerName.Contains("barrel") || lowerName.Contains("crate") ||
lowerName.Contains("loot") || lowerName.Contains("supply"))
{
return "assets/bundled/prefabs/autospawn/resource/" + name + ".prefab";
}
return name;
}
private bool IsResourceObject(GameObject obj)
{
if (obj == null) return false;
var resource = obj.GetComponent<ResourceEntity>();
if (resource != null) return true;
string prefabPath = GetStaticPrefabPath(obj);
if (string.IsNullOrEmpty(prefabPath)) return false;
string lowerPath = prefabPath.ToLower();
return lowerPath.Contains("/resource/") || lowerPath.Contains("/autospawn/resource/") ||
(lowerPath.Contains("/autospawn/") && (lowerPath.Contains("tree") || lowerPath.Contains("ore") ||
lowerPath.Contains("stone") || lowerPath.Contains("pine") || lowerPath.Contains("oak") ||
lowerPath.Contains("birch") || lowerPath.Contains("hemp") || lowerPath.Contains("barrel") ||
lowerPath.Contains("crate")));
}
#endregion
#region Entity Information
string GetSectionColor(string sectionName)
{
if (string.IsNullOrEmpty(sectionName)) return _config.HeaderColor;
string k = sectionName.Trim().ToLower();
if (k == "health") return "1 0 0 1";
if (k == "inventory" || k == "loot") return "0.5 1 0.5 1";
if (k == "transform" || k == "identity" || k == "static object") return _config.EntityColor;
if (k == "components" || k == "door / lock") return _config.TechnicalColor;
if (k == "bounds") return _config.BuildingColor;
if (k == "owner" || k == "player" || k == "vehicle") return _config.PlayerColor;
if (k == "building" || k == "turret") return _config.BuildingColor;
return _config.HeaderColor;
}
void AddSection(List<string> lines, string title, string color)
{
string sectionColor = color ?? GetSectionColor(title);
lines.Add($"<color={sectionColor}>——— {title} ———</color>");
}
void AddLine(List<string> lines, string label, object value, string valueColor)
{
lines.Add(ColorizeText(label, value?.ToString() ?? "—", valueColor));
}
string ColorizeText(string label, string value, string valueColor)
{
string labelColor = _config.BasicColor;
return $"<color={labelColor}>{label}:</color> <color={valueColor}>{value}</color>";
}
const string AlertColor = "1 0 0 1";
string GetEntityInfo(BaseEntity entity, float distance, Vector3 position, BasePlayer player)
{
var loc = GetLocalization(player);
if (entity == null) return CreateColoredLine(loc.Get("target_unknown"), AlertColor, _conf
// ... [Code preview truncated due to length. Full source in attached file] ...