Загрузка данных
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using UnityEngine;
using UnityEngine.SceneManagement;
[BepInPlugin("my.cheatmenu", "CheatMenu", "1.7")]
public class Plugin : BasePlugin
{
public static ManualLogSource L;
public override void Load()
{
L = Log;
ClassInjector.RegisterTypeInIl2Cpp<MenuBehaviour>();
AddComponent<MenuBehaviour>();
Log.LogInfo("CheatMenu loaded, press Insert");
}
}
public class MenuBehaviour : MonoBehaviour
{
public MenuBehaviour(IntPtr ptr) : base(ptr) { }
bool open, fly, stamina;
float flySpeed = 15f;
Transform player;
CharacterController cc;
Rigidbody rb;
int scenePage = 0;
float nextApply;
// режим шахеда
bool drone, exploding;
float droneSpeed = 30f, crashSpeed = 20f, spinSpeed = 360f;
float droneStartTime, respawnAt, explodeTime = -100f;
Vector3 startPos;
GameObject flash;
AudioClip boomClip, buzzClip, musicClip;
AudioSource buzzSrc, musicSrc, remoteBuzz, remoteMusic;
string lastHit = "-";
string soundStatus = "звуки не загружены";
string statsStatus = "";
// размеры, выбор, грабители
Transform sel;
string selInfo = "Выбрано: ничего";
float selfScale = 1f, selScale = 1f, robberScale = 1f;
string robberKey = "robber";
string lastRobberKey = null;
bool aiOff;
int robbersFound = 0;
float nextRobber;
// список объектов
string filter = "";
string lastFilter = null;
bool rootOnly = true;
bool lastRootOnly = true;
int listOffset = 0;
int listRows = 18;
Rect listRect = new Rect(0, 0, 0, 0);
string spawnStatus = "";
// свой ввод текста: 0 нет, 1 поиск, 2 имя грабителя, 3 IP друга
int focus = 0;
static readonly List<GameObject> found = new List<GameObject>();
static readonly List<string> foundLabels = new List<string>();
static readonly Dictionary<int, Vector3> origScale = new Dictionary<int, Vector3>();
static readonly List<Behaviour> disabledAI = new List<Behaviour>();
static readonly HashSet<string> guiErrors = new HashSet<string>();
// сеть
static UdpClient udp;
static Thread thr;
static volatile bool netOn;
static string friendIp = "";
static int netPort = 27777;
static string netStatus = "выключена";
static readonly ConcurrentQueue<string> inbox = new ConcurrentQueue<string>();
// стамина
static Assembly gameAsm;
static readonly Dictionary<string, Type> typeCache = new Dictionary<string, Type>();
static readonly Dictionary<string, double> best = new Dictionary<string, double>();
static readonly HashSet<string> loggedSet = new HashSet<string>();
static readonly string[] StaminaWords = { "stamina", "energy" };
static readonly string[] BadWords = { "regen", "rate", "damage", "cost", "drain", "speed", "time", "delay", "max", "min", "percent", "ratio", "fov", "view", "camera", "zoom", "field", "bar", "text", "ui", "image", "slider" };
// ---------- звук ----------
bool ClipOk(AudioClip c)
{
if (c == null) return false;
try { return c.length > 0f; } catch { return false; }
}
void EnsureSounds()
{
if (!ClipOk(boomClip)) boomClip = LoadWav("boom.wav");
if (!ClipOk(boomClip)) { boomClip = MakeBoom(); }
if (!ClipOk(buzzClip)) buzzClip = LoadWav("buzz.wav");
if (!ClipOk(musicClip)) musicClip = LoadWav("music.wav");
soundStatus = "boom:" + (ClipOk(boomClip) ? "ok" : "нет")
+ " buzz:" + (ClipOk(buzzClip) ? "ok" : "нет")
+ " music:" + (ClipOk(musicClip) ? "ok" : "нет");
}
AudioClip MakeClip(string name, float[] d, int ch, int rate)
{
var clip = AudioClip.Create(name, d.Length / ch, ch, rate, false);
clip.SetData((Il2CppStructArray<float>)d, 0);
clip.hideFlags = HideFlags.HideAndDontSave;
return clip;
}
AudioClip MakeBoom()
{
try
{
int rate = 22050, n = rate * 2;
var d = new float[n];
var r = new System.Random(1);
for (int i = 0; i < n; i++)
{
float t = (float)i / rate;
d[i] = ((float)r.NextDouble() * 2f - 1f) * Mathf.Exp(-t * 3f) * 0.9f;
}
return MakeClip("boom_gen", d, 1, rate);
}
catch (Exception e) { Plugin.L.LogInfo("MakeBoom error: " + e.Message); return null; }
}
AudioClip LoadWav(string name)
{
try
{
using (var s = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
{
if (s == null) { Plugin.L.LogInfo("No embedded sound: " + name); return null; }
var ms = new MemoryStream();
s.CopyTo(ms);
byte[] b = ms.ToArray();
int pos = 12, ch = 1, rate = 44100, bits = 16, fmt = 1, dataPos = -1, dataLen = 0;
while (pos + 8 <= b.Length)
{
string id = Encoding.ASCII.GetString(b, pos, 4);
int size = BitConverter.ToInt32(b, pos + 4);
if (id == "fmt ")
{
fmt = BitConverter.ToInt16(b, pos + 8);
ch = BitConverter.ToInt16(b, pos + 10);
rate = BitConverter.ToInt32(b, pos + 12);
bits = BitConverter.ToInt16(b, pos + 22);
}
else if (id == "data") { dataPos = pos + 8; dataLen = Math.Min(size, b.Length - dataPos); break; }
pos += 8 + size + (size % 2);
}
if (dataPos < 0) { Plugin.L.LogInfo(name + ": no data chunk"); return null; }
if (fmt != 1 && fmt != 65534) { Plugin.L.LogInfo(name + ": unsupported format " + fmt + " (need PCM)"); return null; }
if (bits != 16) { Plugin.L.LogInfo(name + ": need 16-bit, got " + bits); return null; }
int samples = dataLen / 2;
var f = new float[samples];
for (int i = 0; i < samples; i++) f[i] = BitConverter.ToInt16(b, dataPos + i * 2) / 32768f;
var clip = MakeClip(name, f, ch, rate);
Plugin.L.LogInfo("Loaded sound: " + name + " (" + ch + "ch, " + rate + "Hz, " + samples + " samples)");
return clip;
}
}
catch (Exception e) { Plugin.L.LogInfo("LoadWav error (" + name + "): " + e.Message); return null; }
}
AudioSource MakeLoopSource(string name, AudioClip clip, float volume)
{
var g = new GameObject(name);
UnityEngine.Object.DontDestroyOnLoad(g);
var src = g.AddComponent<AudioSource>();
src.clip = clip;
src.loop = true;
src.spatialBlend = 0f;
src.volume = volume;
src.ignoreListenerPause = true;
src.Play();
return src;
}
void KillSource(ref AudioSource src)
{
if (src == null) return;
try { src.Stop(); UnityEngine.Object.Destroy(src.gameObject); } catch { }
src = null;
}
void StopDroneSounds()
{
KillSource(ref buzzSrc);
KillSource(ref musicSrc);
}
void PlayMusic()
{
EnsureSounds();
StopDroneSounds();
if (ClipOk(buzzClip)) buzzSrc = MakeLoopSource("DroneBuzz", buzzClip, 1f);
if (ClipOk(musicClip)) musicSrc = MakeLoopSource("DroneMusic", musicClip, 0.8f);
}
void PlayBoom(float volume)
{
EnsureSounds();
if (!ClipOk(boomClip)) return;
var g = new GameObject("BoomSound");
UnityEngine.Object.DontDestroyOnLoad(g);
var src = g.AddComponent<AudioSource>();
src.clip = boomClip;
src.spatialBlend = 0f;
src.volume = volume;
src.ignoreListenerPause = true;
src.Play();
UnityEngine.Object.Destroy(g, boomClip.length + 0.5f);
}
void RemoteOn()
{
EnsureSounds();
RemoteOff();
if (ClipOk(buzzClip)) remoteBuzz = MakeLoopSource("RemoteBuzz", buzzClip, 0.6f);
if (ClipOk(musicClip)) remoteMusic = MakeLoopSource("RemoteMusic", musicClip, 0.6f);
}
void RemoteOff()
{
KillSource(ref remoteBuzz);
KillSource(ref remoteMusic);
}
// ---------- сеть ----------
void StartNet()
{
try
{
udp = new UdpClient(netPort);
netOn = true;
thr = new Thread(RecvLoop);
thr.IsBackground = true;
thr.Start();
netStatus = "слушаю порт " + netPort;
}
catch (Exception e) { netOn = false; netStatus = "ошибка: " + e.Message; }
}
void StopNet()
{
netOn = false;
try { if (udp != null) udp.Close(); } catch { }
udp = null;
netStatus = "выключена";
}
static void RecvLoop()
{
var ep = new IPEndPoint(IPAddress.Any, 0);
while (netOn)
{
try
{
byte[] data = udp.Receive(ref ep);
if (data.Length == 0 || data.Length > 16) continue;
string ip = friendIp.Trim();
if (ip.Length == 0 || ep.Address.ToString() != ip) continue;
string s = Encoding.ASCII.GetString(data);
if (s == "BUZZ_ON" || s == "BUZZ_OFF" || s == "BOOM") inbox.Enqueue(s);
}
catch { if (!netOn) break; }
}
}
static void Send(string msg)
{
if (!netOn || udp == null) return;
string ip = friendIp.Trim();
if (ip.Length == 0) return;
try
{
byte[] b = Encoding.ASCII.GetBytes(msg);
udp.Send(b, b.Length, ip, netPort);
}
catch { }
}
void HandleRemote(string s)
{
if (s == "BUZZ_ON") RemoteOn();
else if (s == "BUZZ_OFF") RemoteOff();
else if (s == "BOOM") PlayBoom(0.7f);
}
void OnApplicationQuit() { StopNet(); }
// ---------- игрок ----------
bool EnsurePlayer()
{
if (player != null) return true;
cc = FindObjectOfType<CharacterController>();
if (cc != null) { player = cc.transform; return true; }
try
{
var go = GameObject.FindWithTag("Player");
if (go != null) { player = go.transform; rb = go.GetComponent<Rigidbody>(); return true; }
}
catch { }
return false;
}
// ---------- свой ввод текста ----------
string GetText(int id)
{
if (id == 1) return filter;
if (id == 2) return robberKey;
if (id == 3) return friendIp;
return "";
}
void SetText(int id, string v)
{
if (id == 1) filter = v;
else if (id == 2) robberKey = v;
else if (id == 3) friendIp = v;
}
void ProcessTyping()
{
if (!open || focus == 0) return;
if (Input.GetKeyDown(KeyCode.Escape)) { focus = 0; return; }
string t = GetText(focus);
string add = "";
try { add = Input.inputString; } catch { }
foreach (char c in add)
{
if (c == '\b') { if (t.Length > 0) t = t.Substring(0, t.Length - 1); }
else if (c == '\n' || c == '\r') { focus = 0; break; }
else if (!char.IsControl(c) && t.Length < 60) t += c;
}
if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.V))
{
try { string clip = GUIUtility.systemCopyBuffer; if (clip != null) t += clip.Trim(); } catch { }
if (t.Length > 60) t = t.Substring(0, 60);
}
if (focus != 0) SetText(focus, t);
}
void TextBox(Rect r, string value, int id, string hint)
{
string shown = value;
if (value.Length == 0 && focus != id) shown = hint;
if (focus == id && ((int)(Time.unscaledTime * 2f) % 2 == 0)) shown += "|";
GUI.color = focus == id ? Color.yellow : Color.white;
if (GUI.Button(r, shown)) focus = id;
GUI.color = Color.white;
}
// ---------- основной цикл ----------
void Update()
{
string msg;
while (inbox.TryDequeue(out msg)) HandleRemote(msg);
if (Input.GetKeyDown(KeyCode.Insert)) open = !open;
if (!open) focus = 0;
ProcessTyping();
if (focus == 0)
{
if (Input.GetKeyDown(KeyCode.F6)) SetFly(!fly);
if (Input.GetKeyDown(KeyCode.F8)) SetDrone(!drone);
if (Input.GetKeyDown(KeyCode.F9)) SelectUnderCrosshair();
}
if (open) { Cursor.visible = true; Cursor.lockState = CursorLockMode.None; }
// колесо мыши над списком объектов
if (open && found.Count > 0)
{
try
{
float sw = Input.mouseScrollDelta.y;
if (Mathf.Abs(sw) > 0.01f)
{
Vector3 mp = Input.mousePosition;
var pt = new Vector2(mp.x, Screen.height - mp.y);
if (listRect.Contains(pt))
{
listOffset -= (sw > 0 ? 1 : -1) * 3;
listOffset = Mathf.Clamp(listOffset, 0, Mathf.Max(0, found.Count - listRows));
}
}
}
catch { }
}
// обычный полёт
if (fly && !drone && player != null && Camera.main != null && !open)
{
var cam = Camera.main.transform;
Vector3 dir = Vector3.zero;
if (Input.GetKey(KeyCode.W)) dir += cam.forward;
if (Input.GetKey(KeyCode.S)) dir -= cam.forward;
if (Input.GetKey(KeyCode.D)) dir += cam.right;
if (Input.GetKey(KeyCode.A)) dir -= cam.right;
if (Input.GetKey(KeyCode.Space)) dir += Vector3.up;
if (Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
float sp = Input.GetKey(KeyCode.LeftShift) ? flySpeed * 3f : flySpeed;
player.position += dir.normalized * sp * Time.deltaTime;
}
// режим шахеда
if (drone && !exploding && player != null && Camera.main != null && !open)
{
var cam = Camera.main.transform;
Vector3 dir = cam.forward;
if (Input.GetKey(KeyCode.W)) droneSpeed += 25f * Time.deltaTime;
if (Input.GetKey(KeyCode.S)) droneSpeed -= 25f * Time.deltaTime;
droneSpeed = Mathf.Clamp(droneSpeed, 5f, 80f);
float dist = droneSpeed * Time.deltaTime + 0.6f;
bool hit = false;
var hits = Physics.SphereCastAll(player.position, 0.5f, dir, dist);
foreach (var h in hits)
{
if (h.collider == null || h.collider.isTrigger) continue;
if (h.transform == player || h.transform.IsChildOf(player)) continue;
hit = true;
lastHit = h.collider.name;
break;
}
if (hit && Time.time - droneStartTime > 1.5f)
{
if (droneSpeed >= crashSpeed) Explode(player.position);
}
else
{
player.position += dir * droneSpeed * Time.deltaTime;
}
player.Rotate(dir, spinSpeed * Time.deltaTime, Space.World);
}
// сфера взрыва
if (flash != null)
{
float k = (Time.time - explodeTime) / 0.6f;
if (k >= 1f) { UnityEngine.Object.Destroy(flash); flash = null; }
else flash.transform.localScale = Vector3.one * (2f + k * 14f);
}
// возврат на старт
if (exploding && Time.time >= respawnAt)
{
if (player != null) player.position = startPos;
exploding = false;
SetFly(false);
}
if (stamina && Time.time >= nextApply)
{
nextApply = Time.time + 0.25f;
ApplyStats(0);
}
// грабители: ИИ и размер
if (Time.time >= nextRobber && (aiOff || Mathf.Abs(robberScale - 1f) > 0.001f))
{
nextRobber = Time.time + 1.5f;
var rs = FindRobbers();
robbersFound = rs.Count;
if (Mathf.Abs(robberScale - 1f) > 0.001f)
foreach (var go in rs) SetScale(go.transform, robberScale);
if (aiOff) ApplyRobberAI(rs);
}
}
void SetFly(bool value)
{
if (value == fly) return;
fly = value;
if (fly)
{
if (!EnsurePlayer())
{
fly = false;
Plugin.L.LogInfo("Player not found (no CharacterController / Player-tag object)");
return;
}
if (cc != null) cc.enabled = false;
else if (rb != null) rb.isKinematic = true;
}
else
{
if (drone) Send("BUZZ_OFF");
drone = false;
StopDroneSounds();
if (cc != null) cc.enabled = true;
if (rb != null) rb.isKinematic = false;
}
}
void SetDrone(bool value)
{
if (value)
{
SetFly(true);
if (!fly || player == null) return;
startPos = player.position;
droneStartTime = Time.time;
exploding = false;
drone = true;
PlayMusic();
Send("BUZZ_ON");
}
else
{
drone = false;
StopDroneSounds();
Send("BUZZ_OFF");
}
}
void Explode(Vector3 pos)
{
exploding = true;
respawnAt = Time.time + 1.5f;
explodeTime = Time.time;
StopDroneSounds();
Send("BUZZ_OFF");
Send("BOOM");
Plugin.L.LogInfo("EXPLODE at " + pos + ", hit: " + lastHit);
PlayBoom(1f);
try
{
if (flash != null) UnityEngine.Object.Destroy(flash);
flash = GameObject.CreatePrimitive(PrimitiveType.Sphere);
flash.transform.position = pos;
flash.transform.localScale = Vector3.one * 2f;
var col = flash.GetComponent<Collider>();
if (col != null) UnityEngine.Object.Destroy(col);
var rend = flash.GetComponent<Renderer>();
if (rend != null) rend.material.color = new Color(1f, 0.55f, 0.1f);
}
catch { }
var near = Physics.OverlapSphere(pos, 10f);
foreach (var c in near)
{
if (c == null) continue;
var body = c.attachedRigidbody;
if (body != null && body != rb) body.AddExplosionForce(800f, pos, 10f);
}
}
// ---------- размер и выбор ----------
void SetScale(Transform t, float factor)
{
if (t == null) return;
try
{
int id = t.gameObject.GetInstanceID();
if (!origScale.ContainsKey(id)) origScale[id] = t.localScale;
t.localScale = origScale[id] * factor;
}
catch { }
}
float CurrentFactor(Transform t)
{
try
{
int id = t.gameObject.GetInstanceID();
Vector3 o;
if (origScale.TryGetValue(id, out o) && Mathf.Abs(o.x) > 0.0001f) return t.localScale.x / o.x;
}
catch { }
return 1f;
}
void SetSel(Transform t)
{
sel = t;
if (t == null) { selInfo = "Выбрано: ничего"; return; }
selScale = CurrentFactor(t);
string path = t.name;
if (t.parent != null) path = t.root.name + " / ... / " + t.name;
selInfo = "Выбрано: " + path;
}
void SelectUnderCrosshair()
{
if (Camera.main == null) { selInfo = "Нет камеры"; return; }
try
{
var ray = Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2f, Screen.height / 2f, 0f));
var hits = Physics.RaycastAll(ray, 200f);
float bestD = 1e9f;
Transform bt = null;
foreach (var h in hits)
{
if (h.collider == null) continue;
var tr = h.collider.transform;
if (player != null && (tr == player || tr.IsChildOf(player))) continue;
if (h.distance < bestD) { bestD = h.distance; bt = tr; }
}
if (bt == null) { selInfo = "Под прицелом ничего нет"; return; }
SetSel(bt);
}
catch (Exception e) { selInfo = "Ошибка выбора: " + e.Message; }
}
// ---------- грабители ----------
List<GameObject> FindRobbers()
{
var res = new List<GameObject>();
string key = robberKey.ToLower().Trim();
if (key.Length < 2) return res;
var all = Resources.FindObjectsOfTypeAll<GameObject>();
foreach (var go in all)
{
if (go == null) continue;
try
{
if (!go.scene.IsValid()) continue;
if (!go.name.ToLower().Contains(key)) continue;
var p = go.transform.parent;
if (p != null && p.name.ToLower().Contains(key)) continue;
res.Add(go);
}
catch { }
}
return res;
}
void ApplyRobberAI(List<GameObject> robbers)
{
foreach (var go in robbers)
{
try
{
var comps = go.GetComponentsInChildren<Behaviour>(true);
foreach (var b in comps)
{
if (b == null || !b.enabled) continue;
string tn = "";
try { tn = b.GetIl2CppType().Name; } catch { }
bool kill = false;
if (tn == "NavMeshAgent") kill = true;
else
{
var mb = b.TryCast<MonoBehaviour>();
if (mb != null && ResolveType(mb) != null) kill = true;
}
if (kill) { b.enabled = false; disabledAI.Add(b); }
}
}
catch { }
}
}
void RestoreRobberAI()
{
foreach (var b in disabledAI)
{
try { if (b != null) b.enabled = true; } catch { }
}
disabledAI.Clear();
}
// ---------- стамина ----------
Type ResolveType(MonoBehaviour mb)
{
string n = null;
try { n = mb.GetIl2CppType().FullName; } catch { }
if (n == null) return null;
Type t;
if (typeCache.TryGetValue(n, out t)) return t;
if (gameAsm == null)
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
if (a.GetName().Name == "Assembly-CSharp") { gameAsm = a; break; }
t = gameAsm != null ? gameAsm.GetType(n) : null;
typeCache[n] = t;
return t;
}
static string[] Words(string name)
{
string s = Regex.Replace(name, "([a-z0-9])([A-Z])", "$1 $2");
s = s.Replace("_", " ").ToLower();
return s.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
static bool HasAny(string[] words, string[] list)
{
foreach (var w in words) foreach (var l in list) if (w == l) return true;
return false;
}
void ApplyStats(int mode)
{
int cnt = 0;
var all = UnityEngine.Object.FindObjectsOfType<MonoBehaviour>();
foreach (var mb in all)
{
if (mb == null) continue;
try
{
var t = ResolveType(mb);
if (t == null) continue;
object obj = Activator.CreateInstance(t, mb.Pointer);
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var p in props)
{
if (!p.CanRead || !p.CanWrite) continue;
if (p.PropertyType != typeof(float) && p.PropertyType != typeof(int)) continue;
var w = Words(p.Name);
if (HasAny(w, BadWords)) continue;
if (!HasAny(w, StaminaWords)) continue;
object cur = p.GetValue(obj);
double curV = cur is float cf ? cf : (cur is int ci ? ci : 0);
if (mode == 1)
{
Plugin.L.LogInfo("FOUND: " + t.FullName + "." + p.Name + " = " + curV);
cnt++;
continue;
}
string key = t.FullName + "." + p.Name + "#" + mb.Pointer.ToString();
double b;
if (!best.TryGetValue(key, out b) || curV > b) { best[key] = curV; b = curV; }
if (curV < b)
{
if (p.PropertyType == typeof(float)) p.SetValue(obj, (float)b);
else p.SetValue(obj, (int)b);
if (loggedSet.Add(t.FullName + "." + p.Name))
Plugin.L.LogInfo("SET: " + t.FullName + "." + p.Name + " -> " + b);
}
cnt++;
}
}
catch { }
}
statsStatus = "полей стамины: " + cnt;
if (mode == 1) Plugin.L.LogInfo("Scan done, fields: " + cnt);
}
// ---------- спавн и список объектов ----------
void SpawnObject(GameObject go)
{
if (go == null) { spawnStatus = "объект больше не существует, обновите список"; return; }
Vector3 pos = Vector3.zero;
if (Camera.main != null) pos = Camera.main.transform.position + Camera.main.transform.forward * 3f;
try
{
RaycastHit hit;
if (Physics.Raycast(pos + Vector3.up * 2f, Vector3.down, out hit, 20f))
pos = hit.point + Vector3.up * 0.1f;
}
catch { }
var copy = UnityEngine.Object.Instantiate(go, pos, Quaternion.identity);
copy.SetActive(true);
SetSel(copy.transform);
spawnStatus = "создан: " + go.name;
Plugin.L.LogInfo("SPAWNED: " + go.name);
}
void RefreshFound()
{
found.Clear();
foundLabels.Clear();
listOffset = 0;
string f = filter.ToLower().Trim();
var all = Resources.FindObjectsOfTypeAll<GameObject>();
var seen = new HashSet<string>();
var tmp = new List<KeyValuePair<string, GameObject>>();
foreach (var go in all)
{
if (go == null) continue;
try
{
string n = go.name;
if (n.Length == 0) continue;
if (f.Length > 0 && !n.ToLower().Contains(f)) continue;
if (rootOnly && go.transform.parent != null) continue;
bool prefab = !go.scene.IsValid();
string label = (prefab ? "[П] " : "[С] ") + n;
if (!seen.Add(label)) continue;
tmp.Add(new KeyValuePair<string, GameObject>(label, go));
if (tmp.Count >= 3000) break;
}
catch { }
}
tmp.Sort((a, b) => string.Compare(a.Key, b.Key, StringComparison.OrdinalIgnoreCase));
foreach (var kv in tmp) { foundLabels.Add(kv.Key); found.Add(kv.Value); }
spawnStatus = "в списке: " + found.Count;
}
// ---------- интерфейс ----------
void LogGuiError(string section, Exception e)
{
string key = section + ":" + e.GetType().Name + ":" + e.Message;
if (guiErrors.Add(key)) Plugin.L.LogInfo("GUI error in " + section + ": " + e.GetType().Name + ": " + e.Message);
}
void OnGUI()
{
try
{
float fk = (Time.time - explodeTime) / 0.8f;
if (fk >= 0f && fk < 1f)
{
GUI.color = new Color(1f, 0.6f, 0.1f, 1f - fk);
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), Texture2D.whiteTexture);
GUI.color = Color.white;
}
if (open || fly || drone || stamina || aiOff)
{
string st = "Полёт:" + (fly ? "да" : "нет") + " Игрок:" + (player != null ? "найден" : "НЕТ")
+ " Шахед:" + (drone ? ("да, v=" + (int)droneSpeed) : "нет") + " ИИ грабителей:" + (aiOff ? "выкл" : "вкл") + "\n"
+ "Удар:" + lastHit + " " + soundStatus + "\n"
+ statsStatus + " Сеть: " + netStatus + "\n" + selInfo;
GUI.Label(new Rect(Screen.width - 640, 10, 630, 90), st);
}
}
catch (Exception e) { LogGuiError("status", e); }
if (!open) return;
try { DrawLeft(); } catch (Exception e) { LogGuiError("left", e); }
try { DrawMiddle(); } catch (Exception e) { LogGuiError("middle", e); }
try { DrawRight(); } catch (Exception e) { LogGuiError("right", e); }
}
// колонка 1: полёт, шахед, стамина, сцены
void DrawLeft()
{
float x = 20, y = 20, w = 320, h = 26;
GUI.Box(new Rect(10, 10, w + 20, 700), "Меню (Insert)");
y += 20;
bool nf = GUI.Toggle(new Rect(x, y, w, h), fly, " Полёт / ноклип (F6)"); y += h;
if (nf != fly) SetFly(nf);
GUI.Label(new Rect(x, y, w, 20), "Скорость полёта: " + (int)flySpeed); y += 20;
flySpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), flySpeed, 3f, 100f); y += 26;
bool nd = GUI.Toggle(new Rect(x, y, w, h), drone, " Режим шахеда (F8)"); y += h;
if (nd != drone) SetDrone(nd);
GUI.Label(new Rect(x, y, w, 20), "Скорость дрона: " + (int)droneSpeed + " (W/S)"); y += 20;
droneSpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), droneSpeed, 5f, 80f); y += 22;
GUI.Label(new Rect(x, y, w, 20), "Порог взрыва: " + (int)crashSpeed); y += 20;
crashSpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), crashSpeed, 5f, 80f); y += 22;
GUI.Label(new Rect(x, y, w, 20), "Вращение: " + (int)spinSpeed + " град/с"); y += 20;
spinSpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), spinSpeed, 0f, 1080f); y += 26;
if (GUI.Button(new Rect(x, y, 150, 24), "Тест взрыва"))
{
Vector3 p = Camera.main != null ? Camera.main.transform.position + Camera.main.transform.forward * 5f : Vector3.zero;
Explode(p);
exploding = false;
}
if (GUI.Button(new Rect(x + 170, y, 150, 24), "Тест музыки")) { PlayMusic(); Send("BUZZ_ON"); }
y += 28;
if (GUI.Button(new Rect(x, y, w, 24), "Стоп звук")) { StopDroneSounds(); Send("BUZZ_OFF"); }
y += 34;
stamina = GUI.Toggle(new Rect(x, y, w, h), stamina, " Бесконечная стамина"); y += h;
if (GUI.Button(new Rect(x, y, w, 24), "Найти поля стамины (в лог)")) ApplyStats(1);
y += 36;
GUI.Label(new Rect(x, y, w, h), "Сцены (индекс в билде):"); y += 22;
int count = SceneManager.sceneCountInBuildSettings;
for (int i = scenePage * 4; i < Math.Min(count, scenePage * 4 + 4); i++)
{
string path = SceneUtility.GetScenePathByBuildIndex(i);
string nm = Path.GetFileNameWithoutExtension(path);
if (GUI.Button(new Rect(x, y, w, 24), i + ": " + nm)) SceneManager.LoadScene(i);
y += 26;
}
if (GUI.Button(new Rect(x, y, 150, 24), "< Назад")) scenePage = Math.Max(0, scenePage - 1);
if (GUI.Button(new Rect(x + 170, y, 150, 24), "Вперёд >")) { if ((scenePage + 1) * 4 < count) scenePage++; }
}
// колонка 2: размер, грабители, сеть
void DrawMiddle()
{
float x2 = 380, y2 = 20, w2 = 320, h = 26;
GUI.Box(new Rect(370, 10, w2 + 20, 700), "Размер, грабители, сеть");
y2 += 20;
GUI.Label(new Rect(x2, y2, w2, 20), "Мой размер: " + selfScale.ToString("0.00")); y2 += 20;
float nsf = GUI.HorizontalSlider(new Rect(x2, y2, 230, 20), selfScale, 0.1f, 5f);
if (Mathf.Abs(nsf - selfScale) > 0.001f)
{
selfScale = nsf;
if (EnsurePlayer()) SetScale(player, selfScale);
}
if (GUI.Button(new Rect(x2 + 240, y2 - 3, 80, 24), "Сброс"))
{
selfScale = 1f;
if (EnsurePlayer()) SetScale(player, 1f);
}
y2 += 30;
GUI.Label(new Rect(x2, y2, w2, 22), selInfo); y2 += 24;
if (GUI.Button(new Rect(x2, y2, 155, 24), "Выбрать (F9)")) SelectUnderCrosshair();
if (GUI.Button(new Rect(x2 + 165, y2, 155, 24), "Родитель ↑")) { if (sel != null && sel.parent != null) SetSel(sel.parent); }
y2 += 28;
if (GUI.Button(new Rect(x2, y2, 155, 24), "Корень")) { if (sel != null) SetSel(sel.root); }
if (GUI.Button(new Rect(x2 + 165, y2, 155, 24), "Игрок = выбранный"))
{
if (sel != null)
{
player = sel;
cc = sel.GetComponent<CharacterController>();
rb = sel.GetComponent<Rigidbody>();
selInfo = "Игрок теперь: " + sel.name;
}
}
y2 += 30;
GUI.Label(new Rect(x2, y2, w2, 20), "Размер выбранного: " + selScale.ToString("0.00")); y2 += 20;
float nss = GUI.HorizontalSlider(new Rect(x2, y2, 230, 20), selScale, 0.1f, 8f);
if (Mathf.Abs(nss - selScale) > 0.001f)
{
selScale = nss;
if (sel != null) SetScale(sel, selScale);
}
if (GUI.Button(new Rect(x2 + 240, y2 - 3, 80, 24), "Сброс"))
{
selScale = 1f;
if (sel != null) SetScale(sel, 1f);
}
y2 += 34;
GUI.Label(new Rect(x2, y2, w2, 20), "Имя грабителя (нажмите и печатайте):"); y2 += 22;
TextBox(new Rect(x2, y2, w2, 24), robberKey, 2, "часть названия"); y2 += 28;
if (lastRobberKey != robberKey) { lastRobberKey = robberKey; robbersFound = FindRobbers().Count; }
if (GUI.Button(new Rect(x2, y2, w2, 24), "Это грабитель (из выбранного)"))
{
if (sel != null) { robberKey = sel.root.name.ToLower(); lastRobberKey = robberKey; robbersFound = FindRobbers().Count; }
}
y2 += 28;
GUI.Label(new Rect(x2, y2, w2, 20), "Найдено в сцене: " + robbersFound); y2 += 22;
GUI.Label(new Rect(x2, y2, w2, 20), "Размер грабителей: " + robberScale.ToString("0.00")); y2 += 20;
float nsr = GUI.HorizontalSlider(new Rect(x2, y2, 230, 20), robberScale, 0.1f, 8f);
if (Mathf.Abs(nsr - robberScale) > 0.001f)
{
robberScale = nsr;
var rs = FindRobbers();
robbersFound = rs.Count;
foreach (var go in rs) SetScale(go.transform, robberScale);
}
if (GUI.Button(new Rect(x2 + 240, y2 - 3, 80, 24), "Сброс"))
{
robberScale = 1f;
foreach (var go in FindRobbers()) SetScale(go.transform, 1f);
}
y2 += 30;
bool nai = GUI.Toggle(new Rect(x2, y2, w2, h), aiOff, " Выключить ИИ грабителей"); y2 += h;
if (nai != aiOff)
{
aiOff = nai;
if (aiOff) { var rs2 = FindRobbers(); robbersFound = rs2.Count; ApplyRobberAI(rs2); }
else RestoreRobberAI();
}
y2 += 12;
GUI.Label(new Rect(x2, y2, w2, 22), "Звук для друга (у него тот же плагин):"); y2 += 22;
GUI.Label(new Rect(x2, y2, w2, 22), "IP друга (VPN или белый IP):"); y2 += 22;
TextBox(new Rect(x2, y2, w2, 24), friendIp, 3, "например 26.12.34.56"); y2 += 28;
bool nn = GUI.Toggle(new Rect(x2, y2, w2, h), netOn, " Включить связь (UDP " + netPort + ")"); y2 += h;
if (nn && !netOn) StartNet();
if (!nn && netOn) StopNet();
GUI.Label(new Rect(x2, y2, w2, 22), "Статус: " + netStatus);
}
// колонка 3: все объекты
void DrawRight()
{
float x3 = 730, y3 = 20, w3 = 340, h = 26;
GUI.Box(new Rect(720, 10, w3 + 20, 700), "Все объекты (клик = создать)");
y3 += 20;
GUI.Label(new Rect(x3, y3, w3, 20), "Поиск по имени (пусто = все):"); y3 += 22;
TextBox(new Rect(x3, y3, w3, 24), filter, 1, "нажмите и печатайте"); y3 += 28;
rootOnly = GUI.Toggle(new Rect(x3, y3, w3, h), rootOnly, " Только корневые (без вложенных)"); y3 += h;
bool refresh = GUI.Button(new Rect(x3, y3, w3, 24), "Обновить список"); y3 += 28;
if (refresh || lastFilter != filter || lastRootOnly != rootOnly || lastFilter == null)
{
lastFilter = filter;
lastRootOnly = rootOnly;
RefreshFound();
}
int from = found.Count == 0 ? 0 : listOffset + 1;
int to = Math.Min(found.Count, listOffset + listRows);
GUI.Label(new Rect(x3, y3, w3, 22), spawnStatus + " (показано " + from + "-" + to + ") " + (found.Count > listRows ? "колесо мыши листает" : "")); y3 += 24;
if (GUI.Button(new Rect(x3, y3, 160, 22), "▲ вверх"))
listOffset = Mathf.Max(0, listOffset - 10);
if (GUI.Button(new Rect(x3 + 170, y3, 160, 22), "▼ вниз"))
listOffset = Mathf.Clamp(listOffset + 10, 0, Mathf.Max(0, found.Count - listRows));
y3 += 26;
float rowH = 26f;
listRect = new Rect(x3, y3, w3, listRows * rowH);
for (int i = 0; i < listRows; i++)
{
int idx = listOffset + i;
if (idx >= found.Count) break;
if (GUI.Button(new Rect(x3, y3 + i * rowH, w3, rowH - 2), foundLabels[idx]))
{
SpawnObject(found[idx]);
break;
}
}
}
}