Иконка ресурса

FREE DeepSeaAdmin : 1.1.0 1.1.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using UnityEngine.UI;

namespace Oxide.Plugins
{
    [Info("DeepSeaAdmin", "jerky/claude", "1.1.0")]
    [Description("DeepSea管理パネル - サーバー管理者向けDeepSea設定UI")]
    class DeepSeaAdmin : RustPlugin
    {
        const string PERM   = "deepseaadmin.use";
        const string UI     = "DSM_UI";
        const string SCROLL = "DSM_SC";
        int _row;
        ulong _currentBuildPlayer;

        // Per-player y-offsets of bool rows (for selective update without full re-render)
        readonly Dictionary<ulong, Dictionary<string, float>> _boolY =
            new Dictionary<ulong, Dictionary<string, float>>();

        // ─── Config ───────────────────────────────────────────────────────────
        class Cfg
        {
            // Basic
            public bool  allVehicles = false, swimmers = false,
                         blockBuilding = true, logs = false, openOnWipe = false,
                         debugPortal = false, npcSpawnOnIslands = true,
                         fogOfWar = true;
            // Timing
            public float wipeDuration = 10800, cooldownMin = 5400, cooldownMax = 5400,
                         wipeEndPhase = 1800, radPhase = 300,
                         entityInterval = 5, groupInterval = 0.15f;
            // Islands
            public int   islandCount = 6;
            public float islandRadius = 300, islandEdge = 750, islandMinDist = 600,
                         islandPortalDist = 750;
            // Floating City
            public int   cityCount = 1;
            public float cityRadius = 500, cityEdge = 1500, cityMinDist = 1500;
            // Ghost Ships
            public int   ghostCount = 4;
            public float ghostRadius = 80, ghostEdge = 450, ghostMinDist = 400;
            // RHIB
            public int   rhibCount = 4;
            public float rhibRadius = 10, rhibEdge = 1150, rhibMinDist = 300;
            // Misc
            public int   crateCount = 1, portalDir = 0;

            public Cfg Clone() => JsonConvert.DeserializeObject<Cfg>(JsonConvert.SerializeObject(this));
        }

        Cfg cfg;
        bool _firstRun;
        // Per-player pending config (discarded on close without Apply)
        readonly Dictionary<ulong, Cfg> _pending = new Dictionary<ulong, Cfg>();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try { cfg = Config.ReadObject<Cfg>(); if (cfg == null) cfg = new Cfg(); }
            catch { cfg = new Cfg(); }
        }
        protected override void SaveConfig()        => Config.WriteObject(cfg);
        protected override void LoadDefaultConfig() { cfg = new Cfg(); _firstRun = true; }

        // ─── Lifecycle ────────────────────────────────────────────────────────
        void Init() => permission.RegisterPermission(PERM, this);
        void OnServerInitialized()
        {
            if (_firstRun)
            {
                cfg = ReadFromServer();
                SaveConfig();
                _firstRun = false;
                Puts("First run: saved server values to config.");
            }
            else
            {
                ApplyAll(cfg);
            }
        }

        // ─── Read current server values into Cfg ──────────────────────────────
        Cfg ReadFromServer()
        {
            string V(string name)
            {
                var cmd = ConsoleSystem.Index.Server.Find(name);
                return cmd?.String ?? "";
            }
            bool  B(string name) => !V(name).Equals("False", StringComparison.OrdinalIgnoreCase);
            float F(string name) { float v; return float.TryParse(V(name), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out v) ? v : 0f; }
            int   I(string name) { int   v; return int.TryParse(V(name), out v) ? v : 0; }
            return new Cfg
            {
                allVehicles       = B("deepsea.allow_all_vehicles"),
                swimmers          = B("deepsea.allow_swimmers"),
                blockBuilding     = B("deepsea.block_building"),
                logs              = B("deepsea.logs"),
                openOnWipe        = B("deepsea.openonserverwipe"),
                debugPortal       = B("deepsea.debug_portal_spawnattempts"),
                npcSpawnOnIslands = B("ai.npc_spawn_on_deep_sea_islands"),
                fogOfWar          = B("server.deepseafogofwar"),
                wipeDuration      = F("deepsea.wipeduration"),
                cooldownMin       = F("deepsea.wipecooldownmin"),
                cooldownMax       = F("deepsea.wipecooldownmax"),
                wipeEndPhase      = F("deepsea.wipeendphaseduration"),
                radPhase          = F("deepsea.wiperadiationphaseduration"),
                entityInterval    = F("deepsea.entities_spawninterval"),
                groupInterval     = F("deepsea.spawngroups_spawninterval"),
                islandCount       = I("deepsea.island_count"),
                islandRadius      = F("deepsea.island_radius"),
                islandEdge        = F("deepsea.island_edgemargin"),
                islandMinDist     = F("deepsea.island_mindist"),
                islandPortalDist  = F("deepsea.island_portal_terrain_distance"),
                cityCount         = I("deepsea.floatingcity_count"),
                cityRadius        = F("deepsea.floatingcity_radius"),
                cityEdge          = F("deepsea.floatingcity_edgemargin"),
                cityMinDist       = F("deepsea.floatingcity_mindist"),
                ghostCount        = I("deepsea.ghostship_count"),
                ghostRadius       = F("deepsea.ghostship_radius"),
                ghostEdge         = F("deepsea.ghostship_edgemargin"),
                ghostMinDist      = F("deepsea.ghostship_mindist"),
                rhibCount         = I("deepsea.rhib_count"),
                rhibRadius        = F("deepsea.rhib_radius"),
                rhibEdge          = F("deepsea.rhib_edgemargin"),
                rhibMinDist       = F("deepsea.rhib_mindist"),
                crateCount        = I("deepsea.hackablecrate_count"),
                portalDir         = I("deepsea.forceentranceportaldirection"),
            };
        }

        void Unload()
        {
            foreach (var p in BasePlayer.activePlayerList)
                CuiHelper.DestroyUi(p, UI);
        }

        bool HasPerm(BasePlayer p) => p.IsAdmin || permission.UserHasPermission(p.UserIDString, PERM);

        Cfg PendingOf(BasePlayer p)
        {
            if (!_pending.TryGetValue(p.userID, out var c)) { c = cfg.Clone(); _pending[p.userID] = c; }
            return c;
        }

        // ─── Apply all settings to server ────────────────────────────────────
        void ApplyAll(Cfg c)
        {
            SRun($"deepsea.allow_all_vehicles {B(c.allVehicles)}");
            SRun($"deepsea.allow_swimmers {B(c.swimmers)}");
            SRun($"deepsea.block_building {B(c.blockBuilding)}");
            SRun($"deepsea.logs {B(c.logs)}");
            SRun($"deepsea.openonserverwipe {B(c.openOnWipe)}");
            SRun($"deepsea.debug_portal_spawnattempts {B(c.debugPortal)}");
            SRun($"ai.npc_spawn_on_deep_sea_islands {B(c.npcSpawnOnIslands)}");
            SRun($"server.deepseafogofwar {B(c.fogOfWar)}");
            SRun($"deepsea.wipeduration {c.wipeDuration}");
            SRun($"deepsea.wipecooldownmin {c.cooldownMin}");
            SRun($"deepsea.wipecooldownmax {c.cooldownMax}");
            SRun($"deepsea.wipeendphaseduration {c.wipeEndPhase}");
            SRun($"deepsea.wiperadiationphaseduration {c.radPhase}");
            SRun($"deepsea.entities_spawninterval {c.entityInterval}");
            SRun($"deepsea.spawngroups_spawninterval {c.groupInterval}");
            SRun($"deepsea.island_count {c.islandCount}");
            SRun($"deepsea.island_radius {c.islandRadius}");
            SRun($"deepsea.island_edgemargin {c.islandEdge}");
            SRun($"deepsea.island_mindist {c.islandMinDist}");
            SRun($"deepsea.island_portal_terrain_distance {c.islandPortalDist}");
            SRun($"deepsea.floatingcity_count {c.cityCount}");
            SRun($"deepsea.floatingcity_radius {c.cityRadius}");
            SRun($"deepsea.floatingcity_edgemargin {c.cityEdge}");
            SRun($"deepsea.floatingcity_mindist {c.cityMinDist}");
            SRun($"deepsea.ghostship_count {c.ghostCount}");
            SRun($"deepsea.ghostship_radius {c.ghostRadius}");
            SRun($"deepsea.ghostship_edgemargin {c.ghostEdge}");
            SRun($"deepsea.ghostship_mindist {c.ghostMinDist}");
            SRun($"deepsea.rhib_count {c.rhibCount}");
            SRun($"deepsea.rhib_radius {c.rhibRadius}");
            SRun($"deepsea.rhib_edgemargin {c.rhibEdge}");
            SRun($"deepsea.rhib_mindist {c.rhibMinDist}");
            SRun($"deepsea.hackablecrate_count {c.crateCount}");
            SRun($"deepsea.forceentranceportaldirection {c.portalDir}");
        }

        string B(bool v) => v ? "true" : "false";
        void SRun(string cmd) => ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), cmd);
        string SGet(string cmd) => ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), cmd) ?? "";

        // ─── Chat Commands ────────────────────────────────────────────────────
        [ChatCommand("dsm")]
        void CmdDsm(BasePlayer p, string _, string[] args)
        {
            if (!HasPerm(p)) { Chat(p, "権限がありません。"); return; }
            if (_firstRun)
            {
                SaveConfig();
                _firstRun = false;
                Puts("First run: saved default config. Server settings unchanged.");
            }
            if (SGet("deepsea.enabled").Equals("False", StringComparison.OrdinalIgnoreCase))
            { Chat(p, "DeepSeaが無効です。(deepsea.enabled = false)"); return; }
            OpenUI(p);
        }

        // ─── Console Commands ─────────────────────────────────────────────────
        [ConsoleCommand("dsm.close")]
        void CmdClose(ConsoleSystem.Arg a)
        {
            var p = a.Player();
            if (p == null) return;
            _pending.Remove(p.userID);   // discard pending → revert
            CuiHelper.DestroyUi(p, UI);
        }

        [ConsoleCommand("dsm.toggle")]
        void CmdToggle(ConsoleSystem.Arg a)
        {
            var p = a.Player();
            if (p == null || !HasPerm(p)) return;
            var c = PendingOf(p);
            string field = a.GetString(0);
            switch (field)
            {
                case "allvehicles":   c.allVehicles      = !c.allVehicles;      break;
                case "swimmers":      c.swimmers          = !c.swimmers;         break;
                case "blockbuilding": c.blockBuilding     = !c.blockBuilding;    break;
                case "logs":          c.logs              = !c.logs;             break;
                case "openwipe":      c.openOnWipe        = !c.openOnWipe;       break;
                case "debugportal":   c.debugPortal       = !c.debugPortal;      break;
                case "npcspawn":      c.npcSpawnOnIslands = !c.npcSpawnOnIslands;break;
                case "fogofwar":      c.fogOfWar          = !c.fogOfWar;         break;
            }
            bool newVal = BoolFromField(field, c);
            bool interactive = !SGet("deepsea.enabled").Equals("False", StringComparison.OrdinalIgnoreCase);
            if (interactive &&
                _boolY.TryGetValue(p.userID, out var yMap) &&
                yMap.TryGetValue(field, out float y))
            {
                CuiHelper.DestroyUi(p, $"DSM_CB_{field}");
                var upd = new CuiElementContainer();
                string cc = newVal ? "0.25 0.58 0.18 1.0" : "0.26 0.26 0.24 1.0";
                upd.Add(new CuiButton
                {
                    Button = { Command = $"dsm.toggle {field}", Color = cc },
                    RectTransform = { AnchorMin = "0.70 1", AnchorMax = "1 1",
                        OffsetMin = $"4 {-(y+RH-5)}", OffsetMax = $"-6 {-(y+5)}" },
                    Text = { Text = newVal ? "✓  ON" : "✗  OFF", FontSize = 13,
                        Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" }
                }, SCROLL, $"DSM_CB_{field}");
                CuiHelper.AddUi(p, upd);
            }
            else
            {
                OpenUI(p); // fallback
            }
        }

        bool BoolFromField(string field, Cfg c)
        {
            return field switch
            {
                "allvehicles"   => c.allVehicles,
                "swimmers"      => c.swimmers,
                "blockbuilding" => c.blockBuilding,
                "logs"          => c.logs,
                "openwipe"      => c.openOnWipe,
                "debugportal"   => c.debugPortal,
                "npcspawn"      => c.npcSpawnOnIslands,
                "fogofwar"      => c.fogOfWar,
                _               => false
            };
        }

[ConsoleCommand("dsm.set")]
        void CmdSet(ConsoleSystem.Arg a)
        {
            var p = a.Player();
            if (p == null || !HasPerm(p)) return;
            var c = PendingOf(p);
            if (SGet("deepsea.enabled").Equals("False", StringComparison.OrdinalIgnoreCase)) return;
            string f = a.GetString(0), v = a.GetString(1);
            float fv; int iv;
            switch (f)
            {
                case "wipeduration":   if (float.TryParse(v, out fv)) c.wipeDuration    = fv; break;
                case "cooldownmin":    if (float.TryParse(v, out fv)) c.cooldownMin      = fv; break;
                case "cooldownmax":    if (float.TryParse(v, out fv)) c.cooldownMax      = fv; break;
                case "wipeendphase":   if (float.TryParse(v, out fv)) c.wipeEndPhase    = fv; break;
                case "radphase":       if (float.TryParse(v, out fv)) c.radPhase         = fv; break;
                case "entityinterval": if (float.TryParse(v, out fv)) c.entityInterval  = fv; break;
                case "groupinterval":  if (float.TryParse(v, out fv)) c.groupInterval   = fv; break;
                case "islandcount":    if (int.TryParse(v,   out iv)) c.islandCount     = iv; break;
                case "islandradius":   if (float.TryParse(v, out fv)) c.islandRadius    = fv; break;
                case "islandedge":     if (float.TryParse(v, out fv)) c.islandEdge      = fv; break;
                case "islandmindist":  if (float.TryParse(v, out fv)) c.islandMinDist   = fv; break;
                case "islandpdist":    if (float.TryParse(v, out fv)) c.islandPortalDist= fv; break;
                case "citycount":      if (int.TryParse(v,   out iv)) c.cityCount       = iv; break;
                case "cityradius":     if (float.TryParse(v, out fv)) c.cityRadius      = fv; break;
                case "cityedge":       if (float.TryParse(v, out fv)) c.cityEdge        = fv; break;
                case "citymindist":    if (float.TryParse(v, out fv)) c.cityMinDist     = fv; break;
                case "ghostcount":     if (int.TryParse(v,   out iv)) c.ghostCount      = iv; break;
                case "ghostradius":    if (float.TryParse(v, out fv)) c.ghostRadius     = fv; break;
                case "ghostedge":      if (float.TryParse(v, out fv)) c.ghostEdge       = fv; break;
                case "ghostmindist":   if (float.TryParse(v, out fv)) c.ghostMinDist    = fv; break;
                case "rhibcount":      if (int.TryParse(v,   out iv)) c.rhibCount       = iv; break;
                case "rhibradius":     if (float.TryParse(v, out fv)) c.rhibRadius      = fv; break;
                case "rhibedge":       if (float.TryParse(v, out fv)) c.rhibEdge        = fv; break;
                case "rhibmindist":    if (float.TryParse(v, out fv)) c.rhibMinDist     = fv; break;
                case "cratecount":     if (int.TryParse(v,   out iv)) c.crateCount      = iv; break;
                case "portaldir":      if (int.TryParse(v,   out iv)) c.portalDir       = iv; break;
            }
            // No UI refresh on text input (cursor would reset)
        }

        [ConsoleCommand("dsm.apply")]
        void CmdApply(ConsoleSystem.Arg a)
        {
            var p = a.Player();
            if (p == null || !HasPerm(p)) return;
            cfg = PendingOf(p).Clone();
            SaveConfig();
            ApplyAll(cfg);
            _pending[p.userID] = cfg.Clone(); // sync pending with saved
            Chat(p, "設定を反映しました。");
            OpenUI(p);
        }

        [ConsoleCommand("dsm.action")]
        void CmdAction(ConsoleSystem.Arg a)
        {
            var p = a.Player();
            if (p == null || !HasPerm(p)) return;
            string act = a.GetString(0);

            switch (act)
            {
                case "open":
                    SRun("deepsea.open");
                    Chat(p, "DeepSeaの強制オープンを実行しました。オープン完了までしばらくお待ちください。状況はステータスで確認できます。");
                    break;

                case "close":
                    _pending.Remove(p.userID);
                    CuiHelper.DestroyUi(p, UI);
                    Chat(p, "<color=#f5c842>全プレイヤーをDeepSeaから退出させています...</color>");
                    WaitAndClose(p);
                    break;

                case "spawnisland":
                case "spawnghostship":
                case "spawncity":
                case "spawnrhib":
                    SpawnAtPlayer(p, act);
                    break;

                case "status":
                    SendStatusToChat(p, "deepsea.status", "DeepSea ステータス");
                    break;

                case "entitycount":
                    SendStatusToChat(p, "deepsea.printentitycount", "エンティティ数");
                    break;

                case "loot":
                    SendStatusToChat(p, "deepsea.printloot", "戦利品情報");
                    break;

                case "timetowipe":
                    SendStatusToChat(p, "deepsea.printtimetowipe", "ワイプまでの残り時間");
                    break;

                case "enterdeepsea":
                    if (!p.IsAdmin) { Chat(p, "この操作にはサーバー管理者権限が必要です。"); return; }
                    p.SendConsoleCommand("deepsea.enterdeepsea");
                    Chat(p, "DeepSeaへ移動します...");
                    break;

                case "leavedeepsea":
                    if (!p.IsAdmin) { Chat(p, "この操作にはサーバー管理者権限が必要です。"); return; }
                    p.SendConsoleCommand("deepsea.leavedeepsea");
                    Chat(p, "DeepSeaから出ます...");
                    break;
            }
        }

        void SendStatusToChat(BasePlayer p, string cmd, string title)
        {
            string result = SGet(cmd);
            if (string.IsNullOrEmpty(result))
                result = "(データなし。DeepSeaが起動していない可能性があります)";
            Chat(p, $"<color=#f5c842>── {title} ──</color>\n{result}");
        }

        void SpawnAtPlayer(BasePlayer p, string act)
        {
            if (!p.IsAdmin)
            {
                Chat(p, "スポーン操作にはサーバー管理者権限が必要です。");
                return;
            }
            string cmd = null;
            string name = "";
            switch (act)
            {
                case "spawnisland":    cmd = "deepsea.spawnislandhere";        name = "島"; break;
                case "spawnghostship": cmd = "deepsea.spawnghostshiphere";     name = "幽霊船"; break;
                case "spawncity":      cmd = "deepsea.spawnfloatingcityhere";  name = "浮遊都市"; break;
                case "spawnrhib":      cmd = "deepsea.spawnrhibgrouphere";     name = "RHIBグループ"; break;
            }
            if (cmd == null) return;
            Chat(p, $"現在地に{name}を生成しています...");
            p.SendConsoleCommand(cmd);
            timer.Once(2f, () => { if (p != null && p.IsConnected) Chat(p, $"<color=#7fc87f>{name}の生成が完了しました。</color>"); });
        }

        void Chat(BasePlayer p, string msg) => p.ChatMessage($"<color=#c8a96e>[DSM]</color> {msg}");

        void WaitAndClose(BasePlayer initiator)
        {
            foreach (var bp in BasePlayer.activePlayerList)
                bp.SendConsoleCommand("deepsea.leavedeepsea");

            timer.Once(5f, () =>
            {
                SRun("deepsea.close");
                if (initiator != null && initiator.IsConnected)
                    Chat(initiator, "DeepSeaを強制クローズしました。");
            });
        }

        // ─── UI ───────────────────────────────────────────────────────────────
        const float RH = 38f;
        const float SH = 33f;

        // DeepSeaが稼働中か判定(status出力の "Open: True" で確認)
        bool IsDeepSeaActive()
        {
            string s = SGet("deepsea.status");
            if (string.IsNullOrEmpty(s)) return false;
            return s.Contains("Open: True");
        }

        void OpenUI(BasePlayer player)
        {
            CuiHelper.DestroyUi(player, UI);
            _row = 0;
            _currentBuildPlayer = player.userID;
            var cur = PendingOf(player);
            bool en = !SGet("deepsea.enabled").Equals("False", StringComparison.OrdinalIgnoreCase);
            bool active = IsDeepSeaActive();
            var c = new CuiElementContainer();

            // Outer panel — position managed client-side by CuiDraggableComponent
            c.Add(new CuiElement
            {
                Name = UI, Parent = "Overlay",
                Components =
                {
                    new CuiImageComponent { Color ="0.08 0.08 0.07 0.97" },
                    new CuiRectTransformComponent { AnchorMin = "0.15 0.12", AnchorMax = "0.85 0.88" },
                    new CuiNeedsCursorComponent(),
                    new CuiDraggableComponent()
                }
            });

            // Title bar
            c.Add(new CuiElement { Parent = UI, Components = {
                new CuiImageComponent { Color ="0.18 0.15 0.10 1.0" },
                new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "0 -44", OffsetMax = "0 0" }
            }});
            c.Add(new CuiLabel { Text = { Text = "Deep Sea Manager", FontSize = 17, Align = TextAnchor.MiddleCenter, Color = "0.95 0.85 0.58 1.0" },
                RectTransform = { AnchorMin = "0 1", AnchorMax = "0.92 1", OffsetMin = "0 -44", OffsetMax = "0 0" }}, UI);
            c.Add(new CuiButton { Button = { Command = "dsm.close", Color = "0.58 0.15 0.10 0.9" },
                RectTransform = { AnchorMin = "1 1", AnchorMax = "1 1", OffsetMin = "-48 -40", OffsetMax = "-4 -4" },
                Text = { Text = "✕", FontSize = 16, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" }}, UI);

            // Status bar (below title, 36px)
            string statusBgColor  = active ? "0.10 0.22 0.10 1.0" : "0.22 0.10 0.08 1.0";
            string statusDotColor = active ? "0.30 0.80 0.30 1.0" : "0.85 0.28 0.20 1.0";
            string statusText     = active ? "● 稼働中" : "● 停止中";
            c.Add(new CuiElement { Parent = UI, Components = {
                new CuiImageComponent { Color =statusBgColor },
                new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "0 -80", OffsetMax = "0 -44" }
            }});
            c.Add(new CuiLabel { Text = { Text = statusText, FontSize = 13, Align = TextAnchor.MiddleLeft, Color = statusDotColor },
                RectTransform = { AnchorMin = "0 1", AnchorMax = "0.35 1", OffsetMin = "12 -80", OffsetMax = "0 -44" }}, UI);

            if (active)
            {
                // Enter / Leave ボタン(管理者のみ機能)
                c.Add(new CuiButton { Button = { Command = "dsm.action enterdeepsea", Color = "0.20 0.38 0.52 1.0" },
                    RectTransform = { AnchorMin = "1 1", AnchorMax = "1 1", OffsetMin = "-310 -77", OffsetMax = "-160 -47" },
                    Text = { Text = "DeepSeaへ移動", FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.95 0.92 0.88 1.0" }}, UI);
                c.Add(new CuiButton { Button = { Command = "dsm.action leavedeepsea", Color = "0.42 0.28 0.18 1.0" },
                    RectTransform = { AnchorMin = "1 1", AnchorMax = "1 1", OffsetMin = "-155 -77", OffsetMax = "-5 -47" },
                    Text = { Text = "DeepSeaから出る", FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.95 0.92 0.88 1.0" }}, UI);
            }

            // Bottom bar
            c.Add(new CuiElement { Parent = UI, Components = {
                new CuiImageComponent { Color ="0.12 0.11 0.09 1.0" },
                new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 0", OffsetMin = "0 0", OffsetMax = "0 48" }
            }});
            c.Add(new CuiButton { Button = { Command = "dsm.apply", Color = "0.20 0.50 0.14 1.0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "8 7", OffsetMax = "210 41" },
                Text = { Text = "▶ 反映 (Apply)", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" }}, UI);
            c.Add(new CuiLabel { Text = { Text = "※閉じると変更は破棄されます", FontSize = 10, Align = TextAnchor.MiddleLeft, Color = "0.65 0.62 0.55 1.0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "0.5 0", OffsetMin = "218 7", OffsetMax = "0 41" }}, UI);
            c.Add(new CuiButton { Button = { Command = "dsm.close", Color = "0.30 0.28 0.25 1.0" },
                RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-148 7", OffsetMax = "-8 41" },
                Text = { Text = "閉じる", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "0.80 0.78 0.74 1.0" }}, UI);

            // Scroll view
            c.Add(new CuiElement { Name = SCROLL, Parent = UI, Components = {
                new CuiImageComponent { Color = "0.05 0.05 0.04 0.4" },
                new CuiScrollViewComponent
                {
                    ContentTransform = new CuiRectTransform { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "0 -2400", OffsetMax = "-10 0" },
                    Vertical = true, Horizontal = false,
                    MovementType = ScrollRect.MovementType.Elastic,
                    Elasticity = 0.1f, Inertia = true, DecelerationRate = 0.2f,
                    ScrollSensitivity = 35f,
                    VerticalScrollbar = new CuiScrollbar { Size = 8, AutoHide = false }
                },
                new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 48", OffsetMax = "0 -80" },
                new CuiNeedsCursorComponent()
            }});

            // ── Content rows ──
            float y = 0;

            y = SecHead(c, "■ 基本設定 / Basic", y);
            y = BoolRow(c, "全車両の通行許可",       "allvehicles",   cur.allVehicles,       y, en);
            y = BoolRow(c, "水泳での入場許可",       "swimmers",      cur.swimmers,           y, en);
            y = BoolRow(c, "建築禁止",               "blockbuilding", cur.blockBuilding,      y, en);
            y = BoolRow(c, "ログ出力",               "logs",          cur.logs,               y, en);
            y = BoolRow(c, "ワイプ時に自動オープン", "openwipe",      cur.openOnWipe,         y, en);
            y = BoolRow(c, "島へのNPCスポーン",      "npcspawn",      cur.npcSpawnOnIslands,  y, en);
            y = BoolRow(c, "Fog of War",             "fogofwar",      cur.fogOfWar,           y, en);
            y = BoolRow(c, "ポータルスポーンデバッグ","debugportal",  cur.debugPortal,        y, en);

            y = SecHead(c, "■ 操作 / Actions", y);
            y = ActionRow(c, en, y);

            y = SecHead(c, "■ ステータス確認 / Status", y);
            y = StatusRow(c, y);

            y = SecHead(c, "■ タイミング / Timing", y);
            y = FloatRow(c, "ワイプ継続時間 (秒)",         "wipeduration",   cur.wipeDuration,   y, en);
            y = FloatRow(c, "次回オープン最短待機 (秒)",   "cooldownmin",    cur.cooldownMin,    y, en);
            y = FloatRow(c, "次回オープン最長待機 (秒)",   "cooldownmax",    cur.cooldownMax,    y, en);
            y = FloatRow(c, "最終フェーズ時間 (秒)",       "wipeendphase",   cur.wipeEndPhase,   y, en);
            y = FloatRow(c, "放射線フェーズ開始前 (秒)",   "radphase",       cur.radPhase,       y, en);
            y = FloatRow(c, "エンティティスポーン間隔 (秒)","entityinterval", cur.entityInterval, y, en);
            y = FloatRow(c, "スポーングループ間隔 (秒)",   "groupinterval",  cur.groupInterval,  y, en);

            y = SecHead(c, "■ 島の設定 / Islands", y);
            y = IntRow  (c, "島の数",               "islandcount",  cur.islandCount,      y, en);
            y = FloatRow(c, "島の半径",             "islandradius", cur.islandRadius,     y, en);
            y = FloatRow(c, "島の端余白",           "islandedge",   cur.islandEdge,       y, en);
            y = FloatRow(c, "島の最小距離",         "islandmindist",cur.islandMinDist,    y, en);
            y = FloatRow(c, "ポータルと地形の距離", "islandpdist",  cur.islandPortalDist, y, en);

            y = SecHead(c, "■ 浮遊都市 / Floating City", y);
            y = IntRow  (c, "浮遊都市の数",     "citycount",  cur.cityCount,  y, en);
            y = FloatRow(c, "浮遊都市の半径",   "cityradius", cur.cityRadius, y, en);
            y = FloatRow(c, "浮遊都市の端余白", "cityedge",   cur.cityEdge,   y, en);
            y = FloatRow(c, "浮遊都市の最小距離","citymindist",cur.cityMinDist,y, en);

            y = SecHead(c, "■ 幽霊船 / Ghost Ships", y);
            y = IntRow  (c, "幽霊船の数",     "ghostcount",  cur.ghostCount,  y, en);
            y = FloatRow(c, "幽霊船の半径",   "ghostradius", cur.ghostRadius, y, en);
            y = FloatRow(c, "幽霊船の端余白", "ghostedge",   cur.ghostEdge,   y, en);
            y = FloatRow(c, "幽霊船の最小距離","ghostmindist",cur.ghostMinDist,y, en);

            y = SecHead(c, "■ RHIB", y);
            y = IntRow  (c, "RHIBの数",      "rhibcount",  cur.rhibCount,  y, en);
            y = FloatRow(c, "RHIBの半径",    "rhibradius", cur.rhibRadius, y, en);
            y = FloatRow(c, "RHIBの端余白",  "rhibedge",   cur.rhibEdge,   y, en);
            y = FloatRow(c, "RHIBの最小距離","rhibmindist",cur.rhibMinDist,y, en);

            y = SecHead(c, "■ その他 / Misc", y);
            y = IntRow(c, "ハッカブルクレートの数",                           "cratecount", cur.crateCount, y, en);
            y = IntRow(c, "入口ポータル方向 (0=ランダム 1=北 2=東 3=南 4=西)","portaldir",  cur.portalDir,  y, en);

            CuiHelper.AddUi(player, c);
        }

        // ── UI helpers ────────────────────────────────────────────────────────

        float SecHead(CuiElementContainer c, string title, float y)
        {
            c.Add(new CuiElement { Parent = SCROLL, Components = {
                new CuiImageComponent { Color ="0.20 0.17 0.12 1.0" },
                new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"0 {-(y+SH)}", OffsetMax = $"0 {-y}" }
            }});
            c.Add(new CuiLabel { Text = { Text = title, FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "0.92 0.74 0.40 1.0", Font = "RobotoCondensed-Bold.ttf" },
                RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"10 {-(y+SH)}", OffsetMax = $"0 {-y}" }}, SCROLL);
            return y + SH + 1;
        }

        float WarnRow(CuiElementContainer c, string text, float y)
        {
            c.Add(new CuiLabel { Text = { Text = text, FontSize = 10, Align = TextAnchor.MiddleLeft, Color = "0.95 0.72 0.18 0.9" },
                RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"10 {-(y+24)}", OffsetMax = $"0 {-y}" }}, SCROLL);
            return y + 24;
        }

        float BoolRow(CuiElementContainer c, string label, string field, bool val, float y, bool interactive)
        {
            string bg = (_row++ % 2 == 0) ? "0.13 0.13 0.11 1.0" : "0.16 0.16 0.14 1.0";
            c.Add(new CuiElement { Parent = SCROLL, Components = {
                new CuiImageComponent { Color =bg },
                new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"0 {-(y+RH)}", OffsetMax = $"0 {-y}" }
            }});
            string tc = interactive ? "0.88 0.85 0.80 1.0" : "0.44 0.43 0.41 1.0";
            c.Add(new CuiLabel { Text = { Text = label, FontSize = 13, Align = TextAnchor.MiddleLeft, Color = tc },
                RectTransform = { AnchorMin = "0 1", AnchorMax = "0.70 1", OffsetMin = $"10 {-(y+RH)}", OffsetMax = $"0 {-y}" }}, SCROLL);

            if (interactive)
            {
                // Store y so dsm.toggle can selectively update just this button
                if (_currentBuildPlayer != 0)
                {
                    if (!_boolY.TryGetValue(_currentBuildPlayer, out var yMap))
                        _boolY[_currentBuildPlayer] = yMap = new Dictionary<string, float>();
                    yMap[field] = y;
                }
                string cc = val ? "0.25 0.58 0.18 1.0" : "0.26 0.26 0.24 1.0";
                c.Add(new CuiButton { Button = { Command = $"dsm.toggle {field}", Color = cc },
                    RectTransform = { AnchorMin = "0.70 1", AnchorMax = "1 1", OffsetMin = $"4 {-(y+RH-5)}", OffsetMax = $"-6 {-(y+5)}" },
                    Text = { Text = val ? "✓  ON" : "✗  OFF", FontSize = 13, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" }}, SCROLL, $"DSM_CB_{field}");
            }
            else
            {
                c.Add(new CuiElement { Parent = SCROLL, Components = {
                    new CuiImageComponent { Color ="0.20 0.20 0.18 1.0" },
                    new CuiRectTransformComponent { AnchorMin = "0.70 1", AnchorMax = "1 1", OffsetMin = $"4 {-(y+RH-5)}", OffsetMax = $"-6 {-(y+5)}" }
                }});
                c.Add(new CuiLabel { Text = { Text = val ? "✓  ON" : "✗  OFF", FontSize = 13, Align = TextAnchor.MiddleCenter, Color = "0.38 0.38 0.36 1.0" },
                    RectTransform = { AnchorMin = "0.70 1", AnchorMax = "1 1", OffsetMin = $"4 {-(y+RH-5)}", OffsetMax = $"-6 {-(y+5)}" }}, SCROLL);
            }
            return y + RH + 1;
        }

        float FloatRow(CuiElementContainer c, string label, string field, float val, float y, bool en)
            => InputRow(c, label, field, val.ToString("G"), y, en);

        float IntRow(CuiElementContainer c, string label, string field, int val, float y, bool en)
            => InputRow(c, label, field, val.ToString(), y, en);

        float InputRow(CuiElementContainer c, string label, string field, string val, float y, bool en)
        {
            string bg = (_row++ % 2 == 0) ? "0.13 0.13 0.11 1.0" : "0.16 0.16 0.14 1.0";
            c.Add(new CuiElement { Parent = SCROLL, Components = {
                new CuiImageComponent { Color =bg },
                new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"0 {-(y+RH)}", OffsetMax = $"0 {-y}" }
            }});
            string tc = en ? "0.88 0.85 0.80 1.0" : "0.44 0.43 0.41 1.0";
            c.Add(new CuiLabel { Text = { Text = label, FontSize = 12, Align = TextAnchor.MiddleLeft, Color = tc },
                RectTransform = { AnchorMin = "0 1", AnchorMax = "0.60 1", OffsetMin = $"10 {-(y+RH)}", OffsetMax = $"0 {-y}" }}, SCROLL);

            if (en)
            {
                c.Add(new CuiElement { Parent = SCROLL, Components = {
                    new CuiImageComponent { Color ="0.09 0.09 0.08 1.0" },
                    new CuiRectTransformComponent { AnchorMin = "0.61 1", AnchorMax = "1 1", OffsetMin = $"0 {-(y+RH-5)}", OffsetMax = $"-6 {-(y+5)}" }
                }});
                c.Add(new CuiElement { Parent = SCROLL, Components = {
                    new CuiInputFieldComponent { Text = val, FontSize = 13, Align = TextAnchor.MiddleCenter,
                        Color = "0.92 0.90 0.86 1.0", Command = $"dsm.set {field}", CharsLimit = 12, },
                    new CuiRectTransformComponent { AnchorMin = "0.61 1", AnchorMax = "1 1", OffsetMin = $"2 {-(y+RH-5)}", OffsetMax = $"-8 {-(y+5)}" }
                }});
            }
            else
            {
                c.Add(new CuiLabel { Text = { Text = val, FontSize = 13, Align = TextAnchor.MiddleCenter, Color = "0.40 0.40 0.38 1.0" },
                    RectTransform = { AnchorMin = "0.61 1", AnchorMax = "1 1", OffsetMin = $"0 {-(y+RH)}", OffsetMax = $"-6 {-y}" }}, SCROLL);
            }
            return y + RH + 1;
        }

        float ActionRow(CuiElementContainer c, bool en, float y)
        {
            const float h = 50f;
            var acts = new (string lbl, string cmd, string col)[]
            {
                ("強制オープン",       "open",          "0.20 0.50 0.28 1.0"),
                ("強制クローズ",       "close",         "0.55 0.18 0.12 1.0"),
                ("島を生成",           "spawnisland",   "0.30 0.44 0.22 1.0"),
                ("幽霊船を生成",       "spawnghostship","0.40 0.28 0.50 1.0"),
                ("浮遊都市を生成",     "spawncity",     "0.22 0.40 0.50 1.0"),
                ("RHIBグループを生成", "spawnrhib",     "0.44 0.36 0.20 1.0"),
            };
            float bw = 1.0f / acts.Length;
            for (int i = 0; i < acts.Length; i++)
            {
                float x0 = i * bw, x1 = x0 + bw;
                string col = en ? acts[i].col : "0.20 0.20 0.18 1.0";
                string tc  = en ? "0.95 0.92 0.88 1.0" : "0.36 0.36 0.34 1.0";
                string cmd = en ? $"dsm.action {acts[i].cmd}" : "";
                c.Add(new CuiButton { Button = { Command = cmd, Color = col },
                    RectTransform = { AnchorMin = $"{x0:F4} 1", AnchorMax = $"{x1:F4} 1", OffsetMin = $"2 {-(y+h-3)}", OffsetMax = $"-2 {-(y+3)}" },
                    Text = { Text = acts[i].lbl, FontSize = 10, Align = TextAnchor.MiddleCenter, Color = tc }}, SCROLL);
            }
            if (!en)
            {
                c.Add(new CuiLabel { Text = { Text = "※ DeepSea有効が必要です", FontSize = 10, Align = TextAnchor.LowerCenter, Color = "0.92 0.70 0.18 0.8" },
                    RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"0 {-(y+h+18)}", OffsetMax = $"0 {-(y+h)}" }}, SCROLL);
                return y + h + 20;
            }
            return y + h + 4;
        }

        float StatusRow(CuiElementContainer c, float y)
        {
            const float h = 46f;
            var btns = new (string lbl, string cmd, string col)[]
            {
                ("ステータス確認",     "status",      "0.28 0.38 0.50 1.0"),
                ("エンティティ数",     "entitycount", "0.35 0.42 0.28 1.0"),
                ("戦利品情報",         "loot",        "0.48 0.36 0.22 1.0"),
                ("ワイプ残り時間",     "timetowipe",  "0.38 0.28 0.48 1.0"),
            };
            float bw = 1.0f / btns.Length;
            for (int i = 0; i < btns.Length; i++)
            {
                float x0 = i * bw, x1 = x0 + bw;
                c.Add(new CuiButton { Button = { Command = $"dsm.action {btns[i].cmd}", Color = btns[i].col },
                    RectTransform = { AnchorMin = $"{x0:F4} 1", AnchorMax = $"{x1:F4} 1", OffsetMin = $"2 {-(y+h-3)}", OffsetMax = $"-2 {-(y+3)}" },
                    Text = { Text = btns[i].lbl, FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.95 0.92 0.88 1.0" }}, SCROLL);
            }
            c.Add(new CuiLabel { Text = { Text = "※ 結果はチャットに表示されます", FontSize = 9, Align = TextAnchor.LowerCenter, Color = "0.58 0.56 0.52 0.9" },
                RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"0 {-(y+h+16)}", OffsetMax = $"0 {-(y+h)}" }}, SCROLL);
            return y + h + 18;
        }
    }
}

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху