{"TotalCount":42,"Files":[{"Ident":"slfy.circleroyale","Path":"Game/CircleroyaleGame.cs","FileName":"CircleroyaleGame.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\n\n/// \u003Csummary\u003E\n/// CircleRoyale \u4E3B\u7CFB\u7EDF\uFF1A\u8FD0\u884C\u65F6\u751F\u6210\u6574\u4E2A\u4E16\u754C\uFF08\u76F8\u673A/HUD/\u80CC\u666F/\u7403\uFF09\uFF0C\u627F\u8F7D host \u6743\u5A01\u903B\u8F91\u3002\n/// GameObjectSystem \u4F1A\u5728\u573A\u666F\u52A0\u8F7D\u65F6\u81EA\u52A8\u5B9E\u4F8B\u5316\uFF08\u65E0\u9700\u6302\u8F7D\u5230\u573A\u666F\uFF09\uFF0C\u4F46\u7F16\u8F91\u5668\u7684\u7F16\u8F91\u6A21\u5F0F\u4E0B\n/// \u4E5F\u4F1A tick\uFF0C\u6240\u4EE5\u6240\u6709\u6E38\u620F\u903B\u8F91\u90FD\u7528 Game.IsPlaying \u62E6\u622A\u3002\u6A21\u677F\u573A\u666F\u5BF9\u8C61\u4F1A\u88AB\u6574\u4F53\u5C4F\u853D\u3002\n/// \u003C/summary\u003E\npublic sealed class CircleroyaleGame : GameObjectSystem\u003CCircleroyaleGame\u003E\n{\n\tGameObject _root;\n\tNeonCamera _camera;\n\tGameHud _hud;\n\tEndBoard _endBoard;\n\tLobbyPanel _lobby;\n\tRoomBrowser _browser;\n\tSettingsPanel _settings;\n\tRankingsPanel _rankings;\n\tFoodManager _food;\n\tPowerUpManager _power;\n\tNetworkManager _net;\n\tMainMenu _menu;\n\n\treadonly List\u003CBall\u003E _balls = new();\n\treadonly Dictionary\u003CGuid, Vector3\u003E _lastPos = new();   // host \u4F4D\u79FB\u6821\u9A8C\u7528\n\tTimeSince _sinceScan = 1f;         // \u7403\u6CE8\u518C\u626B\u63CF\n\tTimeSince _sinceFoodRequest = 1f;  // \u5BA2\u6237\u7AEF\u8BF7\u6C42\u98DF\u7269\u5168\u91CF\u7684\u8282\u6D41\uFF08\u9996\u5E27\u5373\u8BF7\u6C42\uFF09\n\tTimeSince _sinceMatchTick = 1f;    // \u5012\u8BA1\u65F6 1Hz \u6821\u65F6\u5E7F\u64AD\n\tint _botNameIndex;\n\tint _teamSeq;                      // \u56E2\u961F\u8D5B\u8F6E\u8F6C\u5206\u961F\u8BA1\u6570\uFF08\u5F00\u5C40\u91CD\u7F6E\uFF09\n\n\t// ---- \u5206\u8EAB/\u5B62\u5B50\uFF08M3\uFF0C\u7EAF\u6570\u636E\u5B9E\u4F53\u89C1 CellPiece\uFF09----\n\treadonly List\u003CCellPiece\u003E _cells = new();                    // host \u6743\u5A01\u6A21\u62DF\uFF1B\u5BA2\u6237\u7AEF\u955C\u50CF\uFF08CellsState RPC\uFF09\n\treadonly Dictionary\u003CGuid, Vector2\u003E _steerDir = new();       // host\uFF1A\u6BCF\u7403\u5E73\u6ED1\u8F6C\u5411\uFF08\u5206\u8EAB\u8DDF\u968F\u7528\uFF09\n\treadonly Dictionary\u003CGuid, TimeSince\u003E _sinceEject = new();   // host\uFF1A\u5410\u5B62\u5B50\u8282\u6D41\n\tTimeSince _sinceCellSync = 1f;\n\tbool _cellsDirty;                                           // \u589E\u5220\u6807\u8BB0\uFF08\u5217\u8868\u6E05\u7A7A\u540E\u8FD8\u8981\u8865\u53D1\u4E00\u6B21\u7A7A\u5FEB\u7167\uFF09\n\tint _nextCellId = 1;\n\tVector2[] _spikes;                                          // \u7EFF\u523A\u4F4D\u7F6E\uFF08host \u751F\u6210\uFF1B\u5BA2\u6237\u7AEF\u7ECF SpikesFull \u540C\u6B65\uFF09\n\tfloat[] _spikeFed;                                          // \u6BCF\u6735\u523A\u7D2F\u8BA1\u5582\u5165\u8D28\u91CF\uFF08host \u6743\u5A01\uFF1B\u5BA2\u6237\u7AEF SpikeFed RPC \u955C\u50CF\uFF0Cv0.7.8.24\uFF09\n\treadonly List\u003CSpikeSpine\u003E _spines = new();                  // \u523A\u7206\u5F39\u5E55\uFF1Ahost=\u6743\u5A01\u6A21\u62DF\uFF08\u542B\u547D\u4E2D\uFF09\uFF0C\u5BA2\u6237\u7AEF=\u7EAF\u89C6\u89C9\u5F39\u9053\uFF08v0.7.8.24\uFF09\n\n\t/// \u003Csummary\u003E \u5168\u90E8\u7403\uFF08\u771F\u4EBA \u002B bot\uFF09 \u003C/summary\u003E\n\tpublic IReadOnlyList\u003CBall\u003E Balls =\u003E _balls;\n\n\t/// \u003Csummary\u003E \u672C\u5730\u7403\uFF08M2 \u8D77\uFF1A\u771F\u4EBA=\u81EA\u5DF1\u90A3\u9897\uFF0Cbot \u65E0\uFF09 \u003C/summary\u003E\n\tpublic Ball LocalBall { get; private set; }\n\n\t/// \u003Csummary\u003E \u672C\u5730\u73A9\u5BB6\u662F\u5426\u5904\u4E8E\u6B7B\u4EA1\u5F85\u91CD\u751F\u72B6\u6001\uFF08HUD \u663E\u793A\u6B7B\u4EA1\u9762\u677F\uFF09 \u003C/summary\u003E\n\tpublic bool IsLocalDead =\u003E _worldReady \u0026\u0026 LocalBall.IsValid() \u0026\u0026 !LocalBall.Alive;\n\n\t/// \u003Csummary\u003E \u98DF\u7269\u7BA1\u7406\uFF08\u6E32\u67D3\u4E0E AI \u8BFB\uFF09 \u003C/summary\u003E\n\tpublic FoodManager Food =\u003E _food;\n\n\t/// \u003Csummary\u003E \u9053\u5177\u7BA1\u7406\uFF08\u6E32\u67D3\u8BFB\uFF1Bhost \u6743\u5A01\uFF0C\u5BA2\u6237\u7AEF\u4E3A\u4E8B\u4EF6\u955C\u50CF\uFF09 \u003C/summary\u003E\n\tpublic PowerUpManager PowerUps =\u003E _power;\n\n\t/// \u003Csummary\u003E \u5168\u90E8\u5206\u8EAB/\u5B62\u5B50\uFF08\u6E32\u67D3\u8BFB\uFF1Bhost \u6743\u5A01\uFF0C\u5BA2\u6237\u7AEF\u4E3A 15Hz \u5FEB\u7167\u955C\u50CF\uFF09 \u003C/summary\u003E\n\tpublic IReadOnlyList\u003CCellPiece\u003E Cells =\u003E _cells;\n\n\t/// \u003Csummary\u003E \u7EFF\u523A\u4F4D\u7F6E\uFF08\u9759\u6001\u969C\u788D\uFF1Bhost \u751F\u6210\uFF0C\u5BA2\u6237\u7AEF\u7ECF SpikesFull \u5168\u91CF\u540C\u6B65\uFF09 \u003C/summary\u003E\n\tpublic Vector2[] Spikes =\u003E _spikes;\n\n\t/// \u003Csummary\u003E \u523A\u7684\u7D2F\u8BA1\u5582\u98DF\u91CF\uFF080~SpikeGrowTarget\uFF0C\u6E32\u67D3\u957F\u5927\u7528\uFF1Bhost \u6743\u5A01\uFF0C\u5BA2\u6237\u7AEF\u7ECF SpikeFed RPC \u955C\u50CF\uFF09 \u003C/summary\u003E\n\tpublic float SpikeFedAt( int index ) =\u003E\n\t\t_spikeFed is not null \u0026\u0026 (uint)index \u003C (uint)_spikeFed.Length ? _spikeFed[index] : 0f;\n\n\t/// \u003Csummary\u003E \u523A\u7206\u5F39\u5E55\uFF08\u6E32\u67D3\u8BFB\uFF1Bhost \u6743\u5A01\u6A21\u62DF\u542B\u547D\u4E2D\uFF0C\u5BA2\u6237\u7AEF\u4E3A\u7EAF\u89C6\u89C9\u5F39\u9053\uFF09 \u003C/summary\u003E\n\tpublic IReadOnlyList\u003CSpikeSpine\u003E Spines =\u003E _spines;\n\n\t/// \u003Csummary\u003E HUD\uFF08\u63D0\u4F9B\u5C4F\u5E55\u5149\u6807\u6865\u63A5\u7B49\uFF09 \u003C/summary\u003E\n\tpublic GameHud Hud =\u003E _hud;\n\n\t/// \u003Csummary\u003E \u8DDF\u968F\u76F8\u673A\uFF08\u5C4F\u5E55\u9707\u52A8\u89E6\u53D1\u5165\u53E3\uFF09 \u003C/summary\u003E\n\tpublic NeonCamera Camera =\u003E _camera;\n\n\t/// \u003Csummary\u003E \u623F\u95F4\u5217\u8868\u9875\uFF08\u4E3B\u83DC\u5355 [2] \u6253\u5F00\uFF0C\u70B9\u884C\u52A0\u5165\uFF09 \u003C/summary\u003E\n\tpublic RoomBrowser Browser =\u003E _browser;\n\n\t/// \u003Csummary\u003E host\uFF1A\u623F\u95F4\u5F00\u7740\uFF08\u7B49 START\uFF09\uFF1B\u6B64\u9636\u6BB5\u73A9\u5BB6\u53EF\u8C03\u53C2/\u8FDB\u51FA\uFF0C\u6BD4\u8D5B\u5C1A\u672A\u751F\u6210 \u003C/summary\u003E\n\tpublic bool IsLobbyOpen =\u003E _lobbyOpen;\n\n\t/// \u003Csummary\u003E \u6BD4\u8D5B\u662F\u5426\u5DF2\u5F00\u59CB\uFF08\u5BA2\u6237\u7AEF\u533A\u5206\u0022\u8FDB\u623F\u95F4\u7B49\u0022\u8FD8\u662F\u0022\u76F4\u63A5\u8FDB\u6E38\u620F\u0022\uFF09 \u003C/summary\u003E\n\tpublic bool IsMatchStarted =\u003E _gameStarted;\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u89C6\u89D2\uFF1A\u6B63\u5728\u4F1A\u8BDD\u91CC\uFF08\u623F\u95F4\u7B49\u5F85/\u5BF9\u5C40/\u52A0\u5165\u5012\u8BA1\u65F6\u4E2D\uFF09\u2014\u2014\u6389\u7EBF\u81EA\u52A8\u91CD\u8FDE\u5224\u5B9A\u7528 \u003C/summary\u003E\n\tpublic bool IsClientInSession =\u003E _lobbyWaiting || _gameStarted || _matchRunning || _joinCountdown \u003E= 0f;\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u662F\u5426\u505C\u5728\u7B49\u5F85\u9875\uFF08P \u9000\u5C40/\u6389\u7EBF\u6062\u590D\u4E2D\uFF09\u2014\u2014\u672C\u673A\u7403\u505C\u6B62\u64CD\u63A7\uFF08\u7403\u7559\u5728\u539F\u5730\uFF0C\u9632\u9694\u5C4F\u76F2\u5F00\uFF09 \u003C/summary\u003E\n\tpublic bool IsClientWaiting =\u003E _lobbyWaiting;\n\n\t/// \u003Csummary\u003E\n\t/// \u5BA2\u6237\u7AEF\u8FDB\u5165\u623F\u95F4\u7B49\u5F85\u6001\uFF08\u6240\u6709\u5165\u53E3\u7EDF\u4E00\u8D70\u8FD9\u91CC\uFF0Cv0.7.1.1\uFF09\uFF1A\n\t/// \u7F6E\u6807\u5FD7 \u002B **\u91CD\u7F6E/\u89E3\u9664\u623F\u95F4\u5FC3\u8DF3**\u2014\u2014\u8FDB\u5165\u7B49\u5F85\u7684\u65F6\u523B host \u4E0D\u4E00\u5B9A\u5728\u623F\u95F4\u9636\u6BB5\uFF08\u53EF\u80FD\u5BF9\u5C40\u8FDB\u884C\u4E2D\uFF0C\n\t/// \u6CA1\u6709\u540D\u5355\u5E7F\u64AD\uFF09\uFF0C\u65E7\u8BA1\u65F6\u5668/\u6B66\u88C5\u72B6\u6001\u4F1A\u7ACB\u523B\u8BEF\u5224\u8D85\u65F6\uFF0C\u5FC5\u987B\u7B49 host \u7684\u4E0B\u4E00\u6761 LobbyState \u91CD\u65B0\u6B66\u88C5\u3002\n\t/// \u003C/summary\u003E\n\tvoid EnterClientWaiting()\n\t{\n\t\tif ( _lobbyWaiting ) return;   // \u5DF2\u5728\u7B49\u5F85\u6001\uFF1A\u522B\u91CD\u7F6E\u8BA1\u65F6\uFF08\u5426\u5219\u5FC3\u8DF3\u6C38\u8FDC\u5224\u4E0D\u8D85\u65F6\uFF09\n\n\t\t_lobbyWaiting = true;\n\t\t_lobbyHeartbeatArmed = false;\n\t\t_sinceLobbyHeartbeat = 0;\n\t}\n\n\tstatic GameObject _sharedRoot;   // \u70ED\u91CD\u8F7D/\u91CD\u5EFA\u65F6\u590D\u7528\u540C\u4E00\u4E2A\u4E16\u754C\u6839\uFF0C\u907F\u514D\u5806\u53E0\u591A\u5957\u573A\u666F\n\n\tbool _worldReady;\n\tbool _networkReady;\n\tbool _gameStarted;              // \u6BD4\u8D5B\u8FDB\u884C\u4E2D\uFF08\u6A21\u62DF\u95E8\u63A7\uFF09\uFF1B\u623F\u95F4/\u83DC\u5355\u9636\u6BB5 false\n\tbool _menuDemo;                 // \u4E3B\u83DC\u5355\u80CC\u666F\u6F14\u793A\u8D5B\uFF1Aauthority \u79BB\u7EBF\u8DD1\u4E00\u573A\u7EAF bot \u5BF9\u6218\uFF08v0.6.3.0\uFF09\n\tbool _lobbyOpen;                // host\uFF1A\u623F\u95F4\u5F00\u7740\uFF08\u8FD8\u6CA1\u70B9 START\uFF09\n\tbool _lobbyWaiting;             // client\uFF1A\u5DF2\u8FDE\u5165\uFF0C\u5728\u623F\u95F4\u91CC\u7B49\u623F\u4E3B\u5F00\u5C40\n\tbool _joinRequested;            // \u5BA2\u6237\u7AEF\u70B9\u4E86\u0022\u52A0\u5165\u0022\uFF1A\u7B49\u4F1A\u8BDD\u4EE5\u5BA2\u6237\u7AEF\u8EAB\u4EFD\u6FC0\u6D3B\uFF08\u8FDE\u63A5\u524D\u51E0\u5E27 IsAuthority \u4E0D\u53EF\u4FE1\uFF09\n\tTimeSince _sinceJoinRequest;\n\tTimeSince _sinceMatchOver;      // \u7ED3\u7B97\u663E\u793A\u8BA1\u65F6\uFF0810 \u79D2\u540E\u81EA\u52A8\u56DE\u5927\u5385/\u4E3B\u83DC\u5355\uFF09\n\tbool _lobbyListDirty;           // \u623F\u95F4\u73A9\u5BB6\u540D\u5355\u53D8\u4E86\uFF08\u4E3B\u7EBF\u7A0B\u4E0B\u4E00\u62CD\u5E7F\u64AD LobbyState\uFF09\n\tbyte _lobbyMode;                // client\uFF1A\u623F\u4E3B\u63A8\u9001\u7684\u623F\u95F4\u53C2\u6570\uFF08LobbyState RPC\uFF09\n\tint _lobbyDuration;\n\tint _lobbyPlayers;\n\tstring[] _lobbyNames = System.Array.Empty\u003Cstring\u003E();\n\tfloat _joinCountdown = -1f;     // \u5BA2\u6237\u7AEF\u52A0\u5165\u5012\u8BA1\u65F6\uFF08\u22650 \u65F6\u623F\u95F4\u9875\u663E\u793A 3-2-1\uFF0C\u5F52\u96F6\u8FDB\u5165\u5BF9\u5C40\uFF09\n\tbool _matchRunning;             // \u5BA2\u6237\u7AEF\u89C6\u89D2\uFF1Ahost \u7684\u5BF9\u5C40\u8FDB\u884C\u4E2D\uFF08\u623F\u95F4\u9875\u4EAE JOIN GAME \u6309\u94AE\uFF09\n\n\t// host \u5FC3\u8DF3\uFF08v0.6.8.0\uFF09\uFF1Ahost \u623F\u95F4\u9636\u6BB5 3s \u5E7F\u64AD\u4E00\u6B21\u540D\u5355\u5F53\u5FC3\u8DF3\uFF1B\u5BA2\u6237\u7AEF\u6536\u5230\u5F52\u96F6\uFF0C\n\t// \u9759\u9ED8\u8D85\u65F6 = host \u6B7B\u4E86\uFF08\u8FDB\u7A0B\u88AB\u6740\u65F6\u5F15\u64CE\u4F1A\u8BDD\u53EF\u80FD\u4E0D\u81EA\u52A8\u5931\u6548\uFF09\uFF0C\u5F3A\u5236\u62C6\u7EBF\u8D70\u91CD\u8FDE\n\tTimeSince _sinceLobbyPing = 999f;   // host\uFF1A\u623F\u95F4\u5FC3\u8DF3\u8282\u62CD\n\tbool _lobbyHeartbeatArmed;          // client\uFF1A\u6536\u5230\u8FC7\u7B2C\u4E00\u6B21\u540D\u5355\u540E\u624D\u6B66\u88C5\uFF08\u9632\u521A\u8FDB\u623F\u5C31\u8BEF\u5224\uFF09\n\tTimeSince _sinceLobbyHeartbeat;\n\n\tpublic CircleroyaleGame( Scene scene ) : base( scene )\n\t{\n\t\tListen( Stage.UpdateBones, 0, Tick, \u0022CircleroyaleGame.Tick\u0022 );\n\t}\n\n\tvoid Tick()\n\t{\n\t\tif ( !Game.IsPlaying ) return;\n\n\t\tEnsureWorld();\n\n\t\t// \u5BA2\u6237\u7AEF\uFF1A\u4F1A\u8BDD\u6FC0\u6D3B\u4F46\u4E16\u754C\u8FD8\u5728\u83DC\u5355\u6F14\u793A\u6001\uFF08\u65AD\u7EBF\u91CD\u8FDE\u65F6 EnsureWorld \u5728\u65AD\u7EBF\u671F\u628A\u5206\u652F\u8DD1\u6210\u4E86\n\t\t// \u6F14\u793A\u8D5B\uFF0C\u4E14\u91CD\u8FDE\u4E0D\u4F1A\u7F6E _joinRequested\uFF09\u2192 \u6536\u6F14\u793A\u8D5B\uFF0C\u56DE\u5230\u4F1A\u8BDD\u52A0\u5165\u6D41\n\t\tif ( !NetworkManager.IsAuthority \u0026\u0026 Networking.IsActive \u0026\u0026 _menuDemo )\n\t\t\tStopMenuDemo();\n\n\t\t// \u5BA2\u6237\u7AEF\uFF1A\u5728\u4F1A\u8BDD\u91CC\u4F46\u54EA\u4E2A\u754C\u9762\u90FD\u4E0D\u5728\uFF08\u91CD\u8FDE/\u70ED\u91CD\u8F7D\u540E\uFF09\u2192 \u56DE\u623F\u95F4\u7B49\u5F85\u5E76\u62C9\u5F53\u524D\u72B6\u6001\n\t\tif ( !NetworkManager.IsAuthority \u0026\u0026 Networking.IsActive \u0026\u0026 !_gameStarted\n\t\t\t\u0026\u0026 !_lobbyWaiting \u0026\u0026 _joinCountdown \u003C 0f )\n\t\t{\n\t\t\tEnterClientWaiting();\n\t\t\t_menu?.Hide();\n\t\t\t_browser?.Hide();\n\t\t\t_lobby?.ShowClient();\n\t\t\tNetworkManager.RequestMatchState();\n\t\t}\n\n\t\t// \u5BA2\u6237\u7AEF\uFF1AReplicated \u7684 cr_arena_size \u7531\u5F15\u64CE\u843D\u5230 ConvarArenaSize\uFF0C\u8FD9\u91CC\u540C\u6B65\u5230 ArenaHalfSize\u3002\n\t\t// host \u5728 OnNetworkReady \u505A\u540C\u6B3E\u94B3\u5236\uFF1B\u6B64\u524D\u5BA2\u6237\u7AEF\u4ECE\u4E0D\u8D4B\u503C\u2014\u2014\u7F51\u683C/\u8FB9\u754C/\u94B3\u5236\u4E00\u76F4\u7528\u9759\u6001\u9ED8\u8BA4\u503C\uFF0C\n\t\t// \u573A\u5730\u6539 3 \u500D\u540E\u4E0E host \u5B9E\u6D4B\u8131\u8282\uFF08host 2048 / \u5BA2\u6237\u7AEF 6144\uFF0C\u5BA2\u6237\u7AEF\u7F51\u683C\u5927\u4E86 3 \u500D\uFF09\n\t\tif ( !NetworkManager.IsAuthority )\n\t\t\tGameConfig.ArenaHalfSize = Math.Clamp( GameConfig.ConvarArenaSize, 1024f, 12288f );\n\n\t\t// \u5BA2\u6237\u7AEF\u52A0\u5165\u6D41\uFF1A\u70B9\u4E86\u0022\u52A0\u5165\u0022\u540E\u7B49\u4F1A\u8BDD\u4EE5\u5BA2\u6237\u7AEF\u8EAB\u4EFD\u6FC0\u6D3B\u3002\n\t\t// \u26A0\uFE0F \u4E0D\u80FD\u70B9\u5B8C\u5C31\u4FE1 IsAuthority\u2014\u2014\u8FDE\u63A5\u5EFA\u7ACB\u524D\u7684\u51E0\u5E27 IsActive=false\uFF0CIsAuthority \u6052\u4E3A true\n\t\tif ( _joinRequested \u0026\u0026 !_gameStarted )\n\t\t{\n\t\t\tif ( Networking.IsActive \u0026\u0026 !Networking.IsHost )\n\t\t\t{\n\t\t\t\tGameLog.Info( \u0022[menu] join: connected as client, entering room\u0022 );\n\t\t\t\t_joinRequested = false;\n\t\t\t\tEnterClientWaiting();\n\t\t\t\t_menu?.Hide();\n\t\t\t\t_browser?.Hide();   // \u623F\u95F4\u5217\u8868\u9875\u6536\u8D77\uFF08\u4ECE\u5217\u8868\u70B9\u8FDB\u6765\u7684\u6B63\u5E38\u6536\u5C3E\uFF09\n\t\t\t\t_lobby?.ShowClient();\n\t\t\t\tNetworkManager.RequestMatchState();\n\t\t\t}\n\t\t\telse if ( _sinceJoinRequest \u003E 12f )\n\t\t\t{\n\t\t\t\t_joinRequested = false;\n\t\t\t\t_menu?.SetStatus( \u0022JOIN FAILED \u2014 IS THE HOST UP? PRESS [2] TO RETRY\u0022 );\n\t\t\t\t_browser?.SetStatus( \u0022JOIN TIMED OUT \u2014 PICK ANOTHER ROOM OR REFRESH\u0022 );\n\t\t\t}\n\t\t}\n\n\t\t// \u5BA2\u6237\u7AEF\u5728\u4F1A\u8BDD\u754C\u9762\u91CC\u8FDE\u63A5\u65AD\u5F00\uFF1A**\u4E0D\u7ACB\u523B\u56DE\u83DC\u5355**\u2014\u2014\u754C\u9762\u7559\u5728\u539F\u5730\u7531 NetworkManager\n\t\t// \u81EA\u52A8\u91CD\u8FDE\uFF08TickDisconnectRecovery\uFF09\uFF0C\u91CD\u8BD5\u8017\u5C3D\u56DE\u8C03 OnReconnectFailed \u624D\u56DE\u4E3B\u83DC\u5355\uFF08v0.6.4.6\uFF09\n\n\t\t// \u623F\u95F4\u540D\u5355/\u53C2\u6570\u6709\u53D8\uFF08\u7F51\u7EDC\u7EBF\u7A0B\u53EA\u7F6E\u810F\u6807\uFF0C\u4E3B\u7EBF\u7A0B\u7EDF\u4E00\u5E7F\u64AD LobbyState\uFF09\n\t\tif ( _lobbyOpen \u0026\u0026 _lobbyListDirty )\n\t\t{\n\t\t\t_lobbyListDirty = false;\n\t\t\tBroadcastLobbyState();\n\t\t}\n\n\t\t// host \u623F\u95F4\u5FC3\u8DF3\uFF08v0.6.8.0\uFF09\uFF1A\u540D\u5355\u6CA1\u53D8\u4E5F\u5B9A\u65F6\u5E7F\u64AD\uFF0C\u5BA2\u6237\u7AEF\u9760\u5B83\u5224\u65AD\u0022host \u8FD8\u6D3B\u7740\u0022\n\t\tif ( _lobbyOpen \u0026\u0026 Networking.IsActive \u0026\u0026 _sinceLobbyPing \u003E 3f )\n\t\t{\n\t\t\t_sinceLobbyPing = 0;\n\t\t\tBroadcastLobbyState();\n\t\t}\n\n\t\t// host \u5FC3\u8DF3\u8D85\u65F6\uFF08\u5BA2\u6237\u7AEF\uFF0Cv0.6.8.0\uFF09\uFF1Ahost \u8FDB\u7A0B\u88AB\u6740\u65F6\u5F15\u64CE\u4F1A\u8BDD\u53EF\u80FD\u4E0D\u81EA\u52A8\u5931\u6548\n\t\t// \uFF08\u50F5\u5C38\u6001\uFF0CIsActive \u6052 true\uFF0C\u6389\u7EBF\u6062\u590D\u5FAA\u73AF\u4E0D\u89E6\u53D1\uFF09\u2014\u2014\u5BF9\u5C40/\u623F\u95F4\u5404\u81EA\u6709\u5FC3\u8DF3\uFF0C\n\t\t// \u9759\u9ED8\u8D85\u65F6\u4E3B\u52A8\u62C6\u7EBF\uFF0C\u4EA4\u7ED9 TickDisconnectRecovery\uFF08\u91CD\u8FDE\u8017\u5C3D\u56DE\u4E3B\u83DC\u5355\uFF09\n\t\t// v0.7.1.1 \u4FEE\uFF1A\u4E24\u6761\u5FC3\u8DF3**\u6309 host \u5B9E\u9645\u9636\u6BB5\u53D6\u4E00**\u2014\u2014host \u5728\u5BF9\u5C40\u9636\u6BB5\u4E0D\u53D1\u540D\u5355\u5E7F\u64AD\uFF0C\n\t\t// \u7B49\u5728\u623F\u95F4\u9875\u7684\u5BA2\u6237\u7AEF\u4E0D\u80FD\u62FF\u0022\u540D\u5355\u9759\u9ED8\u0022\u8BEF\u5224\uFF08\u6B64\u524D\u4E2D\u9014\u52A0\u5165 6s \u5373\u88AB\u8E22\u56DE\u4E3B\u83DC\u5355\uFF09\uFF1B\n\t\t// \u5BF9\u5C40\u5FC3\u8DF3\uFF08MatchTick 1Hz\uFF09\u53CD\u8FC7\u6765\u4E5F\u8986\u76D6\u0022\u5BA2\u6237\u7AEF\u5728\u623F\u95F4\u9875\u7B49\u3001host \u5BF9\u5C40\u8FD8\u5728\u8DD1\u0022\u7684\u60C5\u51B5\n\t\tif ( !NetworkManager.IsAuthority \u0026\u0026 Networking.IsActive \u0026\u0026 GameConfig.HostLivenessTimeout \u003E 0f )\n\t\t{\n\t\t\tbool matchSilent = ( _gameStarted || _matchRunning ) \u0026\u0026 !MatchState.MatchOver\n\t\t\t\t\u0026\u0026 MatchState.SinceLastTick \u003E GameConfig.HostLivenessTimeout;\n\t\t\tbool lobbySilent = _lobbyWaiting \u0026\u0026 _lobbyHeartbeatArmed \u0026\u0026 !_matchRunning\n\t\t\t\t\u0026\u0026 _sinceLobbyHeartbeat \u003E GameConfig.HostLivenessTimeout;\n\n\t\t\tif ( matchSilent || lobbySilent )\n\t\t\t{\n\t\t\t\tLog.Warning( \u0022[net] host silent beyond liveness timeout \u2014 forcing disconnect\u0022 );\n\t\t\t\ttry { Networking.Disconnect(); } catch { }\n\n\t\t\t\t// \u5BF9\u5C40\u5DF2\u6B7B\uFF1A\u7ACB\u523B\u9000\u51FA\u5BF9\u5C40\u6001\uFF0C\u6062\u590D\u671F\u505C\u5728\u623F\u95F4\u9875\uFF08CONNECTION LOST\uFF09\uFF0C\n\t\t\t\t// \u4E0D\u518D\u5BF9\u7740\u51BB\u7ED3\u753B\u9762\u5E72\u7B49\uFF08_gameStarted \u6302\u7740\u4F1A\u8BA9 HUD/\u51BB\u7ED3\u5BF9\u5C40\u4E00\u76F4\u5360\u5C4F\uFF09\n\t\t\t\t_gameStarted = false;\n\t\t\t\t_matchRunning = false;\n\t\t\t\t_joinCountdown = -1f;\n\t\t\t\t_lobbyHeartbeatArmed = false;\n\t\t\t\t_lobbyWaiting = true;   // \u4FDD\u4F4F IsClientInSession\uFF1A\u6389\u7EBF\u6062\u590D\u5FAA\u73AF\u8981\u9760\u5B83\u542F\u52A8\n\t\t\t\tLocalBall = null;\n\t\t\t}\n\t\t}\n\n\t\t// \u97F3\u4E50\u5FAA\u73AF\uFF08\u83DC\u5355/\u6218\u6597\u90FD\u8981\uFF09\uFF1A\u97F3\u91CF\u8DDF\u968F convar \u002B \u66F2\u7EC8\u81EA\u52A8\u91CD\u64AD\n\t\tGameMusic.Tick();\n\n\t\tReconcileUi();\n\n\t\t// P \u952E\u5FEB\u901F\u9000\u5C40\uFF08v0.6.9.0 \u7528\u6237\u9700\u6C42\uFF09\uFF1A\u5BF9\u5C40\u4E2D\u76F4\u63A5\u8FD4\u56DE\u5927\u5385\u2014\u2014host \u5E7F\u64AD\u53D6\u6D88\u8BA9\u5168\u5458\u4E00\u8D77\u56DE\u623F\uFF0C\n\t\t// \u5BA2\u6237\u7AEF\u81EA\u5DF1\u56DE\u7B49\u5F85\u9875\uFF1B\u5355\u673A\uFF08\u65E0\u4F1A\u8BDD\uFF09\u56DE\u4E3B\u83DC\u5355\u3002\u7ED3\u7B97\u671F\u4E0D\u53D7\u7406\uFF08EndBoard \u81EA\u52A8\u8FD4\u56DE\u5728\u7BA1\uFF09\n\t\tif ( _gameStarted \u0026\u0026 !MatchState.MatchOver \u0026\u0026 Input.Keyboard.Pressed( \u0022P\u0022 ) )\n\t\t{\n\t\t\tGameLog.Info( \u0022[match] quit to lobby via P\u0022 );\n\t\t\tif ( Networking.IsActive \u0026\u0026 NetworkManager.IsAuthority )\n\t\t\t\tNetworkManager.MatchCancelled();\n\t\t\tAfterSettlement();\n\t\t\treturn;\n\t\t}\n\n\t\t// \u5BA2\u6237\u7AEF\u52A0\u5165\u5012\u8BA1\u65F6\uFF08v0.6.4.0 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1AMatchStarted \u540E\u623F\u95F4\u9875 3-2-1\uFF0C\u5F52\u96F6\u8FDB\u5165\u5BF9\u5C40\n\t\tif ( _joinCountdown \u003E= 0f )\n\t\t{\n\t\t\t_joinCountdown -= Time.Delta;\n\t\t\t_lobby?.ShowJoinCountdown( MathF.Max( 0f, _joinCountdown ) );\n\t\t\tif ( _joinCountdown \u003C 0f )\n\t\t\t{\n\t\t\t\t_lobby?.HideJoinCountdown();\n\t\t\t\tOnMatchStartedLocal();\n\t\t\t}\n\t\t}\n\n\t\t// \u83DC\u5355\u9636\u6BB5\uFF1A\u96F6\u6A21\u62DF\uFF08\u98DF\u7269/bot/\u5403\u5224\u5B9A/\u540C\u6B65\u5168\u90FD\u4E0D\u8DD1\uFF09\u2014\u2014\u9664\u975E\u80CC\u666F\u6F14\u793A\u8D5B\u5728\u8DD1\uFF08v0.6.3.0\uFF09\n\t\tif ( !_gameStarted \u0026\u0026 !_menuDemo ) return;\n\n\t\t// \u6BD4\u8D5B\u8FDB\u7A0B\uFF08M5\uFF09\uFF1Ahost \u6743\u5A01\u5012\u8BA1\u65F6\uFF0C\u5F52\u96F6\u5E7F\u64AD\u7ED3\u7B97\uFF1B\u7ED3\u675F\u540E\u5168\u573A\u51BB\u7ED3\u7531\u7ED3\u7B97\u9762\u677F\u63A5\u7BA1\n\t\tTickMatch();\n\t\tif ( MatchState.MatchOver )\n\t\t{\n\t\t\t// \u7ED3\u7B97\u663E\u793A 10 \u79D2\u540E\u81EA\u52A8\u56DE\u5927\u5385\uFF08\u591A\u4EBA\uFF09/\u4E3B\u83DC\u5355\uFF08\u5355\u673A\uFF09\uFF1BEndBoard \u7A7A\u683C\u53EF\u63D0\u524D\n\t\t\tif ( _sinceMatchOver \u003E GameConfig.SettlementAutoReturnSeconds )\n\t\t\t\tAfterSettlement();\n\t\t\treturn;\n\t\t}\n\n\t\t// \u6743\u5A01\u7AEF\uFF1A\u98DF\u7269\u91CD\u751F / bot \u539F\u5730\u590D\u6D3B / \u4F4D\u79FB\u6821\u9A8C\uFF08\u987A\u4FBF\u4EA7\u51FA\u8F6C\u5411\u65B9\u5411\uFF09/ \u5206\u8EAB\u6A21\u62DF / \u5403\u5224\u5B9A\u3002\n\t\t// \u5206\u8EAB\u6A21\u62DF\u653E\u5728\u4F4D\u79FB\u6821\u9A8C\u4E4B\u540E\u2014\u2014\u8F6C\u5411\u65B9\u5411\u53D6\u81EA\u7403\u7684\u672C\u5E27\u4F4D\u79FB\uFF1B\u5403\u5224\u5B9A\u6700\u540E\u8DD1\uFF08\u5403\u5230\u7684\u662F\u65B0\u4F4D\u7F6E\uFF09\n\t\tif ( NetworkManager.IsAuthority )\n\t\t{\n\t\t\t_food.Tick();\n\t\t\t_power?.Tick( _balls, _cells );\n\t\t\tTickBots();\n\t\t\tTickMovementValidation();\n\t\t\tTickCells();\n\t\t\tTickEating();\n\t\t\tTickSpikes();\n\n\t\t\t// \u5206\u8EAB/\u5B62\u5B50\u72B6\u6001 15Hz \u5FEB\u7167\u5E7F\u64AD\uFF08\u5217\u8868\u6E05\u7A7A\u540E\u8FD8\u8981\u8865\u53D1\u4E00\u6B21\u7A7A\u5FEB\u7167\u8BA9\u5BA2\u6237\u7AEF\u4E5F\u6E05\u7A7A\uFF09\n\t\t\tif ( _sinceCellSync \u003E GameConfig.CellSyncInterval )\n\t\t\t{\n\t\t\t\t_sinceCellSync = 0;\n\t\t\t\tif ( _cells.Count \u003E 0 || _cellsDirty ) BroadcastCells();\n\t\t\t}\n\t\t}\n\n\t\t// \u523A\u7206\u5F39\u5E55\uFF08\u5BA2\u6237\u7AEF\uFF09\uFF1A\u7EAF\u89C6\u89C9\u5F39\u9053\u63A8\u8FDB\uFF08host \u5728 TickSpikes \u6743\u5A01\u6A21\u62DF\u002B\u547D\u4E2D\uFF09\n\t\tif ( !NetworkManager.IsAuthority ) TickSpinesVisual();\n\n\t\t// \u7EC6\u80DE\u6E32\u67D3\u4F4D\u7F6E\u5E73\u6ED1\uFF1Ahost \u9010\u5E27\u6743\u5A01\u4F4D\u7F6E\u76F4\u63A5\u8D34\u9F50\uFF1B\u5BA2\u6237\u7AEF\u628A 15Hz \u5FEB\u7167\u63D2\u987A\n\t\tif ( _cells.Count \u003E 0 )\n\t\t{\n\t\t\tvar st = 1f - MathF.Exp( -12f * Time.Delta );\n\t\t\tforeach ( var c in _cells )\n\t\t\t\tc.DrawPos = NetworkManager.IsAuthority ? c.Pos : Vector3.Lerp( c.DrawPos, c.Pos, st );\n\t\t}\n\n\t\t// \u7403\u6CE8\u518C\uFF1A\u626B\u63CF\u573A\u666F\u6536\u7F16\uFF08\u7F51\u7EDC\u5BF9\u8C61\u7684 OnStart \u4E0D\u89E6\u53D1\uFF0C\u5B9E\u6D4B\uFF09\u002B \u6E05\u7406\u5931\u6548\u7403\n\t\tif ( _sinceScan \u003E 1f )\n\t\t{\n\t\t\t_sinceScan = 0;\n\t\t\tScanBalls();\n\t\t}\n\n\t\t// \u5BA2\u6237\u7AEF\uFF1A\u672C\u5730\u98DF\u7269\u5217\u8868\u672A\u5C31\u7EEA\uFF08\u70ED\u91CD\u8F7D/\u65B0 FoodManager\uFF09\u2192 \u8282\u6D41\u8BF7\u6C42\u5168\u91CF\u3002\n\t\t// \u7B49\u7403\u626B\u63CF\u5230\uFF08=\u573A\u666F\u5FEB\u7167\u5DF2\u6536\uFF09\u518D\u8BF7\u6C42\uFF1B\u521A\u8FDE\u5165\u65F6 host \u7684 OnActive \u4F1A\u4E3B\u52A8\u53D1\uFF0C\u65E0\u9700\u62A2\u8DD1\n\t\tif ( !NetworkManager.IsAuthority \u0026\u0026 _food.Foods is null \u0026\u0026 _balls.Count \u003E 0 \u0026\u0026 _sinceFoodRequest \u003E 3f )\n\t\t{\n\t\t\t_sinceFoodRequest = 0;\n\t\t\ttry\n\t\t\t{\n\t\t\t\tNetworkManager.RequestFoodFull();\n\t\t\t}\n\t\t\tcatch ( Exception e )\n\t\t\t{\n\t\t\t\tGameLog.Info( $\u0022[net] RequestFoodFull failed (session not ready): {e.Message}\u0022 );\n\t\t\t}\n\t\t}\n\n\t\tTickRespawn();\n\t}\n\n\t// ---- \u5403\u4E0E\u6210\u957F ----\n\n\tvoid TickEating()\n\t{\n\t\t// \u7403 \u00D7 \u98DF\u7269\uFF1A\u98DF\u7269\u4E2D\u5FC3\u8FDB\u5165\u7403\u4F53\u5373\u88AB\u5403\uFF08\u7A7A\u95F4\u54C8\u5E0C\u7C97\u7B5B \u002B \u7CBE\u786E\u590D\u6838\uFF0C4140 \u9897\u65F6\u7701 ~20 \u4E07\u6B21/\u5E27\uFF09\n\t\t// \u78C1\u94C1 buff\uFF08v0.7.3.0\uFF09\uFF1A\u5438\u98DF\u534A\u5F84\u7FFB\u500D\u2014\u2014\u9694\u7740\u4E00\u4E2A\u8EAB\u4F4D\u4E5F\u80FD\u628A\u98DF\u7269\u5438\u8FDB\u5634\u91CC\uFF08\u53EA\u5F71\u54CD\u98DF\u7269\uFF09\n\t\tvar foods = _food.Foods;\n\t\tforeach ( var ball in _balls )\n\t\t{\n\t\t\tif ( !ball.IsValid() || !ball.Alive ) continue;\n\n\t\t\tvar bp = ball.WorldPosition;\n\t\t\tvar r = ball.HasBuff( PowerUpManager.Kind.Magnet ) ? ball.Radius * 2f : ball.Radius;\n\t\t\tvar near = _food.Nearby( bp, r );\n\n\t\t\tfor ( int k = 0; k \u003C near.Count; k\u002B\u002B )\n\t\t\t{\n\t\t\t\tint i = near[k];\n\t\t\t\tif ( !foods[i].Alive ) continue;\n\n\t\t\t\tvar dx = foods[i].Pos.x - bp.x;\n\t\t\t\tvar dy = foods[i].Pos.y - bp.y;\n\t\t\t\tif ( dx * dx \u002B dy * dy \u003E r * r ) continue;\n\n\t\t\t\tif ( _food.TryEat( i, ball.OwnerSteamId ) )\n\t\t\t\t{\n\t\t\t\t\tball.Mass = MathF.Min( GameConfig.MaxMass, ball.Mass \u002B GameConfig.FoodMass );\n\t\t\t\t\tNeonRenderer.Spark( new Vector3( foods[i].Pos.x, foods[i].Pos.y, 0f ), foods[i].ColorIndex );\n\t\t\t\t\tif ( GameSfx.IsMine( ball.OwnerSteamId ) )\n\t\t\t\t\t\tGameSfx.EatFood( new Vector3( foods[i].Pos.x, foods[i].Pos.y, 0f ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// \u8D28\u91CF\u8870\u51CF\uFF08v0.7.8.24 \u7403\u7403\u5927\u4F5C\u6218\u624B\u611F\uFF09\uFF1A\u5927\u7403\u6301\u7EED\u6389\u91CD\uFF0C\u538B\u5236\u6EDA\u96EA\u7403\u3001\u903C\u5927\u54E5\u4E0D\u505C\u884C\u52A8\u3002\n\t\t// \u5C0F\u7403\u514D\u8870\u51CF\uFF1B\u5206\u8EAB\u540C\u7387\uFF08TickCells\uFF09\u3002Mass \u662F [Sync(FromHost)]\uFF0C\u8870\u51CF\u503C\u81EA\u52A8\u4E0B\u53D1\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.Alive ) continue;\n\t\t\tif ( b.Mass \u003E GameConfig.MassDecayMinMass )\n\t\t\t\tb.Mass = MathF.Max( GameConfig.MassDecayMinMass, b.Mass - b.Mass * GameConfig.MassDecayPerSecond * Time.Delta );\n\t\t}\n\n\t\t// \u7403 \u00D7 \u7403\uFF1A\u89E6\u78B0\u5373\u5403\uFF08\u8D28\u91CF\u6BD4\u95E8\u69DB\uFF1B\u62A4\u76FE\u671F/\u6B7B\u4EA1\u514D\u6218\uFF09\u3002\n\t\t// \u6B7B\u4EA1\u4E0D\u9500\u6BC1\u5BF9\u8C61\u2014\u2014MarkDead \u540E\u539F\u5730\u590D\u7528\uFF08\u8FDB\u884C\u4E2D NetworkSpawn \u4E0D\u590D\u5236\u5230\u5DF2\u8FDE\u63A5\u5BA2\u6237\u7AEF\uFF0C\u5B9E\u6D4B\uFF09\n\t\tfor ( int i = 0; i \u003C _balls.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar a = _balls[i];\n\t\t\tif ( !a.IsValid() || !a.Alive ) continue;\n\n\t\t\tfor ( int j = i \u002B 1; j \u003C _balls.Count; j\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar b = _balls[j];\n\t\t\t\tif ( !b.IsValid() || !b.Alive ) continue;\n\n\t\t\t\tvar bigger = a.Mass \u003E= b.Mass ? a : b;\n\t\t\t\tvar smaller = bigger == a ? b : a;\n\t\t\t\tif ( SameTeam( a, b ) ) continue;   // \u961F\u53CB\u514D\u4F24\uFF08\u56E2\u961F\u8D5B\uFF0Cv0.6.5.0\uFF09\n\t\t\t\tif ( bigger.Mass \u003C smaller.Mass * GameConfig.EatRatio ) continue;\n\t\t\t\tif ( smaller.IsProtected || bigger.IsProtected ) continue;   // \u62A4\u76FE\u671F\u53CC\u5411\u514D\u6218\n\t\t\t\tif ( smaller.HasBuff( PowerUpManager.Kind.Shield ) ) continue;   // \u9053\u5177\u62A4\u76FE\uFF1A\u4E0D\u53EF\u88AB\u5403\uFF08\u4E0D\u9632\u5C16\u523A\uFF09\n\n\t\t\t\tvar d = bigger.WorldPosition.Distance( smaller.WorldPosition );\n\t\t\t\tif ( d \u003E= bigger.Radius \u002B smaller.Radius ) continue;   // \u89E6\u78B0\u5373\u5403\uFF08\u7528\u6237\u5B9A\u7A3F\uFF09\n\n\t\t\t\t// \u541E\u5E76\u5C01\u9876\uFF1A\u4E0D\u8BBE\u4E0A\u9650\u7684\u8BDD\u8FDE\u9501\u541E\u566C\u4F1A\u8BA9\u534A\u5F84\u8D85\u8FC7\u573A\u5730\u4E00\u534A\uFF0C\n\t\t\t\t// \u8FB9\u754C\u94B3\u5236 min\u003Emax \u629B\u5F02\u5E38\uFF08\u6BCF\u5E27\u5237\u5C4F\uFF09\u4E14\u91CD\u751F\u8005\u79D2\u88AB\u5403\uFF08\u6B7B\u4EA1\u5FAA\u73AF\uFF09\u2014\u2014\u5B9E\u6D4B\n\t\t\t\tfloat gained = smaller.Mass;\n\t\t\t\tbigger.Mass = MathF.Min( GameConfig.MaxMass, bigger.Mass \u002B gained );\n\t\t\t\tSpawnPopFx( smaller );\n\t\t\t\t// \u541E\u7403\u97F3\u53EA\u5BF9\u53CC\u65B9\u64AD\uFF08\u5403\u8005 \u002B \u88AB\u5403\u8005\uFF09\u002B \u5168\u573A\u0022\u8C01\u5403\u4E86\u8C01\u0022\u64AD\u62A5\uFF08\u53CC\u7AEF\u7EDF\u4E00\u5165\u53E3\uFF09\n\t\t\t\tNotifyBallEaten( bigger.OwnerSteamId, smaller.OwnerSteamId, gained );\n\n\t\t\t\t// agar \u591A\u8EAB\u4F53\u89C4\u5219\uFF08v0.6.2.0 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u5355\u4E2A\u8EAB\u4F53\u6B7B\u4EA1\u4E0D\u5BFC\u81F4\u73A9\u5BB6\u6B7B\u4EA1\u2014\u2014\n\t\t\t\t// \u4E3B\u7403\u88AB\u5403\u65F6\u6700\u5927\u7684\u5206\u8EAB\u5347\u7EA7\u6210\u65B0\u4E3B\u7403\uFF0C\u5168\u90E8\u8EAB\u4F53\u6B7B\u5149\u624D\u7B97\u6B7B\n\t\t\t\tif ( TryPromoteFromPieces( smaller ) )\n\t\t\t\t{\n\t\t\t\t\tGameLog.Info( $\u0022[game] body lost: {smaller.PlayerName} \u003C- {bigger.PlayerName} (promoted)\u0022 );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsmaller.MarkDead();\n\t\t\t\tGameLog.Info( \u0022[game] eaten: \u0022 \u002B smaller.PlayerName \u002B \u0022 \u003C- \u0022 \u002B bigger.PlayerName );\n\t\t\t}\n\t\t}\n\n\t\t// ---- \u5206\u8EAB/\u5B62\u5B50\u53C2\u4E0E\u5403\uFF08M3\uFF09----\n\n\t\t// \u5206\u8EAB \u00D7 \u98DF\u7269\uFF08\u540C\u6B3E\u7A7A\u95F4\u54C8\u5E0C\u7C97\u7B5B\uFF09\n\t\tforeach ( var c in _cells )\n\t\t{\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\n\t\t\tvar cp = c.Pos;\n\t\t\tvar cr = c.Radius;\n\t\t\tvar near = _food.Nearby( cp, cr );\n\n\t\t\tfor ( int k = 0; k \u003C near.Count; k\u002B\u002B )\n\t\t\t{\n\t\t\t\tint i = near[k];\n\t\t\t\tif ( !foods[i].Alive ) continue;\n\n\t\t\t\tvar dx = foods[i].Pos.x - cp.x;\n\t\t\t\tvar dy = foods[i].Pos.y - cp.y;\n\t\t\t\tif ( dx * dx \u002B dy * dy \u003E cr * cr ) continue;\n\n\t\t\t\tif ( _food.TryEat( i, c.OwnerSteamId ) )\n\t\t\t\t{\n\t\t\t\t\tc.Mass = MathF.Min( GameConfig.MaxMass, c.Mass \u002B GameConfig.FoodMass );\n\t\t\t\t\tNeonRenderer.Spark( new Vector3( foods[i].Pos.x, foods[i].Pos.y, 0f ), foods[i].ColorIndex );\n\t\t\t\t\tif ( GameSfx.IsMine( c.OwnerSteamId ) )\n\t\t\t\t\t\tGameSfx.EatFood( new Vector3( foods[i].Pos.x, foods[i].Pos.y, 0f ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// \u961F\u4F0D\u6620\u5C04\uFF08\u56E2\u961F\u8D5B\uFF09\uFF1A\u5B62\u5B50\u53EF\u98DF\u5EF6\u8FDF\u5206\u6863 \u002B \u4E0B\u65B9\u7403\u00D7\u5206\u8EAB\u514D\u4F24\u5171\u7528\n\t\tvar teamOf = new Dictionary\u003Clong, int\u003E();\n\t\tif ( MatchState.IsTeam )\n\t\t{\n\t\t\tforeach ( var b in _balls )\n\t\t\t{\n\t\t\t\tif ( b.IsValid() \u0026\u0026 b.TeamIndex \u003E= 0 ) teamOf[b.OwnerSteamId] = b.TeamIndex;\n\t\t\t}\n\t\t}\n\n\t\t// \u5B62\u5B50\u88AB\u541E\uFF1A\u81EA\u5DF1\u002B\u961F\u53CB 0.2s \u6863\uFF08v0.7.8.22/23 \u7528\u6237\u5B9A\u7A3F\uFF0C\u7403\u7403\u5927\u4F5C\u6218\u5F0F\u77ED\u65F6\u4E92\u5582\u2014\u2014\n\t\t// \u81EA\u5DF1\u9A6C\u4E0A\u56DE\u6536\u3001\u5582\u961F\u53CB\u5927\u54E5\u8DDF\u81EA\u98DF\u4E00\u6837\u987A\uFF09\uFF1B\u654C\u4EBA 0.5s \u6863\u30020.2s \u53EA\u662F\u51FA\u751F\u51C0\u7A7A\uFF1A\n\t\t// \u5927\u7403\u5410\u7684\u5B62\u5B50\u534A\u5F84\u53EF\u8D85\u8FC7 16px \u51FA\u751F\u504F\u79FB\uFF0C\u96F6\u5EF6\u8FDF\u4F1A\u88AB\u5410\u51FA\u8005\u51FA\u751F\u540C\u5E27\u5438\u56DE\uFF0C\u5410\u4E1D\u76F4\u63A5\u5931\u6548\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar blob = _cells[i];\n\t\t\tif ( blob.PieceKind != CellPiece.Kind.EjectedMass ) continue;\n\n\t\t\tbool eaten = false;\n\n\t\t\t// \u5B62\u5B50\u5582\u9053\u5177\uFF08\u0022\u8840\u91CF\u6C60\u0022\uFF0Cv0.7.4.0 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u5B62\u5B50\u78B0\u9053\u5177\u88AB\u5438\u6536\u5145\u5165\u5171\u4EAB\u79EF\u5206\u6C60\uFF0C\n\t\t\t// \u79EF\u5206 \u2265 \u5582\u98DF\u8005\u5F53\u524D\u4F53\u91CD 5% \u5373\u89E6\u53D1\u6548\u679C\uFF08\u7ACB\u5373\u751F\u6548\uFF0C\u4E0D\u5165\u80CC\u5305\uFF09\u2014\u2014\u864E\u53E3\u593A\u98DF\u3002\n\t\t\t// \u26A0\uFE0F \u5224\u5B9A\u5FC5\u987B\u5728 EjectedEdibleDelay **\u4E4B\u524D**\uFF08v0.7.5.0 \u4FEE\u0022\u5582\u4E0D\u4E2D\u0022bug\uFF09\uFF1A\n\t\t\t// \u5B62\u5B50\u5410\u51FA\u6ED1\u884C ~355 \u8DDD\u79BB\uFF0C0.5s \u53EF\u98DF\u671F\u5230\u65F6\u5DF2\u98DE\u8FC7 236\u2014\u2014\u7ECF\u8FC7\u9053\u5177\u7684\u90A3\u4E00\u5E27\u51E0\u4E4E\n\t\t\t// \u603B\u5728\u5EF6\u8FDF\u671F\u5185\uFF0C\u7B49\u5EF6\u8FDF\u8FC7\u4E86\u5B62\u5B50\u65E9\u98DE\u8FDC\u4E86\u3002\u5582\u98DF\u65E0\u8D28\u91CF\u56DE\u6D41\uFF0C\u6CA1\u6709\u0022\u79D2\u5403\u81EA\u5DF1\u0022\u6F0F\u6D1E\u3002\n\t\t\tif ( _power?.Ready ?? false )\n\t\t\t{\n\t\t\t\tvar slots = _power.Slots;\n\t\t\t\tfor ( int s = 0; s \u003C slots.Count; s\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tvar slot = slots[s];\n\t\t\t\t\tif ( !slot.Alive ) continue;\n\n\t\t\t\t\tvar pp = new Vector3( slot.Pos.x, slot.Pos.y, 0f );\n\t\t\t\t\tif ( blob.Pos.Distance( pp ) \u003E= GameConfig.PowerUpRadius \u002B 8f ) continue;\n\n\t\t\t\t\teaten = true;\n\t\t\t\t\t_power.Feed( FindBallBySteamId( blob.OwnerSteamId ), s, blob.Mass );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif ( eaten )\n\t\t\t\t{\n\t\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t\t_cellsDirty = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tforeach ( var b in _balls )\n\t\t\t{\n\t\t\t\tif ( !b.IsValid() || !b.Alive ) continue;\n\t\t\t\tvar ally = SameSide( blob.OwnerSteamId, b.OwnerSteamId, teamOf );\n\t\t\t\tif ( blob.SinceSpawn \u003C ( ally ? GameConfig.EjectedAllyEdibleDelay : GameConfig.EjectedEdibleDelay ) ) continue;\n\t\t\t\tif ( b.WorldPosition.Distance( blob.Pos ) \u003C b.Radius \u002B blob.Radius )\n\t\t\t\t{\n\t\t\t\t\tb.Mass = MathF.Min( GameConfig.MaxMass, b.Mass \u002B blob.Mass );\n\t\t\t\t\teaten = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( !eaten )\n\t\t\t{\n\t\t\t\tforeach ( var c in _cells )\n\t\t\t\t{\n\t\t\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c == blob ) continue;\n\t\t\t\t\tvar ally = SameSide( blob.OwnerSteamId, c.OwnerSteamId, teamOf );\n\t\t\t\t\tif ( blob.SinceSpawn \u003C ( ally ? GameConfig.EjectedAllyEdibleDelay : GameConfig.EjectedEdibleDelay ) ) continue;\n\t\t\t\t\tif ( c.Pos.Distance( blob.Pos ) \u003C c.Radius \u002B blob.Radius )\n\t\t\t\t\t{\n\t\t\t\t\t\tc.Mass = MathF.Min( GameConfig.MaxMass, c.Mass \u002B blob.Mass );\n\t\t\t\t\t\teaten = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( eaten )\n\t\t\t{\n\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t_cellsDirty = true;\n\t\t\t}\n\t\t}\n\n\t\t// \u8DE8\u4E3B\u4EBA\uFF1A\u7403 \u00D7 \u5206\u8EAB\uFF08\u540C\u4E3B\u4EBA\u78B0\u649E\u8D70 TickCells \u7684\u5408\u5E76\uFF0C\u4E0D\u5728\u8FD9\u91CC\uFF09\n\t\t// \u961F\u53CB\u514D\u4F24\uFF08v0.6.5.0\uFF09\uFF1A\u56E2\u961F\u8D5B\u4E0B\u5206\u8EAB\u4E0D\u4E0E\u961F\u53CB\u7684\u4E3B\u7403\u4E92\u5403\u2014\u2014teamOf \u5DF2\u5728\u4E0A\u65B9\u5EFA\u597D\n\n\t\tfor ( int i = 0; i \u003C _balls.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar ball = _balls[i];\n\t\t\tif ( !ball.IsValid() || !ball.Alive ) continue;\n\t\t\tif ( ball.IsProtected ) continue;   // \u62A4\u76FE\u671F\u53CC\u5411\u514D\u6218\uFF08\u5206\u8EAB\u6CA1\u6709\u62A4\u76FE\uFF0C\u53EA\u4F1A\u88AB\u5403\uFF09\n\n\t\t\tfor ( int j = _cells.Count - 1; j \u003E= 0; j-- )\n\t\t\t{\n\t\t\t\tvar c = _cells[j];\n\t\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\t\tif ( c.OwnerSteamId == ball.OwnerSteamId ) continue;\n\t\t\t\tif ( MatchState.IsTeam\n\t\t\t\t\t\u0026\u0026 teamOf.TryGetValue( c.OwnerSteamId, out var pt )\n\t\t\t\t\t\u0026\u0026 pt == ball.TeamIndex ) continue;   // \u961F\u53CB\u7684\u5206\u8EAB/\u4E3B\u7403\u4E92\u4E0D\u5403\n\n\t\t\t\t// \u7403\u541E\u5206\u8EAB\n\t\t\t\tif ( ball.Mass \u003E= c.Mass * GameConfig.EatRatio\n\t\t\t\t\t\u0026\u0026 ball.WorldPosition.Distance( c.Pos ) \u003C ball.Radius \u002B c.Radius )\n\t\t\t\t{\n\t\t\t\t\tball.Mass = MathF.Min( GameConfig.MaxMass, ball.Mass \u002B c.Mass );\n\t\t\t\t\t_cells.RemoveAt( j );\n\t\t\t\t\t_cellsDirty = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// \u5206\u8EAB\u53CD\u541E\u4E3B\u7403\uFF08v0.6.2.0 agar \u89C4\u5219\uFF09\uFF1A\u53EA\u635F\u5931\u4E3B\u7403\u8FD9\u4E2A\u8EAB\u4F53\u2014\u2014\u6700\u5927\u5206\u8EAB\u5347\u7EA7\u6210\u65B0\u4E3B\u7403\n\t\t\t\t// \u5E26\u76FE\u4E3B\u7403\u514D\u75AB\uFF08v0.7.3.0\uFF09\uFF1A\u62A4\u76FE\u8FDE\u0022\u88AB\u5206\u8EAB\u541E\u0022\u4E5F\u6321\u4F4F\n\t\t\t\tif ( !ball.HasBuff( PowerUpManager.Kind.Shield )\n\t\t\t\t\t\u0026\u0026 c.Mass \u003E= ball.Mass * GameConfig.EatRatio\n\t\t\t\t\t\u0026\u0026 c.Pos.Distance( ball.WorldPosition ) \u003C c.Radius \u002B ball.Radius )\n\t\t\t\t{\n\t\t\t\t\tfloat gained = ball.Mass;\n\t\t\t\t\tc.Mass = MathF.Min( GameConfig.MaxMass, c.Mass \u002B gained );\n\t\t\t\t\tSpawnPopFx( ball );\n\t\t\t\t\tNotifyBallEaten( c.OwnerSteamId, ball.OwnerSteamId, gained );\n\n\t\t\t\t\tif ( TryPromoteFromPieces( ball ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tGameLog.Info( $\u0022[game] body lost: {ball.PlayerName} \u003C- cell#{c.Id} (promoted)\u0022 );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tball.MarkDead();\n\t\t\t\t\t\tif ( !ball.IsBot ) PopPiecesOf( ball.OwnerSteamId );\n\t\t\t\t\t\tGameLog.Info( \u0022[game] eaten: \u0022 \u002B ball.PlayerName \u002B \u0022 \u003C- cell#\u0022 \u002B c.Id );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// \u8DE8\u4E3B\u4EBA\uFF1A\u5206\u8EAB \u00D7 \u5206\u8EAB\uFF08i \u5347\u5E8F\u904D\u5386\uFF1B\u88AB\u541E\u7684\u81EA\u5DF1\u8981\u9000\u7D22\u5F15\uFF09\n\t\tfor ( int i = 0; i \u003C _cells.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar a = _cells[i];\n\t\t\tif ( a.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\n\t\t\tfor ( int j = _cells.Count - 1; j \u003E i; j-- )\n\t\t\t{\n\t\t\t\tvar b = _cells[j];\n\t\t\t\tif ( b.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\t\tif ( b.OwnerSteamId == a.OwnerSteamId ) continue;\n\t\t\t\tif ( MatchState.IsTeam\n\t\t\t\t\t\u0026\u0026 teamOf.TryGetValue( a.OwnerSteamId, out var ta )\n\t\t\t\t\t\u0026\u0026 teamOf.TryGetValue( b.OwnerSteamId, out var tb )\n\t\t\t\t\t\u0026\u0026 ta == tb ) continue;   // \u961F\u53CB\u5206\u8EAB\u4E92\u4E0D\u5403\uFF08v0.6.5.0\uFF09\n\n\t\t\t\tvar big = a.Mass \u003E= b.Mass ? a : b;\n\t\t\t\tvar small = big == a ? b : a;\n\t\t\t\tif ( big.Mass \u003C small.Mass * GameConfig.EatRatio ) continue;\n\t\t\t\tif ( big.Pos.Distance( small.Pos ) \u003E= big.Radius \u002B small.Radius ) continue;\n\n\t\t\t\tbig.Mass = MathF.Min( GameConfig.MaxMass, big.Mass \u002B small.Mass );\n\t\t\t\t_cellsDirty = true;\n\t\t\t\tif ( small == a )\n\t\t\t\t{\n\t\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t\ti--;   // \u4E0B\u4E00\u4E2A\u5143\u7D20\u79FB\u4F4D\u8865\u4F4D\uFF0C\u5916\u5C42 i\u002B\u002B \u540E\u6B63\u597D\u6307\u5411\u5B83\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t_cells.RemoveAt( j );\n\t\t\t}\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E bot \u6B7B\u4EA1\u540E\u539F\u5730\u590D\u6D3B\uFF08\u4E0D\u518D\u9500\u6BC1\u91CD\u5EFA\u2014\u2014\u8FDB\u884C\u4E2D NetworkSpawn \u4E0D\u590D\u5236\u5230\u5DF2\u8FDE\u63A5\u5BA2\u6237\u7AEF\uFF0C\u5B9E\u6D4B\uFF09\n\t/// \u26A0\uFE0F \u86F0\u4F0F\u7403\uFF08Dormant\uFF09\u4E0D\u662F\u6B7B\u4EA1\u2014\u2014\u590D\u6D3B\u5B83=\u51ED\u7A7A\u591A\u51FA\u65E0\u961F FFA bot\uFF08v0.7.8.25 \u4FEE\uFF09 \u003C/summary\u003E\n\tvoid TickBots()\n\t{\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.IsBot || b.Alive ) continue;\n\t\t\tif ( b.Dormant ) continue;   // \u86F0\u4F0F\u5907\u7528\u7403\uFF1A\u7B49\u5F00\u5C40\u6FC0\u6D3B\uFF0C\u7EDD\u4E0D\u81EA\u52A8\u590D\u6D3B\n\t\t\tif ( b.SinceDeath \u003C GameConfig.BotRespawnSeconds ) continue;\n\n\t\t\tRespawnBall( b );\n\t\t}\n\t}\n\n\tvoid TickRespawn()\n\t{\n\t\tif ( !LocalBall.IsValid() ) return;\n\t\tif ( LocalBall.Alive ) return;\n\t\tif ( !Input.Pressed( \u0022Jump\u0022 ) ) return;\n\n\t\tTryRespawnLocal();\n\t}\n\n\t/// \u003Csummary\u003E \u672C\u673A\u91CD\u751F\u8BF7\u6C42\uFF08\u7A7A\u683C / \u6B7B\u4EA1\u9762\u677F\u70B9\u51FB\u5171\u7528\uFF09\uFF1A\u6743\u5A01\u7AEF\u76F4\u63A5\u539F\u5730\u590D\u6D3B\uFF1B\n\t/// \u5BA2\u6237\u7AEF\u53D1\u9759\u6001 RPC\uFF08host \u7AEF Rpc.Caller \u627E\u56DE\u8FDE\u63A5\uFF0CAlive \u540C\u6B65\u4E0B\u53D1\uFF09\u3002\n\t/// \u83DC\u5355/\u623F\u95F4\u9636\u6BB5\u4E0D\u53D7\u7406\uFF08\u86F0\u4F0F\u7403\u662F\u6B7B\u7684\uFF0C\u4F46\u6BD4\u8D5B\u6CA1\u5F00\u59CB\uFF1B\u7A7A\u683C\u5728\u7ED3\u7B97\u9762\u677F\u53E6\u6709\u7528\u9014\uFF09 \u003C/summary\u003E\n\tpublic void TryRespawnLocal()\n\t{\n\t\tif ( !_gameStarted || MatchState.MatchOver ) return;\n\t\tif ( !LocalBall.IsValid() || LocalBall.Alive ) return;\n\n\t\tGameLog.Info( \u0022[game] local respawn requested\u0022 );\n\t\tif ( NetworkManager.IsAuthority )\n\t\t{\n\t\t\tRespawnBall( LocalBall );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tNetworkManager.RequestRespawn();\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u539F\u5730\u590D\u6D3B\u4E00\u9897\u7403\uFF1A\u91CD\u7F6E\u6570\u503C \u002B Alive=true\uFF08\u540C\u6B65\u4E0B\u53D1\uFF1B\u5BF9\u8C61\u6C38\u4E0D\u9500\u6BC1\uFF09\u3002\n\t/// teamIndex \u2265 0 \u65F6\u91CD\u5199\u961F\u4F0D\u53F7\uFF08\u5F00\u5C40\u6FC0\u6D3B\u65F6\u7684\u56E2\u961F\u5206\u914D\uFF1B\u666E\u901A\u8D5B -1 \u4E0D\u52A8\uFF09\u3002\n\t/// cue=false \u7528\u4E8E\u5F00\u5C40\u6279\u91CF\u6FC0\u6D3B\uFF08\u63D0\u793A\u97F3\u53EA\u7528\u4E8E\u6BD4\u8D5B\u4E2D\u7684\u590D\u6D3B\uFF0Cv0.7.8.11\uFF09 \u003C/summary\u003E\n\tvoid RespawnBall( Ball ball, int teamIndex = -1, bool cue = true )\n\t{\n\t\tball.Mass = GameConfig.StartMass;\n\t\t// \u56E2\u961F\u8D5B\u515C\u5E95\uFF08v0.7.8.25\uFF09\uFF1A\u65E0\u961F\u7403\uFF08\u86F0\u4F0F\u8BEF\u590D\u6D3B/\u5386\u53F2\u5F02\u5E38\u6001\uFF09\u590D\u6D3B\u65F6\u8865\u7F16\u961F\uFF0C\u7EDD\u4E0D\u5E26 T=-1 \u4E0A\u573A\n\t\tif ( teamIndex \u003C 0 \u0026\u0026 MatchState.IsTeam \u0026\u0026 ball.TeamIndex \u003C 0 )\n\t\t\tteamIndex = NextTeamIndex();\n\t\tball.Init( ball.IsBot, teamIndex );   // \u91CD\u7F6E\u4FDD\u62A4\u671F/\u901F\u5EA6/\u989C\u8272\uFF08\u540C\u6B65\u4E0B\u53D1\uFF09\n\t\tball.Dormant = false;\n\t\tball.Alive = true;\n\n\t\t// host \u81EA\u5DF1\u7684\u7403/bot\uFF1A\u76F4\u63A5\u6362\u4F4D\uFF1B\u771F\u4EBA\u5BA2\u6237\u7AEF\u7684\u7403\uFF1A\u8BE5\u5BA2\u6237\u7AEF\u5728 Alive\u2192true \u65F6\u81EA\u884C\u968F\u673A\u6362\u4F4D\uFF08owner \u6A21\u62DF\uFF09\n\t\tif ( ball.IsBot || ball.OwnerSteamId == Game.SteamId.Value )\n\t\t\tball.WorldPosition = RandomSpawnPos();\n\n\t\tGameLog.Info( $\u0022[net] respawned \u0027{ball.PlayerName}\u0027\u0022 );\n\n\t\tif ( cue ) CueRespawn( ball.OwnerSteamId, ball.TeamIndex );\n\t}\n\n\t/// \u003Csummary\u003E \u672C\u673A\u662F\u5426\u8BE5\u542C\u5230\u8FD9\u58F0\u91CD\u751F\uFF1A**\u4EC5\u961F\u53CB**\u2014\u2014\u81EA\u5DF1\u90A3\u6761\u7531 Ball owner \u6A21\u62DF\u5206\u652F\u64AD\uFF0C\u8FD9\u91CC\u8DF3\u8FC7\u9632\u53E0\u97F3 \u003C/summary\u003E\n\tbool ShouldHearRespawn( long steamId, int teamIndex )\n\t{\n\t\tif ( GameSfx.IsMine( steamId ) ) return false;\n\t\tif ( !MatchState.IsTeam || teamIndex \u003C 0 ) return false;\n\n\t\tvar local = LocalBall;\n\t\treturn local.IsValid() \u0026\u0026 local.TeamIndex \u003E= 0 \u0026\u0026 local.TeamIndex == teamIndex;\n\t}\n\n\t/// \u003Csummary\u003E \u91CD\u751F\u63D0\u793A\u97F3\uFF08v0.7.8.11\uFF09\uFF1Abot/\u654C\u65B9\u91CD\u751F\u662F\u566A\u97F3\uFF0C\u53EA\u8BA9\u0022\u81EA\u5DF1\u002B\u961F\u53CB\u0022\u542C\u89C1\u2014\u2014\n\t/// host \u672C\u5730\u5224\u5B9A \u002B RespawnCue \u5E7F\u64AD\uFF0C\u5404\u7AEF\u540C\u4E00\u6761\u0022\u961F\u53CB\u624D\u54CD\u0022\u89C4\u5219 \u003C/summary\u003E\n\tvoid CueRespawn( long steamId, int teamIndex )\n\t{\n\t\tif ( ShouldHearRespawn( steamId, teamIndex ) )\n\t\t\tGameSfx.Respawn( Vector3.Zero );\n\t\tif ( Networking.IsActive ) NetworkManager.RespawnCue( steamId, teamIndex );\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u843D\u5730\uFF08RespawnCue \u5E7F\u64AD\uFF09 \u003C/summary\u003E\n\tpublic void OnRespawnCueRemote( long steamId, int teamIndex )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;   // host \u5DF2\u5728 CueRespawn \u64AD\u8FC7\uFF08\u6321\u5E7F\u64AD\u56DE\u58F0\uFF09\n\t\tif ( ShouldHearRespawn( steamId, teamIndex ) )\n\t\t\tGameSfx.Respawn( Vector3.Zero );\n\t}\n\n\t// ---- \u6BD4\u8D5B\u8FDB\u7A0B\uFF08M5\uFF09\uFF1Ahost \u6743\u5A01\u5012\u8BA1\u65F6\uFF0C\u5F52\u96F6\u5E7F\u64AD\u7ED3\u7B97\u699C ----\n\n\tvoid TickMatch()\n\t{\n\t\tif ( MatchState.MatchOver ) return;\n\n\t\t// \u53CC\u7AEF\u672C\u5730\u9012\u51CF\uFF1Ahost \u662F\u6743\u5A01\uFF0C\u5BA2\u6237\u7AEF 1Hz \u6821\u65F6\u540E\u81EA\u5DF1\u7EED\u8D70\uFF08\u89C2\u611F\u5E73\u6ED1\uFF09\n\t\tMatchState.TimeLeft = MathF.Max( 0f, MatchState.TimeLeft - Time.Delta );\n\n\t\tif ( !NetworkManager.IsAuthority ) return;\n\n\t\tif ( _sinceMatchTick \u003E 1f )\n\t\t{\n\t\t\t_sinceMatchTick = 0;\n\t\t\tNetworkManager.MatchTick( MatchState.TimeLeft );\n\t\t}\n\n\t\tif ( MatchState.TimeLeft \u003C= 0f ) EndMatch();\n\t}\n\n\t/// \u003Csummary\u003E \u6BD4\u8D5B\u7ED3\u675F\uFF08host\uFF09\uFF1A\u6309\u603B\u8D28\u91CF\uFF08\u4E3B\u7403\u002B\u5206\u8EAB\uFF09\u6392\u7ED3\u7B97\u699C\uFF0C\u5E7F\u64AD \u002B \u672C\u5730\u843D\u5730\u3002\n\t/// \u83DC\u5355\u80CC\u666F\u6F14\u793A\u8D5B\u65F6\u95F4\u5230\u5C31\u5C31\u5730\u91CD\u5F00\uFF0C\u4E0D\u8D70\u7ED3\u7B97\u3002 \u003C/summary\u003E\n\tvoid EndMatch()\n\t{\n\t\tif ( MatchState.MatchOver ) return;\n\n\t\tif ( _menuDemo )\n\t\t{\n\t\t\tMatchState.ActivateForMatch();   // \u6F14\u793A\u8D5B\u5FAA\u73AF\u91CD\u5F00\uFF08\u65E0\u8FDE\u63A5\uFF0CRPC \u7A7A\u53D1\u65E0\u5BB3\uFF09\n\t\t\tGameLog.Info( \u0022[menu] demo match restarted\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tvar rows = new List\u003CScoreWire\u003E();\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() ) continue;\n\t\t\trows.Add( new ScoreWire\n\t\t\t{\n\t\t\t\tSteamId = b.OwnerSteamId,\n\t\t\t\tName = b.PlayerName,\n\t\t\t\tMass = TotalMassFor( b ),\n\t\t\t\tTeam = (byte)Math.Max( 0, b.TeamIndex ),\n\t\t\t\tBot = b.IsBot,\n\t\t\t} );\n\t\t}\n\t\tvar arr = rows.ToArray();\n\t\tArray.Sort( arr, ( x, y ) =\u003E y.Mass.CompareTo( x.Mass ) );\n\n\t\tNetworkManager.MatchOver( arr );   // \u5BA2\u6237\u7AEF\u5C55\u793A\u7528\n\t\tOnMatchOverRemote( arr );          // host \u7ACB\u5373\u843D\u5730\uFF08\u5E7F\u64AD\u56DE\u58F0\u88AB MatchOver \u5B88\u536B\u6321\u6389\uFF09\n\n\t\tGameLog.Info( $\u0022[match] over \u2014 {arr.Length} players, winner: {( arr.Length \u003E 0 ? arr[0].Name : \u0022?\u0022 )}\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u6BD4\u8D5B\u7ED3\u675F\u843D\u5730\uFF08\u5168\u7AEF\uFF09\uFF1A\u51BB\u7ED3 \u002B \u7ED3\u7B97\u9762\u677F\uFF1B\u672C\u673A\u6210\u7EE9\u4E0A\u4E91\u3001\u672C\u5730\u699C\u843D\u76D8\uFF08\u5404\u7AEF\u5404\u81EA\u505A\uFF09\u3002\n\t/// 10 \u79D2\u540E\u81EA\u52A8\u56DE\u5927\u5385\uFF08\u591A\u4EBA\uFF09/\u4E3B\u83DC\u5355\uFF08\u5355\u673A\uFF09\uFF0C\u89C1 AfterSettlement\u3002 \u003C/summary\u003E\n\tpublic void OnMatchOverRemote( ScoreWire[] standings )\n\t{\n\t\tif ( MatchState.MatchOver ) return;   // host \u5E7F\u64AD\u56DE\u58F0\u9632\u91CD\u5165\n\t\tMatchState.MatchOver = true;\n\t\t_sinceMatchOver = 0;\n\n\t\tvar local = LocalBall;\n\t\t_endBoard?.Show( standings,\n\t\t\tlocal.IsValid() ? local.OwnerSteamId : 0,\n\t\t\tlocal.IsValid() ? local.PlayerName : \u0022\u0022 );\n\t\tLocalBoard.Record( standings );\n\t\tScoreUploader.SubmitOwn( standings, this );\n\t\tbool won = GameAchievements.Settlement( standings, local.IsValid() ? local.OwnerSteamId : 0 );   // \u6210\u5C31\uFF1A\u593A\u51A0/\u767B\u9876\n\t\tGameXp.Settlement( standings, local.IsValid() ? local.OwnerSteamId : 0, won );                    // \u7ECF\u9A8C\u7ED3\u7B97\u4E0A\u62A5\uFF08v0.7.8.19\uFF09\n\t\tGameLog.Info( $\u0022[match] settlement shown ({standings?.Length ?? 0} rows)\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u541E\u7403\u901A\u77E5\uFF08host\uFF09\uFF1A\u5E7F\u64AD\u5BA2\u6237\u7AEF \u002B \u672C\u673A\u64AD\u62A5/\u97F3\u6548 \u003C/summary\u003E\n\tvoid NotifyBallEaten( long eaterSteamId, long eatenSteamId, float massGained )\n\t{\n\t\tif ( Networking.IsActive ) NetworkManager.BallEaten( eaterSteamId, eatenSteamId, massGained );   // \u65E0\u4F1A\u8BDD\uFF08\u83DC\u5355\u6F14\u793A\u8D5B\uFF09\u4E0D\u5E7F\u64AD\n\t\tOnBallEaten( eaterSteamId, eatenSteamId, massGained );\n\t}\n\n\t/// \u003Csummary\u003E \u541E\u7403\u4E8B\u4EF6\uFF08\u53CC\u7AEF\u7EDF\u4E00\u5165\u53E3\uFF09\uFF1A\u5168\u573A\u0022\u8C01\u5403\u4E86\u8C01\u0022\u64AD\u62A5\uFF1B\u97F3\u6548\u53EA\u5BF9\u53C2\u4E0E\u7684\u53CC\u65B9\uFF1B\n\t/// \u51FB\u6740\u8005=\u672C\u673A\u65F6\u5F39\u4E2A\u4EBA\u9AD8\u5149\u6A2A\u5E45\uFF08v0.7.6.0\uFF0C\u91D1\u8272\u827A\u672F\u5B57\uFF09 \u003C/summary\u003E\n\tpublic void OnBallEaten( long eaterSteamId, long eatenSteamId, float massGained )\n\t{\n\t\tvar eater = FindBallBySteamId( eaterSteamId );\n\t\tvar eaten = FindBallBySteamId( eatenSteamId );\n\n\t\t// \u541E\u7403\u6ED1\u5165\uFF08v0.7.8.28 juice\uFF09\uFF1A\u53CC\u7AEF\u7EDF\u4E00\u5165\u53E3\u2014\u2014\u88AB\u5403\u8005\u7F29\u56E2\u6ED1\u8FDB\u5403\u8005\u5634\u91CC\u3002\n\t\t// \u5206\u8EAB\u53CD\u541E\u4E3B\u7403\u65F6 eater \u662F\u5176\u4E3B\u7403\uFF08\u53D6\u4E0D\u5230\u5177\u4F53\u5206\u8EAB\uFF09\uFF0C\u6ED1\u5411\u5176\u4E3B\u961F\u8EAB\u4F53\uFF0C\u89C2\u611F\u4E3A\u0022\u88AB\u6536\u7F16\u0022\n\t\tif ( eater.IsValid() \u0026\u0026 eaten.IsValid() )\n\t\t\tNeonRenderer.Swallow( eaten.WorldPosition, eaten.ColorIndex, eater.WorldPosition, eaten.Radius );\n\n\t\tif ( GameSfx.IsMine( eaterSteamId ) || GameSfx.IsMine( eatenSteamId ) )\n\t\t\tGameSfx.EatBall( eaten.IsValid() ? eaten.WorldPosition : Vector3.Zero );\n\n\t\tif ( GameSfx.IsMine( eaterSteamId ) )\n\t\t{\n\t\t\t// \u51FB\u6740\u9AD8\u5149\u4E09\u8FDE\uFF08v0.7.7.0\uFF09\uFF1A\u8FDE\u51FB\u6A2A\u5E45\uFF08\u6587\u6848/\u5B57\u53F7\u968F\u6863\u4F4D\uFF09\u002B \u5347\u8C03\u97F3 \u002B \u5C4F\u5E55\u9707\u52A8\n\t\t\tint combo = GameSfx.RegisterKill();\n\t\t\t_hud?.AddKillBanner( combo, eaten?.PlayerName ?? \u0022???\u0022, massGained );\n\t\t\tGameSfx.ComboSound( combo );\n\t\t\t_camera?.Shake( MathF.Min( 8f \u002B combo * 1.5f, 14f ) );\n\n\t\t\tGameAchievements.LocalKill( combo, massGained, eatenSteamId );   // \u6210\u5C31\uFF1A\u51FB\u6740\u65CF\uFF08v0.7.8.14\uFF09\n\t\t}\n\t\telse if ( GameSfx.IsMine( eatenSteamId ) )\n\t\t{\n\t\t\tGameAchievements.LocalDeath( eaterSteamId );   // \u6210\u5C31\uFF1A\u9996\u6B21\u88AB\u5403 \u002B \u8BB0\u4EC7\u540D\u5355\n\t\t}\n\n\t\t_hud?.AddKillFeed( eater?.PlayerName ?? \u0022???\u0022,\n\t\t\teaten?.PlayerName ?? \u0022???\u0022,\n\t\t\tmassGained, eaterSteamId, eatenSteamId );\n\t}\n\n\t/// \u003Csummary\u003E host \u4F4D\u79FB\u6821\u9A8C\uFF1A\u8FDC\u7AEF\u6A21\u62DF\u7684\u7403\u5355\u5E27\u4F4D\u79FB\u8D85\u7406\u8BBA\u4E0A\u9650 3 \u500D\u5373\u62C9\u56DE\uFF08\u53CD\u77AC\u79FB\u6A61\u76AE\u7B4B\uFF09 \u003C/summary\u003E\n\tvoid TickMovementValidation()\n\t{\n\t\tvar mySteamId = Game.SteamId.Value;\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() ) continue;\n\n\t\t\tvar key = b.Id;\n\t\t\tvar last = _lastPos.TryGetValue( key, out var lp ) ? lp : b.WorldPosition;\n\n\t\t\tif ( !b.IsBot \u0026\u0026 b.OwnerSteamId != mySteamId )\n\t\t\t{\n\t\t\t\tvar maxMove = b.Speed * Time.Delta * 3f \u002B 16f;\n\t\t\t\tif ( b.WorldPosition.Distance( last ) \u003E maxMove )\n\t\t\t\t\tb.WorldPosition = last;\n\t\t\t}\n\n\t\t\t_lastPos[key] = b.WorldPosition;\n\n\t\t\t// \u8F6C\u5411\u65B9\u5411\uFF08\u5206\u8EAB\u8DDF\u968F\u7528\uFF09\uFF1A\u53D6\u672C\u5E27\u4F4D\u79FB\u65B9\u5411\u5E73\u6ED1\uFF1B\u9759\u6B62\u65F6\u4FDD\u6301\u539F\u65B9\u5411\n\t\t\tvar delta = b.WorldPosition - last;\n\t\t\tif ( delta.Length \u003E 0.1f )\n\t\t\t{\n\t\t\t\tvar n = new Vector3( delta.x, delta.y, 0f ).Normal;\n\t\t\t\tvar prev = _steerDir.TryGetValue( key, out var sd ) ? sd : new Vector2( n.x, n.y );\n\t\t\t\t_steerDir[key] = new Vector2( prev.x \u002B ( n.x - prev.x ) * 0.4f, prev.y \u002B ( n.y - prev.y ) * 0.4f );\n\t\t\t}\n\t\t}\n\t}\n\n\tint CountPlayers( bool isBot )\n\t{\n\t\tint n = 0;\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( b.IsValid() \u0026\u0026 b.IsBot == isBot ) n\u002B\u002B;\n\t\t}\n\t\treturn n;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u626B\u63CF\u573A\u666F\u91CC\u7684 Ball \u5E76\u6536\u7F16\u8FDB\u5217\u8868\uFF08\u7F51\u7EDC\u5BF9\u8C61\u7684 OnStart \u4E0D\u53EF\u9760\uFF0C\u5B9E\u6D4B\u4E0D\u89E6\u53D1\uFF09\u3002\n\t/// \u672C\u673A\u7403\u5224\u5B9A\u7528\u6240\u6709\u6743\u5B57\u6BB5\uFF1A\u975E\u4EE3\u7406 \u002B \u975Ebot \u002B OwnerSteamId \u662F\u81EA\u5DF1\u7684 SteamId\u3002\n\t/// \u003C/summary\u003E\n\tvoid ScanBalls()\n\t{\n\t\t// \u5BA2\u6237\u7AEF\uFF1A\u5FEB\u7167\u4E16\u754C\u6839\u81EA\u6108\u542F\u7528\uFF08\u9996\u6B21 Tick \u53EF\u80FD\u65E9\u4E8E\u5FEB\u7167\u5E94\u7528\u5B8C\u6210\uFF0CEnsureWorld \u6251\u7A7A\u65F6\u515C\u4F4F\uFF09\n\t\tif ( !NetworkManager.IsAuthority )\n\t\t{\n\t\t\tAdoptSnapshotWorld();\n\t\t}\n\n\t\tforeach ( var b in Scene.GetAllComponents\u003CBall\u003E() )\n\t\t{\n\t\t\tif ( !b.IsValid() ) continue;\n\n\t\t\t// \u5B58\u91CF bot \u81EA\u6108\uFF1Av0.5.1.3 \u4E4B\u524D\u751F\u6210\u7684 bot OwnerSteamId=0\uFF08\u5206\u8EAB\u5934\u50CF/\u540D\u724C\u65E0\u6CD5\u5F52\u5C5E\uFF09\u2014\u2014\n\t\t\t// host \u8865\u53D1\u552F\u4E00\u5047 id\uFF08[Sync] \u81EA\u52A8\u4E0B\u53D1\u5BA2\u6237\u7AEF\uFF09\uFF0C\u70ED\u91CD\u8F7D/\u8001\u4F1A\u8BDD\u91CC\u7684 bot \u4E5F\u88AB\u8986\u76D6\n\t\t\tif ( NetworkManager.IsAuthority \u0026\u0026 b.IsBot \u0026\u0026 b.OwnerSteamId \u003C GameConfig.BotSteamIdBase )\n\t\t\t{\n\t\t\t\tb.OwnerSteamId = NextBotSteamId();\n\t\t\t\tGameLog.Info( $\u0022[game] legacy bot \u0027{b.PlayerName}\u0027 assigned steam id {b.OwnerSteamId}\u0022 );\n\t\t\t}\n\n\t\t\tif ( !_balls.Contains( b ) )\n\t\t\t{\n\t\t\t\t_balls.Add( b );\n\t\t\t}\n\n\t\t\t// \u672C\u673A\u7403 = \u81EA\u5DF1\u62E5\u6709\u7684\u90A3\u9897\uFF08IsProxy=false \u5373\u6240\u6709\u6743\uFF0C\u5929\u7136\u53EF\u9760\uFF09\u3002\n\t\t\t// \u4E0D\u8981\u7528 OwnerSteamId \u5339\u914D\uFF1A\u540C\u673A\u53CC\u5F00\u65F6\u8FDE\u63A5\u662F\u5408\u6210 ID\uFF0C\u4E0E Game.SteamId \u5BF9\u4E0D\u4E0A\uFF08\u5B9E\u6D4B\u8E29\u5751\uFF09\u3002\n\t\t\t// \u26A0\uFE0F \u6BCF\u6B21\u626B\u63CF\u90FD\u8981\u65AD\u8A00\uFF08\u4E0D\u80FD\u53EA\u5728\u65B0\u6536\u7F16\u65F6\uFF09\uFF1A\u5BA2\u6237\u7AEF\u7684 Ball.OnStart \u8DD1\u5728\u5FEB\u7167\u671F\uFF0C\n\t\t\t// \u65E9\u4E8E\u672C\u7AEF EnsureWorld \u5EFA\u76F8\u673A\uFF0C\u9996\u6B21 SetLocalBall \u65F6 _camera \u4E3A null \u88AB\u8DF3\u8FC7\uFF1B\n\t\t\t// SetTarget \u5185\u90E8\u6709\u540C\u7403\u5B88\u536B\uFF0C\u91CD\u590D\u8C03\u7528\u96F6\u5F00\u9500\u3002\n\t\t\tif ( !b.IsProxy \u0026\u0026 !b.IsBot )\n\t\t\t{\n\t\t\t\tSetLocalBall( b );\n\t\t\t}\n\t\t}\n\n\t\tfor ( int i = _balls.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tif ( !_balls[i].IsValid() ) _balls.RemoveAt( i );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u7403\u6CE8\u518C\uFF08Ball.OnStart \u5728\u6240\u6709\u673A\u5668\u4E0A\u8C03\u7528\uFF1Bhost \u751F\u6210\u4E0E\u5BA2\u6237\u7AEF\u590D\u5236\u7EDF\u4E00\u5165\u53E3\uFF09 \u003C/summary\u003E\n\tpublic void RegisterBall( Ball ball )\n\t{\n\t\tif ( !_balls.Contains( ball ) ) _balls.Add( ball );\n\t}\n\n\t/// \u003Csummary\u003E \u6807\u8BB0\u672C\u673A\u62E5\u6709\u7684\u7403\uFF0C\u5E76\u8BA9\u8DDF\u968F\u76F8\u673A\u9501\u5B9A\u5B83\u3002\n\t/// host \u4E0A\u4E3A\u8FDC\u7A0B\u8FDE\u63A5\u751F\u6210\u7684\u7403\uFF08IsRemoteOwned\uFF09\u7EDD\u4E0D\u5145\u5F53\u672C\u673A\u7403\u2014\u2014\u540C\u8D26\u53F7\u53CC\u5F00\u65F6\n\t/// \u5B83\u4EEC\u4E0E host \u7403\u540C\u540D\u540C SteamId\uFF0C\u552F\u4E00\u53EF\u9760\u7684\u533A\u522B\u5C31\u662F\u751F\u6210\u610F\u56FE\uFF08\u5B9E\u6D4B\u8E29\u5751\uFF09\u3002 \u003C/summary\u003E\n\tpublic void SetLocalBall( Ball ball )\n\t{\n\t\tif ( ball is null || ball.IsRemoteOwned ) return;\n\n\t\tLocalBall = ball;\n\t\t_camera?.SetTarget( ball );\n\t}\n\n\t/// \u003Csummary\u003E \u573A\u5730\u5185\u968F\u673A\u51FA\u751F\u70B9\uFF1A**agar \u5F0F\u5B89\u5168\u51FA\u751F**\u2014\u2014\u91C7\u6837\u591A\u70B9\uFF0C\u9009\u79BB\u0022\u80FD\u5403\u6389\u51FA\u751F\u8D28\u91CF\u0022\u7684\n\t/// \u6700\u8FD1\u5A01\u80C1\u6700\u8FDC\u7684\u70B9\u3002\u968F\u673A\u88F8\u751F\u4F1A\u88AB\u8DEF\u8FC7\u7684\u4E2D bot \u79D2\u5403\uFF08\u5B9E\u6D4B\uFF1A\u52A0\u5165 1.6s \u88AB\u5403\uFF09 \u003C/summary\u003E\n\tpublic Vector3 RandomSpawnPos()\n\t{\n\t\tvar half = GameConfig.ArenaHalfSize - 128f;\n\n\t\tVector3 best = new Vector3( Game.Random.Float( -half, half ), Game.Random.Float( -half, half ), 0f );\n\t\tvar bestScore = float.MinValue;\n\n\t\tfor ( int i = 0; i \u003C GameConfig.SpawnSafeSamples; i\u002B\u002B )\n\t\t{\n\t\t\tvar p = new Vector3( Game.Random.Float( -half, half ), Game.Random.Float( -half, half ), 0f );\n\t\t\tvar score = float.MaxValue;\n\n\t\t\tforeach ( var b in _balls )\n\t\t\t{\n\t\t\t\tif ( !b.IsValid() || !b.Alive ) continue;\n\t\t\t\tif ( b.Mass \u003C GameConfig.StartMass * GameConfig.EatRatio ) continue;   // \u5403\u4E0D\u52A8\u51FA\u751F\u7403\u7684\u5FFD\u7565\n\n\t\t\t\tvar d = p.Distance( b.WorldPosition ) - b.Radius;\n\t\t\t\tif ( d \u003C score ) score = d;\n\t\t\t}\n\n\t\t\tif ( score \u003E bestScore )\n\t\t\t{\n\t\t\t\tbestScore = score;\n\t\t\t\tbest = p;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\tstatic string LocalPlayerName()\n\t{\n\t\ttry { return Connection.Local?.DisplayName ?? \u0022You\u0022; }\n\t\tcatch { return \u0022You\u0022; }\n\t}\n\n\t// ---- \u5206\u8EAB/\u5B62\u5B50\uFF1Ahost \u6743\u5A01\u6A21\u62DF\uFF08M3\uFF09----\n\n\t/// \u003Csummary\u003E\n\t/// \u5206\u8EAB/\u5B62\u5B50\u6BCF\u5E27\u6A21\u62DF\uFF1A\u51B2\u91CF\u8870\u51CF \u2192 \u8DDF\u968F\u4E3B\u4EBA\u8F6C\u5411 \u2192 \u573A\u5730\u94B3\u5236 \u2192 \u5408\u5E76\u68C0\u67E5 \u2192 \u6E05\u7406\u3002\n\t/// \u5B62\u5B50\u65E0\u8F6C\u5411\uFF08\u6ED1\u884C\u5230\u505C\uFF09\uFF0C\u5206\u8EAB\u6309\u4E3B\u4EBA\u7403\u7684\u5E73\u6ED1\u8F6C\u5411\u65B9\u5411\u8DDF\u968F\u79FB\u52A8\u3002\n\t/// \u003C/summary\u003E\n\tvoid TickCells()\n\t{\n\t\tif ( _cells.Count == 0 ) return;\n\n\t\tvar dt = Time.Delta;\n\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = _cells[i];\n\n\t\t\t// \u4E3B\u4EBA\u7403\u5F15\u7528\u5931\u6548\uFF08\u70ED\u91CD\u8F7D\u6362\u7A0B\u5E8F\u96C6\u5BF9\u8C61 / \u4E3B\u4EBA\u771F\u6CA1\u4E86\uFF09\u2192 \u5148\u6309 SteamId \u91CD\u6302\u81EA\u6108\n\t\t\t// \uFF08v0.7.8.29\uFF1A\u70ED\u91CD\u8F7D\u4F1A\u628A\u7EC4\u4EF6\u6362\u6210\u65B0\u7A0B\u5E8F\u96C6\u5BF9\u8C61\uFF0C\u65E7 OwnerBall \u5F15\u7528\u5168\u5931\u6548\uFF0C\n\t\t\t// \u65E7\u903B\u8F91\u76F4\u63A5\u6D88\u6563 = \u6BCF\u6B21\u70ED\u91CD\u8F7D\u5206\u8EAB\u51ED\u7A7A\u84B8\u53D1\uFF09\uFF1B\u91CD\u6302\u4E0D\u4E0A\u624D\u6D88\u6563\uFF08\u5B62\u5B50\u4E2D\u6027\u5B58\u6D3B\uFF0C\u4E0D\u6E05\u7406\uFF09\n\t\t\tif ( c.PieceKind != CellPiece.Kind.EjectedMass\n\t\t\t\t\u0026\u0026 ( !c.OwnerBall.IsValid() || !c.OwnerBall.Alive ) )\n\t\t\t{\n\t\t\t\tvar re = FindBallBySteamId( c.OwnerSteamId );\n\t\t\t\tif ( ( !re.IsValid() || !re.Alive ) \u0026\u0026 _sinceScan \u003E 0.5f )\n\t\t\t\t{\n\t\t\t\t\t_sinceScan = 0;\n\t\t\t\t\tScanBalls();   // \u70ED\u91CD\u8F7D\u7A97\u53E3\uFF1A\u4E3B\u4EBA\u7403\u5176\u5B9E\u5728\u573A\u666F\u91CC\uFF08\u65B0\u7A0B\u5E8F\u96C6\u5BF9\u8C61\uFF09\uFF0C\u7ACB\u523B\u6536\u7F16\u518D\u8BD5\n\t\t\t\t\tre = FindBallBySteamId( c.OwnerSteamId );\n\t\t\t\t}\n\t\t\t\tif ( re.IsValid() \u0026\u0026 re.Alive )\n\t\t\t\t{\n\t\t\t\t\tc.OwnerBall = re;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t\t_cellsDirty = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// \u5C16\u523A\u5206\u8EAB\u5230\u671F\uFF08v0.7.4.0 \u7528\u6237\u5B9A\u7A3F B\uFF09\uFF1A\u53D8\u56DE\u666E\u901A\u5206\u8EAB\u2014\u2014\u8D28\u91CF\u4FDD\u7559\uFF0C\n\t\t\t// \u6309\u8D28\u91CF\u53D6\u5408\u5E76\u51B7\u5374\uFF08v0.7.8.27 \u66F2\u7EBF\uFF09\u540E\u6309\u666E\u901A\u5206\u8EAB\u89C4\u5219\u5408\u4F53\u56DE\u6536\uFF1B\u6B64\u540E\u4E0D\u518D\u662F\u6B66\u5668\n\t\t\tif ( c.PieceKind == CellPiece.Kind.SpikeMinion \u0026\u0026 c.SinceSpawn \u003E GameConfig.SpikeMinionLife )\n\t\t\t{\n\t\t\t\tc.PieceKind = CellPiece.Kind.SplitPiece;\n\t\t\t\tc.SinceSpawn = 0;\n\t\t\t\tc.MergeCooldown = GameConfig.MergeCooldownFor( c.Mass );\n\t\t\t\tNeonRenderer.Puff( c.Pos, c.ColorIndex );\n\t\t\t\t_cellsDirty = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// \u8D28\u91CF\u8870\u51CF\uFF08v0.7.8.24 \u7403\u7403\u5927\u4F5C\u6218\u624B\u611F\uFF09\uFF1A\u5206\u8EAB\u4E0E\u7403\u540C\u7387\u6301\u7EED\u6389\u91CD\uFF08\u5B62\u5B50\u4E0D\u8870\u51CF\uFF09\n\t\t\tif ( c.PieceKind != CellPiece.Kind.EjectedMass \u0026\u0026 c.Mass \u003E GameConfig.MassDecayMinMass )\n\t\t\t{\n\t\t\t\tc.Mass = MathF.Max( GameConfig.MassDecayMinMass, c.Mass - c.Mass * GameConfig.MassDecayPerSecond * dt );\n\t\t\t}\n\n\t\t\tc.Impulse *= MathF.Exp( -( c.PieceKind == CellPiece.Kind.EjectedMass\n\t\t\t\t? GameConfig.EjectImpulseDecay : GameConfig.SplitImpulseDecay ) * dt );\n\n\t\t\tif ( c.PieceKind != CellPiece.Kind.EjectedMass )\n\t\t\t{\n\t\t\t\tvar dir = SteerDirOf( c.OwnerBall );\n\t\t\t\tvar target = new Vector3( dir.x, dir.y, 0f ) * c.Speed;\n\n\t\t\t\t// \u5012\u6D41\u5F15\u529B\uFF08\u627918\u2463\uFF0C\u0022\u79D2\u5408\u0022\u7684\u7269\u7406\u57FA\u7840\uFF09\uFF1A\u5206\u8EAB\u6BD4\u4E3B\u7403\u91CD\uFF08\u5410\u7403\u51CF\u91CD\u53EF\u5236\u9020\u4F53\u91CD\u5DEE\uFF09\n\t\t\t\t// \u6216\u51FA\u751F\u8D85\u8FC7 PieceHomeAfterSeconds \u2192 \u81EA\u52A8\u8F6C\u5411\u4E3B\u7403\u6EDA\u56DE\uFF0C\u4E0E\u6447\u6746\u8F93\u5165\u6309\u6743\u91CD\u6DF7\u5408\u3002\n\t\t\t\t// \u5C16\u523A\u5206\u8EAB\u6392\u9664\u2014\u201420 \u79D2\u6B66\u5668\u671F\u7EDD\u4E0D\u5012\u6D41\u5408\u4F53\uFF08\u7528\u6237\u5B9A\u7A3F A/B\uFF09\n\t\t\t\tvar owner = c.OwnerBall;\n\t\t\t\tif ( owner.IsValid() \u0026\u0026 owner.Alive \u0026\u0026 c.PieceKind == CellPiece.Kind.SplitPiece \u0026\u0026 c.IsMergeReady\n\t\t\t\t\t\u0026\u0026 ( c.Mass \u003E owner.Mass || c.SinceSpawn \u003E GameConfig.PieceHomeAfterSeconds ) )\n\t\t\t\t{\n\t\t\t\t\tvar to = owner.WorldPosition - c.Pos;\n\t\t\t\t\tif ( to.Length \u003E 1f )\n\t\t\t\t\t{\n\t\t\t\t\t\tto = to.Normal;\n\t\t\t\t\t\tvar blended = new Vector3(\n\t\t\t\t\t\t\tto.x * GameConfig.PieceAttractWeight \u002B dir.x,\n\t\t\t\t\t\t\tto.y * GameConfig.PieceAttractWeight \u002B dir.y, 0f );\n\t\t\t\t\t\tif ( blended.Length \u003E 0.001f )\n\t\t\t\t\t\t\ttarget = blended.Normal * c.Speed;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// \u5206\u8EAB\u8F6C\u5411\u54CD\u5E94\u540C\u6837\u968F\u8D28\u91CF\u8870\u51CF\uFF08\u4E0E\u4E3B\u7403\u540C\u66F2\u7EBF\uFF0C\u5927\u5206\u8EAB\u4E00\u6837\u7B28\u62D9\uFF09\n\t\t\t\tvar turn = MathF.Max( GameConfig.MinTurnSpeed,\n\t\t\t\t\tGameConfig.BaseTurnSpeed * MathF.Pow( GameConfig.StartMass / c.Mass, GameConfig.TurnCurve ) );\n\t\t\t\tvar t = 1f - MathF.Exp( -turn * dt );\n\t\t\t\tc.SteerVel = Vector3.Lerp( c.SteerVel, target, t );\n\t\t\t}\n\n\t\t\tvar pos = c.Pos \u002B ( c.Impulse \u002B c.SteerVel ) * dt;\n\t\t\tvar half = MathF.Max( 0f, GameConfig.ArenaHalfSize - c.Radius );\n\t\t\tpos.x = Math.Clamp( pos.x, -half, half );\n\t\t\tpos.y = Math.Clamp( pos.y, -half, half );\n\t\t\tpos.z = 0f;\n\t\t\tc.Pos = pos;\n\t\t}\n\n\t\t// \u540C\u4E3B\u4EBA\u5408\u5E76\uFF08\u51B7\u5374\u6309\u8D28\u91CF\u66F2\u7EBF v0.7.8.27\uFF1A\u5C0F\u5206\u8EAB 1s\u3001\u5927\u5206\u8EAB\u6700\u957F 14s\uFF09\uFF1A**\u78B0\u5230\u5373\u5408\u5E76**\uFF08\u5706\u7F18\u76F8\u89E6\uFF0C\n\t\t// \u4E0D\u518D\u8981\u6C42\u5D4C\u8FDB\u5706\u5FC3\u2014\u2014\u7528\u6237\u5B9E\u6D4B\u53CD\u9988\uFF1A\u9760\u5728\u4E00\u8D77\u4E0D\u52A8\u4E0D\u7B97\uFF0C\u89C2\u611F\u5C31\u662F\u0022\u4E0D\u80FD\u5408\u5E76\u0022\uFF09\u3002\u4F18\u5148\u5E76\u56DE\u4E3B\u7403\uFF0C\u5176\u6B21\u5927\u5206\u8EAB\u541E\u5C0F\u5206\u8EAB\u3002\n\t\t// \u26A0\uFE0F \u5185\u5C42 j \u53EA\u5411\u4E0A\u626B\uFF08j \u003E i\uFF09\uFF1Aj \u6389\u5230 i \u4EE5\u4E0B\u518D RemoveAt(i) \u4F1A\u7D22\u5F15\u8D8A\u754C\n\t\t//\uFF08\u4E0B\u65B9\u5143\u7D20\u88AB\u5220\u540E i \u5DF2\u6F02\u79FB\uFF0C\u5B9E\u6D4B ArgumentOutOfRangeException \u5237\u5C4F\uFF09\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar a = _cells[i];\n\t\t\tif ( a.PieceKind != CellPiece.Kind.SplitPiece || !a.IsMergeReady ) continue;\n\n\t\t\tvar owner = a.OwnerBall;\n\t\t\tif ( owner.IsValid() \u0026\u0026 owner.Alive \u0026\u0026 owner.WorldPosition.Distance( a.Pos ) \u003C owner.Radius \u002B a.Radius )\n\t\t\t{\n\t\t\t\towner.Mass = MathF.Min( GameConfig.MaxMass, owner.Mass \u002B a.Mass );\n\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t_cellsDirty = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor ( int j = i - 1; j \u003E= 0; j-- )\n\t\t\t{\n\t\t\t\tvar b = _cells[j];\n\t\t\t\tif ( b.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\t\tif ( b.OwnerSteamId != a.OwnerSteamId || !b.IsMergeReady ) continue;\n\n\t\t\t\tvar big = a.Mass \u003E= b.Mass ? a : b;\n\t\t\t\tvar small = big == a ? b : a;\n\t\t\t\tif ( big.Pos.Distance( small.Pos ) \u003E= big.Radius \u002B small.Radius ) continue;\n\n\t\t\t\tbig.Mass = MathF.Min( GameConfig.MaxMass, big.Mass \u002B small.Mass );\n\t\t\t\t_cells.RemoveAt( j );   // \u53EA\u5220 j \u003C i \u7684\uFF0Ca \u7684\u7D22\u5F15 i \u6052\u5B9A\u5B89\u5168\n\t\t\t\t_cellsDirty = true;\n\t\t\t\tbreak;   // \u6BCF\u5E27\u6BCF\u5206\u8EAB\u5E76\u4E00\u4E2A\uFF0C\u4E0B\u4E00\u5E27\u7EE7\u7EED\n\t\t\t}\n\t\t}\n\n\t\t// \u654C\u65B9\u5206\u8EAB\u76F8\u4E92\u63A8\u6324\uFF08v0.7.8.24 \u7403\u7403\u5927\u4F5C\u6218\u624B\u611F\uFF09\uFF1A\u5403\u4E0D\u52A8=\u5B9E\u5FC3\uFF0C\u6309\u8D28\u91CF\u6743\u91CD\u4E92\u76F8\u8BA9\u4F4D\u3002\n\t\t// \u540C\u4E3B\u4EBA\u8D70\u4E0A\u65B9\u5408\u5E76\uFF1B\u56E2\u961F\u8D5B\u961F\u53CB\u53EF\u91CD\u53E0\uFF1B\u5C16\u523A\u5206\u8EAB\u662F\u6B66\u5668\u4E0D\u53C2\u4E0E\uFF08\u78B0\u5230\u8D70\u6B66\u5668\u5224\u5B9A\uFF09\n\t\tvar pushTeamOf = new Dictionary\u003Clong, int\u003E();\n\t\tif ( MatchState.IsTeam )\n\t\t{\n\t\t\tforeach ( var b in _balls )\n\t\t\t{\n\t\t\t\tif ( b.IsValid() \u0026\u0026 b.TeamIndex \u003E= 0 ) pushTeamOf[b.OwnerSteamId] = b.TeamIndex;\n\t\t\t}\n\t\t}\n\t\tfor ( int i = 0; i \u003C _cells.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar a = _cells[i];\n\t\t\tif ( a.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\tfor ( int j = i \u002B 1; j \u003C _cells.Count; j\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar b = _cells[j];\n\t\t\t\tif ( b.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\t\tif ( a.OwnerSteamId == b.OwnerSteamId ) continue;\n\t\t\t\tif ( MatchState.IsTeam\n\t\t\t\t\t\u0026\u0026 pushTeamOf.TryGetValue( a.OwnerSteamId, out var at )\n\t\t\t\t\t\u0026\u0026 pushTeamOf.TryGetValue( b.OwnerSteamId, out var bt )\n\t\t\t\t\t\u0026\u0026 at == bt ) continue;\n\t\t\t\t// \u80FD\u5403\u5C31\u5403\uFF08TickEating \u5904\u7406\uFF09\uFF0C\u4E0D\u63A8\u2014\u2014\u5426\u5219\u63A8\u6324\u4F1A\u628A\u53EF\u541E\u7684\u5BF9\u624B\u6321\u5F00\uFF0C\u5927\u5206\u8EAB\u6C38\u8FDC\u5403\u4E0D\u6389\u5C0F\u5206\u8EAB\n\t\t\t\tvar hi = MathF.Max( a.Mass, b.Mass );\n\t\t\t\tvar lo = MathF.Min( a.Mass, b.Mass );\n\t\t\t\tif ( hi \u003E= lo * GameConfig.EatRatio ) continue;\n\n\t\t\t\tvar dx = b.Pos.x - a.Pos.x;\n\t\t\t\tvar dy = b.Pos.y - a.Pos.y;\n\t\t\t\tvar rr = a.Radius \u002B b.Radius;\n\t\t\t\tvar d2 = dx * dx \u002B dy * dy;\n\t\t\t\tif ( d2 \u003E= rr * rr ) continue;\n\t\t\t\tvar d = MathF.Sqrt( MathF.Max( d2, 0.0001f ) );\n\t\t\t\tvar push = MathF.Min( rr - d, GameConfig.PushMaxStep );\n\t\t\t\tvar nx = d \u003E 0.001f ? dx / d : 1f;\n\t\t\t\tvar ny = d \u003E 0.001f ? dy / d : 0f;\n\t\t\t\tvar wa = b.Mass / ( a.Mass \u002B b.Mass );   // \u8D8A\u91CD\u8BA9\u5F97\u8D8A\u5C11\n\t\t\t\ta.Pos = new Vector3( a.Pos.x - nx * push * wa, a.Pos.y - ny * push * wa, 0f );\n\t\t\t\tb.Pos = new Vector3( b.Pos.x \u002B nx * push * ( 1f - wa ), b.Pos.y \u002B ny * push * ( 1f - wa ), 0f );\n\t\t\t\t_cellsDirty = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u6267\u884C\u5206\u88C2\uFF08host \u6743\u5A01\uFF1Bowner \u672C\u5730\u76F4\u8C03 / \u5BA2\u6237\u7AEF\u7ECF RequestSplit RPC\uFF09\u3002\n\t/// **agar \u6807\u51C6\u5168\u5458\u5206\u88C2\uFF08v0.7.1.0 \u7528\u6237\u9700\u6C42\uFF09**\uFF1A\u7A7A\u683C = \u4E3B\u7403 \u002B \u6BCF\u4E2A\u591F\u91CD\u7684\u5206\u8EAB**\u540C\u65F6\u5BF9\u534A\u5206\u88C2**\uFF0C\n\t/// \u65B0\u8EAB\u4F53**\u5404\u81EA\u671D\u5149\u6807\u5F39\u51FA**\uFF08v0.7.8.7 \u624B\u611F\uFF1AaimPoint \u4E3A\u5149\u6807\u4E16\u754C\u5750\u6807\uFF0C\u4E3B\u7403\u4E0E\u6BCF\u4E2A\u5206\u8EAB\n\t/// \u72EC\u7ACB\u7B97\u6307\u5411\uFF0Cagar \u6807\u51C6\u6247\u5F62\u94FA\u5F00\uFF09\u2014\u2014\u8FDE\u6309\u53EF\u5FEB\u901F\u94FA\u5F00\u5230\u5206\u8EAB\u4E0A\u9650\uFF081\u21922\u21924\u21928\u219216 \u53EA\u8981 4 \u6309\uFF09\u3002\n\t/// \u6BCF\u9897\u8EAB\u4F53\u72EC\u7ACB\u8FC7\u8D28\u91CF\u95E8\u69DB\uFF08\u5BF9\u534A\u540E\u4E0D\u51FB\u7A7F\u8D28\u91CF\u4E0B\u9650 1\uFF09\uFF0C\u69FD\u6EE1\u5373\u505C\u3002\n\t/// \u003C/summary\u003E\n\tpublic void DoSplit( Ball ball, Vector2 aimPoint )\n\t{\n\t\tif ( !GameConfig.EnableSplit || !NetworkManager.IsAuthority ) return;\n\t\tif ( ball is null || !ball.IsValid() || !ball.Alive ) return;\n\t\tif ( ball.Mass \u003C GameConfig.SplitMinMass ) return;\n\n\t\tvar mainDir = FallbackDir( ball, DirTo( ball.WorldPosition, aimPoint ) );\n\n\t\t// \u26A0\uFE0F \u5148\u6355\u83B7\u65E7\u5206\u8EAB\u6570\u91CF\u518D\u751F\u6210\u4E3B\u7403\u65B0\u5206\u8EAB\uFF1ASpawnPiece \u662F\u5C3E\u90E8\u8FFD\u52A0\uFF0C\u82E5\u5FAA\u73AF\u4ECE\n\t\t// _cells.Count-1 \u8D77\u6B65\u4F1A\u7B2C\u4E00\u8F6E\u5C31\u904D\u5386\u5230\u521A\u5F39\u51FA\u7684\u65B0\u5206\u8EAB\u628A\u5B83\u518D\u5288\u4E00\u6B21\u2014\u2014\n\t\t// \u4E00\u6B21\u7A7A\u683C\u51FA 3 \u9897\uFF08\u672C\u4F53\u002B\u65B0\u5206\u8EAB\u002B\u65B0\u5206\u8EAB\u7684\u5B6A\u751F\uFF09\uFF0C\u5B9E\u6D4B v0.7.8.26 \u4FEE\n\t\tint oldCount = _cells.Count;\n\n\t\t// \u4E3B\u7403\u5BF9\u534A\n\t\tif ( CountPiecesOf( ball.OwnerSteamId ) \u003C GameConfig.MaxSplitPieces )\n\t\t{\n\t\t\tvar pieceMass = ball.Mass * 0.5f;\n\t\t\tball.Mass -= pieceMass;\n\t\t\tSpawnPiece( ball, mainDir, pieceMass );\n\t\t\tNeonRenderer.Puff( ball.WorldPosition, ball.ColorIndex );\n\t\t}\n\n\t\t// \u6BCF\u4E2A\u591F\u91CD\u7684**\u65E7**\u5206\u8EAB\u4E5F\u5BF9\u534A\uFF08**\u5404\u81EA\u671D\u5149\u6807\u5F39\u51FA**\uFF1B\u5012\u5E8F\u53EA\u904D\u5386\u5230 oldCount\uFF0C\n\t\t// \u672C\u8F6E\u65B0\u751F\u6210\u7684\u4E3B\u7403\u5206\u8EAB/\u5B6A\u751F\u4E00\u5F8B\u4E0D\u53C2\u4E0E\u518D\u5206\u88C2\uFF09\u3002\n\t\t// \u65B0\u5206\u8EAB\u540C\u6837\u8D34\u7740\u6BCD\u5206\u8EAB**\u5916\u7F18**\u751F\u6210\uFF08v0.7.2.0\uFF09\uFF0C\u5426\u5219 1s \u51B7\u5374\u4E00\u5230\u5C31\u88AB\u5438\u56DE\u53BB\n\t\tfor ( int i = oldCount - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = _cells[i];\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c.OwnerSteamId != ball.OwnerSteamId ) continue;\n\t\t\tif ( c.Mass \u003C GameConfig.SplitMinMass * 2f ) continue;\n\t\t\tif ( CountPiecesOf( ball.OwnerSteamId ) \u003E= GameConfig.MaxSplitPieces ) break;\n\n\t\t\tvar half = c.Mass * 0.5f;\n\t\t\tc.Mass -= half;\n\t\t\tvar twinRadius = GameConfig.StartRadius * MathF.Sqrt( half / GameConfig.StartMass );\n\t\t\tvar dir = DirOr( c.Pos, aimPoint, mainDir );\n\n\t\t\tvar twin = new CellPiece\n\t\t\t{\n\t\t\t\tId = _nextCellId\u002B\u002B,\n\t\t\t\tPieceKind = CellPiece.Kind.SplitPiece,\n\t\t\t\tOwnerSteamId = c.OwnerSteamId,\n\t\t\t\tOwnerBall = c.OwnerBall,\n\t\t\t\tColorIndex = c.ColorIndex,\n\t\t\t\tMass = half,\n\t\t\t\tPos = c.Pos \u002B new Vector3( dir.x, dir.y, 0f ) * ( c.Radius \u002B twinRadius ),\n\t\t\t\tDrawPos = c.Pos,\n\t\t\t\tImpulse = new Vector3( dir.x, dir.y, 0f ) * GameConfig.SplitImpulseSpeed,\n\t\t\t\tSinceSpawn = 0,\n\t\t\t\tMergeCooldown = GameConfig.MergeCooldownFor( half ),   // v0.7.8.27 \u66F2\u7EBF\n\t\t\t};\n\t\t\tClampToArena( twin );\n\t\t\t_cells.Add( twin );\n\t\t\t_cellsDirty = true;\n\t\t}\n\n\t\tGameLog.Info( $\u0022[game] split-all \u0027{ball.PlayerName}\u0027\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u751F\u6210\u4E00\u4E2A\u5206\u88C2\u5206\u8EAB\uFF08\u8C03\u7528\u65B9\u5DF2\u6263\u4E3B\u7403\u8D28\u91CF\uFF09\u3002\n\t/// **\u8D34\u7740\u4E3B\u7403\u5916\u7F18\u751F\u6210\uFF08v0.7.2.0\uFF09**\uFF1A\u539F 0.4\u00D7\u534A\u5F84\u5728\u7403\u4F53\u5185\u90E8\uFF0C\u5927\u7403\u5206\u8EAB\u98DE\u4E0D\u51FA\u5408\u5E76\u5438\u9644\u8303\u56F4\uFF0C\n\t/// 1s \u51B7\u5374\u4E00\u5230\u5C31\u88AB\u5438\u56DE\u4E3B\u7403\u2014\u2014\u73A9\u5BB6\u5B9E\u9645\u6C38\u8FDC\u53EA\u6709\u4E00\u9897\u8EAB\u4F53\uFF0C\u4E3B\u7403\u4E00\u6B7B\u65E0\u8EAB\u53EF\u664B\u5347\u76F4\u63A5\u771F\u6B7B \u003C/summary\u003E\n\tCellPiece SpawnPiece( Ball ball, Vector2 dir, float mass )\n\t{\n\t\tvar pieceRadius = GameConfig.StartRadius * MathF.Sqrt( mass / GameConfig.StartMass );\n\t\tvar c = new CellPiece\n\t\t{\n\t\t\tId = _nextCellId\u002B\u002B,\n\t\t\tPieceKind = CellPiece.Kind.SplitPiece,\n\t\t\tOwnerSteamId = ball.OwnerSteamId,\n\t\t\tOwnerBall = ball,\n\t\t\tColorIndex = ball.ColorIndex,\n\t\t\tMass = mass,\n\t\t\tPos = ball.WorldPosition \u002B new Vector3( dir.x, dir.y, 0f ) * ( ball.Radius \u002B pieceRadius ),\n\t\t\tDrawPos = ball.WorldPosition,\n\t\t\tImpulse = new Vector3( dir.x, dir.y, 0f ) * GameConfig.SplitImpulseSpeed,\n\t\t\tSinceSpawn = 0,\n\t\t\tMergeCooldown = GameConfig.MergeCooldownFor( mass ),   // v0.7.8.27 \u66F2\u7EBF\uFF1A\u5206\u8EAB\u8D8A\u5927\u5408\u56DE\u8D8A\u4E45\n\t\t};\n\t\tClampToArena( c );\n\t\t_cells.Add( c );\n\t\t_cellsDirty = true;\n\t\treturn c;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u649E\u523A\u70B8\u88C2\uFF1A\u8FDE\u73AF\u5F3A\u5236\u5BF9\u534A\u5206\u88C2\uFF0C\u76F4\u5230\u5206\u8EAB\u69FD\u6EE1\u6216\u8D28\u91CF\u6389\u5230\u5206\u88C2\u95E8\u69DB\u4EE5\u4E0B\u3002\n\t/// \u8FD4\u56DE\u662F\u5426\u771F\u70B8\u4E86\u4E1C\u897F\u2014\u2014\u5927\u7403\u505C\u5728\u523A\u4E0A\u4F1A\u6BCF\u5E27\u89E6\u53D1\uFF0C\u9760\u5B83\u6321\u4F4F\u7279\u6548/\u65E5\u5FD7\u5237\u5C4F\u3002\n\t/// \u003C/summary\u003E\n\tbool DoBurst( Ball ball )\n\t{\n\t\tvar slots = GameConfig.MaxSplitPieces - CountPiecesOf( ball.OwnerSteamId );\n\t\tif ( slots \u003C= 0 || ball.Mass \u003C GameConfig.SplitMinMass * 2f ) return false;\n\n\t\tint n = 0;\n\t\twhile ( n \u003C slots \u0026\u0026 ball.Mass \u003E= GameConfig.SplitMinMass * 2f )\n\t\t{\n\t\t\tvar pieceMass = ball.Mass * 0.5f;\n\t\t\tball.Mass -= pieceMass;\n\t\t\tvar p = SpawnPiece( ball, RandomDir(), pieceMass );\n\t\t\tp.MergeCooldown = GameConfig.SpikePieceMergeCooldown;   // \u649E\u523A\u788E\u7247\uFF1A\u77ED\u51B7\u5374\uFF0C\u4E3B\u4EBA\u53EF\u4EE5\u5F88\u5FEB\u5403\u56DE\u6765\n\t\t\tn\u002B\u002B;\n\t\t}\n\n\t\tSpawnPopFx( ball );\n\t\tGameLog.Info( $\u0022[game] burst on spike: \u0027{ball.PlayerName}\u0027 into {n} pieces\u0022 );\n\t\treturn true;\n\t}\n\n\t/// \u003Csummary\u003E \u5927\u5206\u8EAB\u649E\u523A\uFF1A\u5BF9\u534A\u88C2\u6210\u4E24\u4E2A\uFF08\u5404\u81EA\u91CD\u7F6E\u5408\u5E76\u51B7\u5374\uFF09\uFF1B\u69FD\u6EE1\u5219\u88AB\u523A\u5438\u6536 \u003C/summary\u003E\n\tvoid BurstPiece( CellPiece c )\n\t{\n\t\tvar slots = GameConfig.MaxSplitPieces - CountPiecesOf( c.OwnerSteamId );\n\t\tif ( slots \u003C= 0 )\n\t\t{\n\t\t\t_cells.Remove( c );\n\t\t\t_cellsDirty = true;\n\t\t\treturn;\n\t\t}\n\n\t\tvar dir = RandomDir();\n\t\tvar half = c.Mass * 0.5f;\n\n\t\tc.Mass = half;\n\t\tc.SinceSpawn = 0;\n\t\tc.MergeCooldown = GameConfig.SpikePieceMergeCooldown;\n\t\tc.Impulse = new Vector3( dir.x, dir.y, 0f ) * GameConfig.SplitImpulseSpeed;\n\n\t\tvar d2 = -dir;\n\t\tvar other = new CellPiece\n\t\t{\n\t\t\tId = _nextCellId\u002B\u002B,\n\t\t\tPieceKind = CellPiece.Kind.SplitPiece,\n\t\t\tOwnerSteamId = c.OwnerSteamId,\n\t\t\tOwnerBall = c.OwnerBall,\n\t\t\tColorIndex = c.ColorIndex,\n\t\t\tMass = half,\n\t\t\tPos = c.Pos \u002B new Vector3( d2.x, d2.y, 0f ) * ( c.Radius * 0.5f ),\n\t\t\tDrawPos = c.Pos,\n\t\t\tImpulse = new Vector3( d2.x, d2.y, 0f ) * GameConfig.SplitImpulseSpeed,\n\t\t\tSinceSpawn = 0,\n\t\t\tMergeCooldown = GameConfig.SpikePieceMergeCooldown,\n\t\t};\n\t\tClampToArena( other );\n\t\t_cells.Add( other );\n\t\t_cellsDirty = true;\n\n\t\tNeonRenderer.Pop( c.Pos, c.ColorIndex, c.Radius * 2f );\n\t\tif ( GameSfx.IsMine( c.OwnerSteamId ) )\n\t\t\tGameSfx.Pop( c.Pos );\n\t\tNetworkManager.PoppedEffect( c.Pos, (byte)c.ColorIndex, c.Radius * 2f, c.OwnerSteamId );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u5C16\u523A\u5224\u5B9A\uFF08host\uFF0Cv0.5.1.4 \u7528\u6237\u5B9A\u7A3F agar \u75C5\u6BD2\u89C4\u5219\uFF09\uFF1A\n\t/// - \u7EC6\u80DE**\u6BD4\u523A\u5C0F**\uFF1A\u78B0\u5230\u5373\u88AB\u5C16\u523A\u5403\u6389\uFF08\u73A9\u5BB6\u6B7B\u4EA1 / \u5206\u8EAB\u6D88\u5931\uFF09\uFF1B\n\t/// - \u7EC6\u80DE**\u6BD4\u523A\u5927**\uFF1A\u628A\u5C16\u523A\u5403\u6389\uFF08\u002BSpikeMass\uFF09\u5E76\u5F3A\u5236\u89E6\u53D1\u5206\u88C2\uFF08DoBurst \u8FDE\u73AF\u70B8\uFF09\uFF0C\u5C16\u523A\u5728\u522B\u5904\u91CD\u751F\uFF08\u573A\u4E0A\u6052 12 \u9897\uFF09\uFF1B\n\t/// - \u5B62\u5B50\u78B0\u523A\u82AF\u88AB\u5438\u6536\uFF08\u5582\u523A\uFF09\u3002\u51FA\u751F\u4FDD\u62A4\u671F\u5185\u514D\u75AB\u5C16\u523A\u3002\n\t/// \u003C/summary\u003E\n\tvoid TickSpikes()\n\t{\n\t\tvar spikes = _spikes;\n\t\tif ( spikes is null ) return;\n\n\t\tvar touch = GameConfig.SpikeRadius * GameConfig.SpikeTouchFactor;\n\n\t\t// \u7403 \u00D7 \u523A\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.Alive || b.IsProtected ) continue;   // \u62A4\u76FE\u671F\u514D\u75AB\u5C16\u523A\n\n\t\t\tvar bp = b.WorldPosition;\n\t\t\tfor ( int i = 0; i \u003C spikes.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar dx = spikes[i].x - bp.x;\n\t\t\t\tvar dy = spikes[i].y - bp.y;\n\t\t\t\tvar rr = b.Radius \u002B touch;\n\t\t\t\tif ( dx * dx \u002B dy * dy \u003E rr * rr ) continue;\n\n\t\t\t\tif ( b.Radius \u003E GameConfig.SpikeRadius )\n\t\t\t\t{\n\t\t\t\t\t// \u5403\u523A\uFF1A\u002B100 \u8D28\u91CF \u002B \u5F3A\u5236\u5206\u88C2\uFF0C\u523A\u91CD\u751F\u5230\u65B0\u4F4D\u7F6E\n\t\t\t\t\tb.Mass = MathF.Min( GameConfig.MaxMass, b.Mass \u002B GameConfig.SpikeMass );\n\t\t\t\t\tDoBurst( b );\n\t\t\t\t\tRespawnSpike( i );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// \u88AB\u523A\u5403\uFF08v0.6.2.0 agar \u89C4\u5219\uFF09\uFF1A\u53EA\u635F\u5931\u8FD9\u4E2A\u8EAB\u4F53\u2014\u2014\u6700\u5927\u5206\u8EAB\u5347\u7EA7\u6210\u65B0\u4E3B\u7403\n\t\t\t\t\tSpawnPopFx( b );\n\n\t\t\t\t\tif ( TryPromoteFromPieces( b ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tGameLog.Info( $\u0022[game] body lost to spike: {b.PlayerName} (promoted)\u0022 );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tb.MarkDead();\n\t\t\t\t\t\tif ( !b.IsBot ) PopPiecesOf( b.OwnerSteamId );\n\t\t\t\t\t\tGameLog.Info( \u0022[game] eaten by spike: \u0022 \u002B b.PlayerName );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// \u5206\u8EAB \u00D7 \u523A\uFF08\u540C\u89C4\u5219\uFF1B\u5012\u5E8F\u904D\u5386\u2014\u2014\u88AB\u5403\u8981\u79FB\u9664\uFF09\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = _cells[i];\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\n\t\t\tfor ( int sIdx = 0; sIdx \u003C spikes.Length; sIdx\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar dx = spikes[sIdx].x - c.Pos.x;\n\t\t\t\tvar dy = spikes[sIdx].y - c.Pos.y;\n\t\t\t\tvar rr = c.Radius \u002B touch;\n\t\t\t\tif ( dx * dx \u002B dy * dy \u003E rr * rr ) continue;\n\n\t\t\t\tif ( c.Radius \u003E GameConfig.SpikeRadius )\n\t\t\t\t{\n\t\t\t\t\tc.Mass = MathF.Min( GameConfig.MaxMass, c.Mass \u002B GameConfig.SpikeMass );\n\t\t\t\t\tBurstPiece( c );   // \u5F3A\u5236\u5BF9\u534A\u88C2\uFF08\u788E\u7247\u77ED\u51B7\u5374\uFF0C\u4E3B\u4EBA\u80FD\u5FEB\u901F\u5403\u56DE\uFF09\n\t\t\t\t\tRespawnSpike( sIdx );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tNeonRenderer.Puff( c.Pos, c.ColorIndex );\n\t\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t\t_cellsDirty = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// \u5B62\u5B50\u5582\u523A\uFF08v0.7.8.24 \u7403\u7403\u5927\u4F5C\u6218\u5F0F\uFF09\uFF1A\u5438\u6536\u5145\u80FD \u2192 \u5582\u6EE1\u7206\u5F00\u5C04\u523A\u3002\u523A\u4E0D\u6362\u4F4D\u4E0D\u91CD\u751F\uFF0C\n\t\t// \u5582\u98DF\u91CF\u5F52\u96F6\u91CD\u65B0\u6512\u2014\u2014\u0022\u5728\u654C\u4EBA\u8EAB\u8FB9\u517B\u523A\u0022\u7684\u6218\u672F\u6838\u5FC3\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = _cells[i];\n\t\t\tif ( c.PieceKind != CellPiece.Kind.EjectedMass ) continue;\n\n\t\t\tfor ( int sIdx = 0; sIdx \u003C spikes.Length; sIdx\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar dx = spikes[sIdx].x - c.Pos.x;\n\t\t\t\tvar dy = spikes[sIdx].y - c.Pos.y;\n\t\t\t\tif ( dx * dx \u002B dy * dy \u003E touch * touch ) continue;\n\n\t\t\t\tif ( _spikeFed is not null \u0026\u0026 (uint)sIdx \u003C (uint)_spikeFed.Length )\n\t\t\t\t{\n\t\t\t\t\t_spikeFed[sIdx] \u002B= c.Mass;\n\t\t\t\t\tNetworkManager.SpikeFed( sIdx, _spikeFed[sIdx] );   // \u5BA2\u6237\u7AEF\u955C\u50CF\uFF08\u523A\u957F\u5927\u663E\u793A\u7528\uFF09\n\t\t\t\t\tif ( _spikeFed[sIdx] \u003E= GameConfig.SpikeGrowTarget )\n\t\t\t\t\t\tBurstSpike( sIdx );\n\t\t\t\t}\n\n\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t_cellsDirty = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tTickSpines();\n\t\tTickSpikeMinions( spikes );\n\t}\n\n\t/// \u003Csummary\u003E \u523A\u7206\uFF08host \u6743\u5A01\uFF0Cv0.7.8.24\uFF09\uFF1A\u5582\u98DF\u91CF\u6E05\u96F6 \u002B \u5747\u5300\u89D2\u5EA6\u5C04\u51FA\u5F39\u5E55\uFF08\u547D\u4E2D\u5728 TickSpines\uFF09\u3002\n\t/// \u89D2\u5EA6\u516C\u5F0F SpineDir \u4E0E\u5BA2\u6237\u7AEF OnSpikeBurstRemote \u5171\u7528\uFF08\u786E\u5B9A\u6027\uFF0C\u89C6\u89C9\u5F39\u9053=\u6743\u5A01\u5F39\u9053\uFF09 \u003C/summary\u003E\n\tvoid BurstSpike( int index )\n\t{\n\t\tif ( _spikes is null || _spikeFed is null || (uint)index \u003E= (uint)_spikes.Length ) return;\n\t\t_spikeFed[index] = 0f;\n\n\t\tvar pos = _spikes[index];\n\t\tvar n = GameConfig.SpikeBurstCount;\n\t\tfor ( int k = 0; k \u003C n; k\u002B\u002B )\n\t\t{\n\t\t\t_spines.Add( new SpikeSpine\n\t\t\t{\n\t\t\t\tPos = new Vector2( pos.x, pos.y ),\n\t\t\t\tVel = SpineDir( k, n, index ) * GameConfig.SpineSpeed,\n\t\t\t\tSinceSpawn = 0,   // TimeSince \u9ED8\u8BA4\u503C\u22600\uFF08\u8BB0\u5F55\u65F6\u523B 0 \u2192 \u8BFB\u51FA\u5DE8\u5927\u503C\u7ACB\u5373\u8FC7\u671F\uFF09\uFF0C\u5FC5\u987B\u663E\u5F0F\u5F52\u96F6\n\t\t\t} );\n\t\t}\n\t\tNetworkManager.SpikeBurst( index );   // \u5168\u7AEF\u7206\u6548\uFF1B\u5BA2\u6237\u7AEF\u5728\u540C\u6B3E\u89D2\u5EA6\u751F\u6210\u89C6\u89C9\u5F39\u5E55\n\t}\n\n\t/// \u003Csummary\u003E \u5F39\u5E55\u65B9\u5411\uFF08\u786E\u5B9A\u6027\u516C\u5F0F\uFF0Chost \u6743\u5A01\u4E0E\u5BA2\u6237\u7AEF\u89C6\u89C9\u5171\u7528\uFF09\uFF1A\u5747\u5300\u5706\u5468 \u002B \u6309\u523A\u5E8F\u53F7\u9519\u5F00 \u003C/summary\u003E\n\tstatic Vector2 SpineDir( int k, int n, int index )\n\t{\n\t\tvar ang = k / (float)n * MathF.PI * 2f \u002B index * 0.7f;\n\t\treturn new Vector2( MathF.Cos( ang ), MathF.Sin( ang ) );\n\t}\n\n\t/// \u003Csummary\u003E \u523A\u7206\u5F39\u5E55\u63A8\u8FDB\uFF08host \u6743\u5A01\uFF09\uFF1A\u547D\u4E2D\u7403/\u5206\u8EAB\u6263\u8D28\u91CF\uFF08\u62A4\u76FE\u4E0D\u9632\u5C16\u523A\u65CF\uFF1B\u51FA\u751F\u4FDD\u62A4\u514D\u75AB\uFF09\uFF0C\n\t/// \u5230\u671F\u6D88\u6563\u3002\u4E2D\u7ACB\u4F24\u5BB3\u2014\u2014\u547D\u4E2D\u8C01\u90FD\u6263\uFF08\u5305\u62EC\u5582\u523A\u8005\u81EA\u5DF1\uFF09\uFF0C\u5F39\u5E55\u672C\u4F53\u6D88\u5931 \u003C/summary\u003E\n\tvoid TickSpines()\n\t{\n\t\tvar dt = Time.Delta;\n\t\tfor ( int i = _spines.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar s = _spines[i];\n\t\t\tif ( s.SinceSpawn \u003E GameConfig.SpineLife ) { _spines.RemoveAt( i ); continue; }\n\t\t\ts.Pos \u002B= s.Vel * dt;\n\n\t\t\tbool consumed = false;\n\n\t\t\tforeach ( var b in _balls )\n\t\t\t{\n\t\t\t\tif ( !b.IsValid() || !b.Alive || b.IsProtected ) continue;\n\t\t\t\tvar dx = b.WorldPosition.x - s.Pos.x;\n\t\t\t\tvar dy = b.WorldPosition.y - s.Pos.y;\n\t\t\t\tvar rr = b.Radius \u002B GameConfig.SpineRadius;\n\t\t\t\tif ( dx * dx \u002B dy * dy \u003E= rr * rr ) continue;\n\n\t\t\t\tb.Mass = MathF.Max( GameConfig.StartMass, b.Mass * ( 1f - GameConfig.SpineHitMassFraction ) );\n\t\t\t\tNetworkManager.SpineHit( s.Pos.x, s.Pos.y, (byte)Math.Clamp( b.ColorIndex, 0, 255 ) );\n\t\t\t\tconsumed = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( consumed ) { _spines.RemoveAt( i ); continue; }\n\n\t\t\tfor ( int j = _cells.Count - 1; j \u003E= 0; j-- )\n\t\t\t{\n\t\t\t\tvar c = _cells[j];\n\t\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\t\tvar dx = c.Pos.x - s.Pos.x;\n\t\t\t\tvar dy = c.Pos.y - s.Pos.y;\n\t\t\t\tvar rr = c.Radius \u002B GameConfig.SpineRadius;\n\t\t\t\tif ( dx * dx \u002B dy * dy \u003E= rr * rr ) continue;\n\n\t\t\t\tc.Mass = MathF.Max( GameConfig.StartMass, c.Mass * ( 1f - GameConfig.SpineHitMassFraction ) );\n\t\t\t\t_cellsDirty = true;\n\t\t\t\tNetworkManager.SpineHit( s.Pos.x, s.Pos.y, (byte)Math.Clamp( c.ColorIndex, 0, 255 ) );\n\t\t\t\t_spines.RemoveAt( i );\n\t\t\t\tconsumed = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( consumed ) continue;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u523A\u7206\u5F39\u5E55\uFF08\u5BA2\u6237\u7AEF\u955C\u50CF\uFF09\uFF1A\u7EAF\u89C6\u89C9\u5F39\u9053\u63A8\u8FDB \u002B \u5230\u671F\u6D88\u6563\uFF0C\u4E0D\u5224\u547D\u4E2D\uFF08\u6263\u8D28\u91CF\u7531 host \u6743\u5A01\u4E0B\u53D1\uFF09 \u003C/summary\u003E\n\tvoid TickSpinesVisual()\n\t{\n\t\tvar dt = Time.Delta;\n\t\tfor ( int i = _spines.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar s = _spines[i];\n\t\t\tif ( s.SinceSpawn \u003E GameConfig.SpineLife ) { _spines.RemoveAt( i ); continue; }\n\t\t\ts.Pos \u002B= s.Vel * dt;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u5C16\u523A\u5206\u8EAB\u5224\u5B9A\uFF08v0.7.4.0\uFF09\uFF1A\u654C\u65B9\u73A9\u5BB6/\u5206\u8EAB\u78B0\u5230\u6309\u91CE\u523A\u89C4\u5219\u2014\u2014\u6BD4\u523A\u5927\u88AB\u70B8\uFF08\u8FDE\u73AF\u5BF9\u534A\u002B\u523A\u88AB\u5403\uFF09\uFF0C\n\t/// \u6BD4\u523A\u5C0F\u88AB\u523A\u5403\uFF08\u6B7B/\u664B\u5347\uFF0C\u523A\u4E0D\u6D88\u8017\uFF09\u3002**\u540C\u4E3B\u4EBA\u4E0E\u56E2\u961F\u8D5B\u961F\u53CB\u514D\u75AB**\uFF08\u6B66\u5668\u4E0D\u4F24\u81EA\u5DF1\u4EBA\uFF09\u3002\n\t/// \u70B8\u6389\u0022\u6BD4\u81EA\u5DF1\uFF08\u4E3B\u4EBA\uFF09\u5927\u7684\u7403\u0022\u2192 \u4E3B\u4EBA\u5956 3 \u79D2\u65E0\u654C\uFF08\u7528\u6237\u5B9A\u7A3F C\uFF1A\u6B66\u5668\u4E13\u7ED9\u5C0F\u7403\u53CD\u5927\u7403\uFF0C\n\t/// \u5927\u7403\u7528\u5B83\u70B8\u5C0F\u7403\u96F6\u5956\u52B1\uFF09\u3002\u5224\u5B9A\u82AF\u4E0E\u91CE\u523A\u540C\u6B3E\uFF08SpikeRadius \u00D7 SpikeTouchFactor\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tvoid TickSpikeMinions( Vector2[] spikes )\n\t{\n\t\tvar touch = GameConfig.SpikeRadius * GameConfig.SpikeTouchFactor;\n\t\tvar teamOf = new Dictionary\u003Clong, int\u003E();\n\t\tif ( MatchState.IsTeam )\n\t\t{\n\t\t\tforeach ( var b in _balls )\n\t\t\t{\n\t\t\t\tif ( b.IsValid() \u0026\u0026 b.TeamIndex \u003E= 0 ) teamOf[b.OwnerSteamId] = b.TeamIndex;\n\t\t\t}\n\t\t}\n\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar minion = _cells[i];\n\t\t\tif ( minion.PieceKind != CellPiece.Kind.SpikeMinion ) continue;\n\n\t\t\tvar owner = minion.OwnerBall;\n\t\t\tbool ownerAlive = owner.IsValid() \u0026\u0026 owner.Alive;\n\n\t\t\tbool consumed = false;\n\n\t\t\t// \u654C\u65B9\u7403 \u00D7 \u5C16\u523A\u5206\u8EAB\n\t\t\tforeach ( var b in _balls )\n\t\t\t{\n\t\t\t\tif ( !b.IsValid() || !b.Alive ) continue;\n\t\t\t\tif ( b == owner ) continue;   // \u4E3B\u4EBA\u514D\u75AB\n\t\t\t\tif ( MatchState.IsTeam \u0026\u0026 ownerAlive \u0026\u0026 SameTeam( b, owner ) ) continue;\n\n\t\t\t\tvar d = b.WorldPosition.Distance( minion.Pos );\n\t\t\t\tif ( d \u003E= b.Radius \u002B touch ) continue;\n\n\t\t\t\tif ( b.Radius \u003E GameConfig.SpikeRadius )\n\t\t\t\t{\n\t\t\t\t\t// \u5927\u7403\u88AB\u70B8\uFF1A\u5403\u6389\u5C16\u523A\u5206\u8EAB\u7684\u8D28\u91CF \u002B \u8FDE\u73AF\u5BF9\u534A\u70B8\u88C2\uFF1B\n\t\t\t\t\t// \u70B8\u6389\u0022\u6BD4\u4E3B\u4EBA\u5927\u7684\u7403\u0022 \u2192 \u4E3B\u4EBA 3s \u65E0\u654C\uFF08\u590D\u7528\u62A4\u76FE buff\uFF09\n\t\t\t\t\tfloat gained = minion.Mass;\n\t\t\t\t\tb.Mass = MathF.Min( GameConfig.MaxMass, b.Mass \u002B gained );\n\t\t\t\t\tDoBurst( b );\n\t\t\t\t\tNotifyBallEaten( minion.OwnerSteamId, b.OwnerSteamId, gained );\n\n\t\t\t\t\tif ( ownerAlive \u0026\u0026 b.Mass \u003E owner.Mass )\n\t\t\t\t\t{\n\t\t\t\t\t\towner.ApplyBuff( (byte)PowerUpManager.Kind.Shield, GameConfig.SpikeMinionRewardSeconds );\n\t\t\t\t\t\tGameAchievements.SpikeMinionBlast( minion.OwnerSteamId );   // \u6210\u5C31\uFF1A\u4EE5\u5C0F\u535A\u5927\uFF08v0.7.8.13\uFF09\n\t\t\t\t\t\tGameLog.Info( $\u0022[game] spike minion burst \u0027{b.PlayerName}\u0027 -\u003E \u0027{owner.PlayerName}\u0027 invincible {GameConfig.SpikeMinionRewardSeconds}s\u0022 );\n\t\t\t\t\t}\n\n\t\t\t\t\tNeonRenderer.Pop( minion.Pos, minion.ColorIndex, minion.Radius * 3f );\n\t\t\t\t\tif ( Networking.IsActive ) NetworkManager.PoppedEffect( minion.Pos, (byte)minion.ColorIndex, minion.Radius * 3f, minion.OwnerSteamId );\n\t\t\t\t\tconsumed = true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// \u6BD4\u523A\u5C0F\uFF1A\u88AB\u523A\u5403\uFF08\u523A\u4FDD\u7559\uFF0C\u4E0E\u91CE\u523A\u4E00\u81F4\uFF09\n\t\t\t\t\tSpawnPopFx( b );\n\t\t\t\t\tif ( !TryPromoteFromPieces( b ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tb.MarkDead();\n\t\t\t\t\t\tif ( !b.IsBot ) PopPiecesOf( b.OwnerSteamId );\n\t\t\t\t\t}\n\t\t\t\t\tGameLog.Info( $\u0022[game] eaten by spike minion: {b.PlayerName}\u0022 );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// \u654C\u65B9\u5206\u8EAB \u00D7 \u5C16\u523A\u5206\u8EAB\uFF08\u5927\u5206\u8EAB\u5403\u523A\u002B\u70B8\u88C2\uFF0C\u5C0F\u5206\u8EAB\u88AB\u5403\uFF09\n\t\t\tif ( !consumed )\n\t\t\t{\n\t\t\t\tfor ( int j = _cells.Count - 1; j \u003E= 0; j-- )\n\t\t\t\t{\n\t\t\t\t\tvar c = _cells[j];\n\t\t\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c == minion ) continue;\n\t\t\t\t\tif ( c.OwnerSteamId == minion.OwnerSteamId ) continue;   // \u4E3B\u4EBA\u514D\u75AB\n\t\t\t\t\tif ( MatchState.IsTeam\n\t\t\t\t\t\t\u0026\u0026 teamOf.TryGetValue( c.OwnerSteamId, out var pt )\n\t\t\t\t\t\t\u0026\u0026 ownerAlive \u0026\u0026 owner.TeamIndex \u003E= 0 \u0026\u0026 pt == owner.TeamIndex ) continue;\n\n\t\t\t\t\tif ( c.Pos.Distance( minion.Pos ) \u003E= c.Radius \u002B touch ) continue;\n\n\t\t\t\t\tif ( c.Radius \u003E GameConfig.SpikeRadius )\n\t\t\t\t\t{\n\t\t\t\t\t\tc.Mass = MathF.Min( GameConfig.MaxMass, c.Mass \u002B minion.Mass );\n\t\t\t\t\t\tBurstPiece( c );\n\t\t\t\t\t\tNeonRenderer.Pop( minion.Pos, minion.ColorIndex, minion.Radius * 3f );\n\t\t\t\t\t\tconsumed = true;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tNeonRenderer.Puff( c.Pos, c.ColorIndex );\n\t\t\t\t\t\t_cells.RemoveAt( j );\n\t\t\t\t\t\t_cellsDirty = true;\n\t\t\t\t\t\tif ( j \u003C i ) i--;   // \u5DF2\u79FB\u9664\u7684\u5206\u8EAB\u5728\u5F53\u524D\u904D\u5386\u4F4D\u4E4B\u524D\uFF0C\u8865\u7D22\u5F15\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( consumed )\n\t\t\t{\n\t\t\t\t_cells.RemoveAt( i );\n\t\t\t\t_cellsDirty = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u523A\u88AB\u5403\u6389\u540E\u5728\u522B\u5904\u91CD\u751F\uFF08\u907F\u5F00\u4E2D\u5FC3\u51FA\u751F\u533A\uFF09\uFF0C\u5168\u91CF\u5E7F\u64AD\uFF0812 \u4E2A\u4F4D\u7F6E\u5C0F\u5305\uFF0C\u5E42\u7B49\uFF09 \u003C/summary\u003E\n\tvoid RespawnSpike( int index )\n\t{\n\t\t_spikes[index] = RandomSpikePos();\n\t\tif ( Networking.IsActive ) NetworkManager.SpikesFull( _spikes );   // \u65E0\u4F1A\u8BDD\u4E0D\u5E7F\u64AD\n\t}\n\n\tVector2 RandomSpikePos()\n\t{\n\t\tvar half = GameConfig.ArenaHalfSize - GameConfig.FoodMargin - GameConfig.SpikeRadius;\n\t\tVector2 p;\n\t\tdo\n\t\t{\n\t\t\tp = new Vector2( Game.Random.Float( -half, half ), Game.Random.Float( -half, half ) );\n\t\t}\n\t\twhile ( p.Length \u003C 512f );   // \u4E2D\u5FC3\u533A\u7559\u7A7A\uFF08\u51FA\u751F/\u91CD\u751F\u4E0D\u81F3\u4E8E\u8138\u4E0A\u6709\u523A\uFF09\n\t\treturn p;\n\t}\n\n\t/// \u003Csummary\u003E host \u751F\u6210\u5C16\u523A\uFF08\u907F\u5F00\u4E2D\u5FC3\u51FA\u751F\u533A\uFF09\u5E76\u5E7F\u64AD\uFF1B\u65B0\u8FDE\u63A5\u8D70 OnActive \u5B9A\u5411\u8865\u53D1 \u003C/summary\u003E\n\tvoid GenerateSpikes()\n\t{\n\t\t_spikes = new Vector2[GameConfig.SpikeCount];\n\t\t_spikeFed = new float[GameConfig.SpikeCount];\n\t\t_spines.Clear();\n\t\tfor ( int i = 0; i \u003C _spikes.Length; i\u002B\u002B )\n\t\t{\n\t\t\t_spikes[i] = RandomSpikePos();\n\t\t}\n\t\tif ( Networking.IsActive ) NetworkManager.SpikesFull( _spikes );   // \u65E0\u4F1A\u8BDD\uFF08\u83DC\u5355\u6F14\u793A\u8D5B\uFF09\u4E0D\u5E7F\u64AD\n\t\tGameLog.Info( $\u0022[game] spikes generated: {_spikes.Length}\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u6267\u884C\u5410\u5B62\u5B50\uFF08host \u6743\u5A01\uFF0C**agar \u6807\u51C6\u591A\u8EAB\u4F53\u5582\u7403**\uFF09\uFF1A\u5BF9\u73A9\u5BB6\u7684\u6BCF\u4E00\u4E2A\u8EAB\u4F53\u2014\u2014\u4E3B\u7403\u548C\u6240\u6709\n\t/// \u591F\u8D28\u91CF\u7684\u5206\u8EAB\u2014\u2014\u5404\u5410\u4E00\u9897\u4E2D\u6027\u5B62\u5B50\uFF08v0.7.8.7 \u624B\u611F\uFF1AaimPoint \u4E3A\u5149\u6807\u4E16\u754C\u5750\u6807\uFF0C\n\t/// \u6BCF\u9897\u8EAB\u4F53**\u72EC\u7ACB\u671D\u5149\u6807**\u5F39\u51FA\uFF0C\u5B62\u5B50\u6247\u5F62\u6563\u5F00\uFF09\u3002\u6309\u4F4F\u8FDE\u5410\u3002\n\t/// \u003C/summary\u003E\n\tpublic void DoEject( Ball ball, Vector2 aimPoint )\n\t{\n\t\tif ( !GameConfig.EnableEject || !NetworkManager.IsAuthority ) return;\n\t\tif ( ball is null || !ball.IsValid() || !ball.Alive ) return;\n\t\tif ( _sinceEject.TryGetValue( ball.Id, out var t ) \u0026\u0026 t \u003C GameConfig.EjectCooldownSeconds ) return;\n\t\t_sinceEject[ball.Id] = 0;\n\n\t\tvar mainDir = FallbackDir( ball, DirTo( ball.WorldPosition, aimPoint ) );\n\n\t\tEjectFrom( ball, mainDir );\n\n\t\t// \u4E24\u9636\u6BB5\uFF08\u5148\u6536\u96C6\u540E\u751F\u6210\uFF09\uFF1ASpawnBlob \u8D85\u5B62\u5B50\u4E0A\u9650\u4F1A _cells.Remove \u6324\u6389\u6700\u8001\u4E00\u9897\uFF0C\n\t\t// \u8FB9\u904D\u5386 _cells \u8FB9\u751F\u6210 = \u0022Collection was modified\u0022\uFF08\u5B9E\u6D4B\u70B8 bot \u5582\u5927\u54E5\u8DEF\u5F84\uFF0Cv0.7.8.8 \u4FEE\uFF09\n\t\tList\u003CCellPiece\u003E ejectors = null;\n\n\t\tforeach ( var c in _cells )\n\t\t{\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c.OwnerSteamId != ball.OwnerSteamId ) continue;\n\t\t\tif ( EjectCost( c.Mass ) \u003C= 0f ) continue;\n\n\t\t\t( ejectors ??= new List\u003CCellPiece\u003E() ).Add( c );\n\t\t}\n\n\t\tif ( ejectors is null ) return;\n\n\t\tforeach ( var c in ejectors )\n\t\t{\n\t\t\tvar cost = EjectCost( c.Mass );   // \u4E24\u9636\u6BB5\u95F4\u8D28\u91CF\u4E0D\u4F1A\u88AB\u6539\uFF0C\u91CD\u7B97\u5373\u5F97\n\t\t\tif ( cost \u003C= 0f ) continue;\n\n\t\t\tc.Mass -= cost;\n\t\t\tSpawnBlob( c.Pos, DirOr( c.Pos, aimPoint, mainDir ), c.Radius, c.OwnerSteamId, c.ColorIndex, cost * GameConfig.EjectBlobEfficiency );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u8EAB\u4F53 \u2192 \u5149\u6807\u7684\u5355\u4F4D\u6307\u5411\uFF1B\u5149\u6807\u6B63\u8D34\u8BE5\u8EAB\u4F53\u65F6\u8FD4\u56DE fallback\uFF08\u4E3B\u7403\u65B9\u5411\uFF09 \u003C/summary\u003E\n\tstatic Vector2 DirOr( Vector3 from, Vector2 aimPoint, Vector2 fallback )\n\t{\n\t\tvar d = DirTo( from, aimPoint );\n\t\treturn d.Length \u003E 0.001f ? d : fallback;\n\t}\n\n\t/// \u003Csummary\u003E \u8EAB\u4F53 \u2192 \u7784\u51C6\u70B9\u7684\u5355\u4F4D\u6307\u5411\uFF1B\u6B63\u8D34\u65F6\u8FD4\u56DE Zero\uFF08\u8C03\u7528\u65B9\u8D70 FallbackDir \u515C\u5E95\uFF09 \u003C/summary\u003E\n\tstatic Vector2 DirTo( Vector3 from, Vector2 aimPoint )\n\t{\n\t\tvar dx = aimPoint.x - from.x;\n\t\tvar dy = aimPoint.y - from.y;\n\t\tvar len = MathF.Sqrt( dx * dx \u002B dy * dy );\n\t\treturn len \u003E 0.001f ? new Vector2( dx / len, dy / len ) : Vector2.Zero;\n\t}\n\n\t/// \u003Csummary\u003E \u8BE5\u8D28\u91CF\u5410\u4E00\u9897\u7684\u6210\u672C\uFF08\u7EA6 1% \u968F\u4F53\u91CF\u7F29\u653E\uFF0C\u4E0B\u9650 1\uFF09\uFF1B\u8FD4\u56DE 0 = \u4F1A\u51FB\u7A7F\u8D28\u91CF\u4E0B\u9650 1\uFF0C\u4E0D\u5141\u8BB8 \u003C/summary\u003E\n\tstatic float EjectCost( float mass )\n\t{\n\t\tvar cost = MathF.Max( GameConfig.EjectMinCost, mass * GameConfig.EjectMassPercent );\n\t\treturn mass - cost \u003E= GameConfig.EjectMinMass ? cost : 0f;\n\t}\n\n\t/// \u003Csummary\u003E \u4E3B\u7403\u5410\u51FA\u4E00\u9897\u5B62\u5B50 \u003C/summary\u003E\n\tvoid EjectFrom( Ball ball, Vector2 dir )\n\t{\n\t\tvar cost = EjectCost( ball.Mass );\n\t\tif ( cost \u003C= 0f ) return;\n\n\t\tball.Mass -= cost;\n\t\tSpawnBlob( ball.WorldPosition, dir, ball.Radius, ball.OwnerSteamId, ball.ColorIndex, cost * GameConfig.EjectBlobEfficiency );\n\t}\n\n\t/// \u003Csummary\u003E \u751F\u6210\u4E00\u9897\u4E2D\u6027\u5B62\u5B50\uFF08\u8D28\u91CF=\u6D88\u8017\u00D7\u8FD4\u8FD8\u7387\uFF09\u5E76\u505A\u603B\u91CF\u4E0A\u9650\u6536\u655B\uFF08\u6324\u6389\u6700\u8001\uFF09 \u003C/summary\u003E\n\tvoid SpawnBlob( Vector3 pos, Vector2 dir, float radius, long owner, int colorIndex, float mass )\n\t{\n\t\tvar c = new CellPiece\n\t\t{\n\t\t\tId = _nextCellId\u002B\u002B,\n\t\t\tPieceKind = CellPiece.Kind.EjectedMass,\n\t\t\tOwnerSteamId = owner,\n\t\t\tColorIndex = colorIndex,\n\t\t\tMass = mass,\n\t\t\tPos = pos \u002B new Vector3( dir.x, dir.y, 0f ) * ( radius \u002B 16f ),\n\t\t\tDrawPos = pos,\n\t\t\tImpulse = new Vector3( dir.x, dir.y, 0f ) * GameConfig.EjectImpulseSpeed,\n\t\t\tSinceSpawn = 0,\n\t\t};\n\t\tClampToArena( c );\n\t\t_cells.Add( c );\n\t\t_cellsDirty = true;\n\t\tNeonRenderer.Puff( c.Pos, colorIndex );\n\n\t\t// \u5B62\u5B50\u603B\u91CF\u4E0A\u9650\uFF1A\u6324\u6389\u6700\u8001\u7684\u4E00\u9897\uFF08TimeSince \u8D8A\u5927\u8D8A\u8001\uFF09\n\t\tint blobs = 0;\n\t\tCellPiece oldest = null;\n\t\tforeach ( var x in _cells )\n\t\t{\n\t\t\tif ( x.PieceKind != CellPiece.Kind.EjectedMass ) continue;\n\t\t\tblobs\u002B\u002B;\n\t\t\tif ( oldest is null || x.SinceSpawn \u003E oldest.SinceSpawn ) oldest = x;\n\t\t}\n\t\tif ( blobs \u003E GameConfig.MaxEjectedBlobs \u0026\u0026 oldest is not null )\n\t\t{\n\t\t\t_cells.Remove( oldest );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E host \u2192 \u5168\u4F53\uFF1A\u5206\u8EAB/\u5B62\u5B50\u72B6\u6001\u5FEB\u7167\uFF08\u5168\u91CF\u66FF\u6362\u5F0F\uFF0C\u91CF\u5C0F\u9891\u7387\u4F4E\uFF0C\u4E0D\u505A\u589E\u91CF\uFF09 \u003C/summary\u003E\n\tvoid BroadcastCells()\n\t{\n\t\t_cellsDirty = false;\n\n\t\tvar wires = new CellWire[_cells.Count];\n\t\tfor ( int i = 0; i \u003C _cells.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar c = _cells[i];\n\t\t\twires[i] = new CellWire\n\t\t\t{\n\t\t\t\tId = c.Id,\n\t\t\t\tKind = (byte)c.PieceKind,\n\t\t\t\tColorIndex = (byte)c.ColorIndex,\n\t\t\t\tOwner = c.OwnerSteamId,\n\t\t\t\tX = c.Pos.x,\n\t\t\t\tY = c.Pos.y,\n\t\t\t\tMass = c.Mass,\n\t\t\t};\n\t\t}\n\t\tNetworkManager.CellsState( wires );\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u5E94\u7528\u5FEB\u7167\uFF1A\u6309 Id \u5C31\u5730\u66F4\u65B0\uFF08\u4FDD\u7559 DrawPos \u5E73\u6ED1\uFF09\uFF0C\u65B0\u589E\u8865\u5165\uFF0C\u6D88\u5931\u79FB\u9664 \u003C/summary\u003E\n\tpublic void OnCellsStateRemote( CellWire[] wires )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;   // host \u56DE\u58F0\u6321\u6389\n\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = _cells[i];\n\t\t\tbool found = false;\n\t\t\tfor ( int w = 0; w \u003C wires.Length; w\u002B\u002B )\n\t\t\t{\n\t\t\t\tif ( wires[w].Id != c.Id ) continue;\n\t\t\t\tc.Pos = new Vector3( wires[w].X, wires[w].Y, 0f );\n\t\t\t\tc.Mass = wires[w].Mass;\n\t\t\t\tc.ColorIndex = wires[w].ColorIndex;\n\t\t\t\tc.OwnerSteamId = wires[w].Owner;\n\t\t\t\tc.PieceKind = (CellPiece.Kind)wires[w].Kind;\n\t\t\t\tfound = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !found ) _cells.RemoveAt( i );\n\t\t}\n\n\t\tforeach ( var w in wires )\n\t\t{\n\t\t\tbool exists = false;\n\t\t\tforeach ( var c in _cells )\n\t\t\t{\n\t\t\t\tif ( c.Id == w.Id ) { exists = true; break; }\n\t\t\t}\n\t\t\tif ( exists ) continue;\n\n\t\t\tvar pos = new Vector3( w.X, w.Y, 0f );\n\t\t\t_cells.Add( new CellPiece\n\t\t\t{\n\t\t\t\tId = w.Id,\n\t\t\t\tPieceKind = (CellPiece.Kind)w.Kind,\n\t\t\t\tColorIndex = w.ColorIndex,\n\t\t\t\tOwnerSteamId = w.Owner,\n\t\t\t\tMass = w.Mass,\n\t\t\t\tPos = pos,\n\t\t\t\tDrawPos = pos,\n\t\t\t} );\n\t\t\tNeonRenderer.Puff( pos, w.ColorIndex );   // \u5BA2\u6237\u7AEF\u8865\u4E0A\u751F\u6210\u55B7\u53D1\uFF08host \u5DF2\u5728\u672C\u5730\u653E\u8FC7\uFF09\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u6B7B\u4EA1\u7206\u88C2\u7279\u6548\uFF1Ahost \u672C\u5730\u751F\u6210 \u002B \u5E7F\u64AD\uFF08\u5BA2\u6237\u7AEF\u7ECF PoppedEffect RPC \u751F\u6210\u540C\u6B3E\uFF09\u3002\n\t/// \u7206\u88C2\u97F3\u53EA\u64AD\u0022\u81EA\u5DF1\u6B7B\u4EA1/\u7206\u88C2\u0022\u7684\uFF08\u89C6\u89C9\u7279\u6548\u4ECD\u5168\u573A\u64AD\uFF0C\u58F0\u97F3\u6536\u7A84\u5230\u672C\u4EBA\uFF09 \u003C/summary\u003E\n\tvoid SpawnPopFx( Ball ball )\n\t{\n\t\tvar pos = ball.WorldPosition;\n\t\tNeonRenderer.Pop( pos, ball.ColorIndex, ball.Radius );\n\t\tif ( GameSfx.IsMine( ball.OwnerSteamId ) )\n\t\t\tGameSfx.Pop( pos );\n\t\tif ( Networking.IsActive ) NetworkManager.PoppedEffect( pos, (byte)ball.ColorIndex, ball.Radius, ball.OwnerSteamId );   // \u65E0\u4F1A\u8BDD\u4E0D\u5E7F\u64AD\n\t}\n\n\t/// \u003Csummary\u003E \u6E05\u6389\u67D0\u4E3B\u4EBA\u7684\u5168\u90E8\u5206\u8EAB\uFF08\u4E3B\u7403\u6B7B\u4EA1/\u65AD\u7EBF\u65F6\u8C03\u7528\uFF1B\u5B62\u5B50\u4FDD\u7559\uFF09 \u003C/summary\u003E\n\tpublic void PopPiecesOf( long ownerSteamId )\n\t{\n\t\tfor ( int i = _cells.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = _cells[i];\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c.OwnerSteamId != ownerSteamId ) continue;\n\t\t\t_cells.RemoveAt( i );\n\t\t\t_cellsDirty = true;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u5C16\u523A\u5206\u8EAB\uFF08v0.7.4.0 \u9053\u5177\u6548\u679C\uFF09\uFF1A\u4ECE\u4E3B\u4EBA\u73B0\u6263 SpikeMinionMassPercent \u7684\u8D28\u91CF\u751F\u6210\u4E00\u9897\u8DDF\u968F\u5206\u8EAB\uFF0C\n\t/// 20 \u79D2\u5185\u662F\u0022\u523A\u0022\u2014\u2014\u654C\u65B9\u73A9\u5BB6\u78B0\u5230\u6309\u5C16\u523A\u89C4\u5219\u5904\u7406\uFF08\u5927\u7403\u88AB\u70B8/\u5C0F\u7403\u6B7B\uFF09\uFF0C\u5230\u671F\u53D8\u56DE\u666E\u901A\u5206\u8EAB\u5408\u4F53\u56DE\u6536\u8D28\u91CF\u3002\n\t/// \u751F\u6210\u5373\u5E26\u4E3B\u4EBA\u8F6C\u5411\u8DDF\u968F\uFF08TickCells \u7684 SpikeMinion \u5206\u652F\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tpublic void SpawnSpikeMinion( Ball ball )\n\t{\n\t\tif ( ball is null || !ball.IsValid() || !ball.Alive ) return;\n\n\t\tvar mass = MathF.Max( 10f, ball.Mass * GameConfig.SpikeMinionMassPercent );\n\t\tmass = MathF.Min( mass, ball.Mass - 1f );\n\t\tif ( mass \u003C 10f ) return;   // \u8D28\u91CF\u592A\u4F4E\uFF08\u6781\u5C0F\u7403\uFF09\u6263\u4E0D\u51FA 10\uFF0C\u653E\u5F03\uFF08\u9053\u5177\u767D\u62FF\u2014\u2014\u53EF\u63A5\u53D7\u7684\u6781\u7AEF\u8FB9\u754C\uFF09\n\n\t\tball.Mass -= mass;\n\n\t\tvar dir = RandomDir();\n\t\tvar c = new CellPiece\n\t\t{\n\t\t\tId = _nextCellId\u002B\u002B,\n\t\t\tPieceKind = CellPiece.Kind.SpikeMinion,\n\t\t\tOwnerSteamId = ball.OwnerSteamId,\n\t\t\tOwnerBall = ball,\n\t\t\tColorIndex = ball.ColorIndex,\n\t\t\tMass = mass,\n\t\t\tPos = ball.WorldPosition \u002B new Vector3( dir.x, dir.y, 0f ) * ( ball.Radius \u002B 40f ),\n\t\t\tDrawPos = ball.WorldPosition,\n\t\t\tSinceSpawn = 0,\n\t\t};\n\t\tClampToArena( c );\n\t\t_cells.Add( c );\n\t\t_cellsDirty = true;\n\n\t\tNeonRenderer.Puff( c.Pos, ball.ColorIndex );\n\t\tGameLog.Info( $\u0022[game] spike minion spawned for \u0027{ball.PlayerName}\u0027 (mass {mass:0})\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// agar \u591A\u8EAB\u4F53\u89C4\u5219\uFF08v0.6.2.0 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u4E3B\u7403\u88AB\u5403/\u649E\u523A\u65F6\uFF0C\u628A\u6700\u5927\u7684\u5206\u8EAB\u0022\u5347\u7EA7\u0022\u6210\u65B0\u4E3B\u7403\n\t/// \u2014\u2014\u7403\u5BF9\u8C61\u539F\u5730\u7EED\u547D\uFF08\u8D28\u91CF/\u4F4D\u7F6E\u6362\u6210\u8BE5\u5206\u8EAB\u7684\uFF09\uFF0C\u73A9\u5BB6\u7EE7\u7EED\u73A9\uFF1B\u5168\u90E8\u8EAB\u4F53\u6B7B\u5149\u624D\u7B97\u771F\u6B7B\u3002\n\t/// \u4E3B\u7403\u4E0E\u5206\u8EAB\u5728\u540C\u4E00 tick \u5185\u5B8C\u6210\u4EA4\u63A5\uFF0CAlive \u4ECE\u4E0D\u7FFB\u8F6C\uFF0C\u5BA2\u6237\u7AEF\u4E0D\u4F1A\u770B\u5230\u6B7B\u4EA1/\u91CD\u7F6E\u3002\n\t/// \u8FD4\u56DE\u662F\u5426\u6210\u529F\uFF08\u6CA1\u6709\u5206\u8EAB = \u771F\u6B7B\u4EA1\uFF0C\u8D70\u539F MarkDead \u6D41\u7A0B\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tbool TryPromoteFromPieces( Ball ball )\n\t{\n\t\tif ( ball.IsBot ) return false;   // bot \u4E0D\u5206\u88C2\uFF0C\u65E0\u5206\u8EAB\u53EF\u664B\u5347\n\n\t\tCellPiece best = null;\n\t\tforeach ( var c in _cells )\n\t\t{\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c.OwnerSteamId != ball.OwnerSteamId ) continue;\n\t\t\tif ( best is null || c.Mass \u003E best.Mass ) best = c;\n\t\t}\n\t\tif ( best is null ) return false;\n\n\t\tfloat mass = best.Mass;\n\t\tVector3 pos = best.Pos;\n\t\t_cells.Remove( best );\n\t\t_cellsDirty = true;\n\n\t\tball.Mass = mass;\n\t\tball.Alive = true;\n\t\tball.WorldPosition = pos;   // host \u81EA\u5DF1\u7684\u7403\uFF1Aowner \u6A21\u62DF\u4ECE\u8FD9\u91CC\u7EE7\u7EED\n\n\t\t// \u8FDC\u7A0B\u73A9\u5BB6\u7684\u7403\u5F52\u5176\u4E3B\u4EBA\u6A21\u62DF\uFF0C\u4F4D\u7F6E\u8981\u901A\u77E5\u4E3B\u4EBA\u77AC\u79FB\u8FC7\u53BB\uFF08\u5426\u5219\u4E0B\u4E00\u5E27\u88AB\u4E3B\u4EBA\u7684\u6A21\u62DF\u5199\u56DE\u65E7\u4F4D\u7F6E\uFF09\n\t\tif ( ball.IsRemoteOwned || ball.OwnerSteamId != Game.SteamId.Value )\n\t\t{\n\t\t\tforeach ( var conn in Connection.All )\n\t\t\t{\n\t\t\t\tif ( conn is null || !conn.IsActive ) continue;\n\t\t\t\tif ( conn.SteamId.Value != ball.OwnerSteamId ) continue;\n\n\t\t\t\tusing ( Rpc.FilterInclude( conn ) )\n\t\t\t\t{\n\t\t\t\t\tNetworkManager.MainPromoted( pos.x, pos.y );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tGameLog.Info( $\u0022[game] promoted piece -\u003E main: {ball.PlayerName} mass={mass:0}\u0022 );\n\t\treturn true;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u89C6\u89D2\u7126\u70B9\uFF08v0.6.2.0 \u7528\u6237\u5B9A\u7A3F\u0022\u89C6\u89D2\u8DDF\u968F\u6700\u5927\u7684\u5206\u8EAB\u0022\uFF09\uFF1A\u4E3B\u7403\u4E0E\u81EA\u5DF1\u6240\u6709\u5206\u8EAB\u4E2D\n\t/// \u8D28\u91CF\u6700\u5927\u7684\u90A3\u4E2A\u8EAB\u4F53\u7684\u4F4D\u7F6E/\u8D28\u91CF\u3002\n\t/// \u003C/summary\u003E\n\tpublic Vector3 FocusPositionOf( Ball ball, out float biggestMass )\n\t{\n\t\tbiggestMass = ball.IsValid() ? ball.Mass : 0f;\n\t\tvar pos = ball.IsValid() ? ball.WorldPosition : Vector3.Zero;\n\t\tif ( !ball.IsValid() ) return pos;\n\n\t\tforeach ( var c in _cells )\n\t\t{\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c.OwnerSteamId != ball.OwnerSteamId ) continue;\n\t\t\tif ( c.Mass \u003E biggestMass )\n\t\t\t{\n\t\t\t\tbiggestMass = c.Mass;\n\t\t\t\tpos = c.Pos;\n\t\t\t}\n\t\t}\n\t\treturn pos;\n\t}\n\n\tint CountPiecesOf( long ownerSteamId )\n\t{\n\t\tint n = 0;\n\t\tforeach ( var c in _cells )\n\t\t{\n\t\t\tif ( c.PieceKind == CellPiece.Kind.SplitPiece \u0026\u0026 c.OwnerSteamId == ownerSteamId ) n\u002B\u002B;\n\t\t}\n\t\treturn n;\n\t}\n\n\t/// \u003Csummary\u003E \u8BE5\u73A9\u5BB6\u662F\u5426\u8FD8\u6709\u5206\u8EAB\u62A4\u4F53\uFF08\u4E3B\u7403\u88AB\u5403\u4FDD\u62A4 \u002B \u62A4\u76FE\u865A\u73AF\u6E32\u67D3\u7528\uFF09 \u003C/summary\u003E\n\tpublic bool HasPieces( long ownerSteamId ) =\u003E CountPiecesOf( ownerSteamId ) \u003E 0;\n\n\t/// \u003Csummary\u003E \u73A9\u5BB6\u603B\u8D28\u91CF = \u4E3B\u7403 \u002B \u5168\u90E8\u5206\u8EAB\uFF08\u6392\u884C\u699C/HUD \u7528\uFF1B\u5206\u8EAB\u7684\u8D28\u91CF\u4E5F\u662F\u771F\u5B9E\u6218\u529B\uFF09 \u003C/summary\u003E\n\tpublic float TotalMassFor( Ball ball )\n\t{\n\t\tif ( ball is null || !ball.IsValid() ) return 0f;\n\n\t\tfloat m = ball.Mass;\n\t\tvar sid = ball.OwnerSteamId;\n\t\tforeach ( var c in _cells )\n\t\t{\n\t\t\tif ( c.PieceKind == CellPiece.Kind.SplitPiece \u0026\u0026 c.OwnerSteamId == sid ) m \u002B= c.Mass;\n\t\t}\n\t\treturn m;\n\t}\n\n\tVector2 SteerDirOf( Ball ball )\n\t{\n\t\tif ( !ball.IsValid() ) return Vector2.Zero;\n\t\treturn _steerDir.TryGetValue( ball.Id, out var d ) ? d : Vector2.Zero;\n\t}\n\n\t/// \u003Csummary\u003E \u5F39\u5C04\u65B9\u5411\uFF1A\u4F18\u5148\u7528\u8F93\u5165\u65B9\u5411\uFF0C\u6CA1\u7ED9\uFF08\u9759\u6B62\u5F00\u706B\uFF09\u5C31\u6CBF\u7528\u7403\u7684\u79FB\u52A8\u65B9\u5411\uFF0C\u518D\u9000\u968F\u673A \u003C/summary\u003E\n\tVector2 FallbackDir( Ball ball, Vector2 dir )\n\t{\n\t\tif ( dir.Length \u003E 0.001f ) return dir.Normal;\n\t\tvar d = SteerDirOf( ball );\n\t\tif ( d.Length \u003E 0.001f ) return d.Normal;\n\n\t\treturn RandomDir();\n\t}\n\n\t/// \u003Csummary\u003E \u5747\u5300\u968F\u673A\u5355\u4F4D\u65B9\u5411 \u003C/summary\u003E\n\tVector2 RandomDir()\n\t{\n\t\tvar a = Game.Random.Float( 0f, MathF.PI * 2f );\n\t\treturn new Vector2( MathF.Cos( a ), MathF.Sin( a ) );\n\t}\n\n\tvoid ClampToArena( CellPiece c )\n\t{\n\t\tvar half = MathF.Max( 0f, GameConfig.ArenaHalfSize - c.Radius );\n\t\tc.Pos.x = Math.Clamp( c.Pos.x, -half, half );\n\t\tc.Pos.y = Math.Clamp( c.Pos.y, -half, half );\n\t\tc.Pos.z = 0f;\n\t}\n\n\t// ---- \u4E16\u754C\u642D\u5EFA ----\n\n\t/// \u003Csummary\u003E\n\t/// UI \u5BF9\u8D26\uFF08v0.6.4.5\uFF09\uFF1A\u7531\u72B6\u6001\u6807\u5FD7**\u63A8\u5BFC**\u5404\u9762\u677F\u5E94\u6709\u7684\u53EF\u89C1\u6027\uFF0C\u6BCF\u5E27\u5F3A\u5236\u6267\u884C\u3002\n\t/// \u4FEE\u0022\u5BA2\u6237\u7AEF\u52A0\u5165\u540E UI \u5168\u6D88\u5931 / \u623F\u95F4\u548C\u4E3B\u83DC\u5355\u53E0\u5728\u4E00\u8D77\u0022\u2014\u2014\u6B64\u524D Show/Hide \u5168\u9760\u5404\u8DEF\u5F84\u81EA\u89C9\u8C03\u7528\uFF0C\n\t/// \u4EFB\u4F55\u4E00\u6761\u8DEF\u5F84\u6F0F\u8C03\uFF08\u4E2D\u9014\u52A0\u5165\u3001\u70ED\u91CD\u8F7D\u91CD\u5EFA\u9762\u677F\u3001\u6B8B\u7559\u5012\u8BA1\u65F6\u5361\u4F4F\u6062\u590D\u5206\u652F\uFF09\u5C31\u6C38\u8FDC\u505C\u5728\u574F\u754C\u9762\u3002\n\t/// \u003C/summary\u003E\n\tvoid ReconcileUi()\n\t{\n\t\tbool inSession = Networking.IsActive;\n\n\t\t// \u6B8B\u7559\u52A0\u5165\u5012\u8BA1\u65F6\u4F1A\u8BA9\u5BA2\u6237\u7AEF\u6062\u590D\u5206\u652F\uFF08_joinCountdown\u003C0 \u624D\u8D70\uFF09\u6C38\u4E45\u5931\u6548\u2014\u2014\u5BF9\u5C40\u5DF2\u4E0D\u5728\u5C31\u6E05\u6389\n\t\tif ( _joinCountdown \u003E= 0f \u0026\u0026 ( !_matchRunning || _gameStarted || !inSession ) )\n\t\t\t_joinCountdown = -1f;\n\n\t\tif ( _gameStarted )\n\t\t{\n\t\t\t_menu?.Hide();\n\t\t\t_lobby?.Hide();\n\t\t\t_browser?.Hide();\n\t\t\tif ( !MatchState.MatchOver )\n\t\t\t{\n\t\t\t\t// \u5BF9\u5C40\u4E2D HUD \u5FC5\u987B\u5728\uFF1A\u6CA1\u5EFA\u8FC7\u5C31\u5EFA\uFF08\u70ED\u91CD\u8F7D\u91CD\u5EFA\u4E16\u754C\u540E\u5F15\u7528\u5931\u6548 \u2192 \u91CD\u5EFA\u81EA\u6108\uFF09\n\t\t\t\tif ( _hud.IsValid() )\n\t\t\t\t{\n\t\t\t\t\tif ( !_hud.GameObject.Enabled ) _hud.GameObject.Enabled = true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCreateHud();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif ( _lobbyOpen )\n\t\t{\n\t\t\t_menu?.Hide();\n\t\t\t_lobby?.ShowHost();   // \u5E42\u7B49\uFF1A\u5DF2\u663E\u793A\u4E0D\u91CD\u7F6E\u72B6\u6001\n\t\t\treturn;\n\t\t}\n\n\t\tif ( inSession \u0026\u0026 !NetworkManager.IsAuthority )\n\t\t{\n\t\t\t// \u5BA2\u6237\u7AEF\u5728\u4F1A\u8BDD\u91CC\u4F46\u6CA1\u8FDB\u5BF9\u5C40\uFF1A\u5FC5\u987B\u505C\u5728\u623F\u95F4\u9875\uFF08\u7B49\u5F85 / JOIN GAME \u6309\u94AE / 3-2-1\uFF09\n\t\t\t_menu?.Hide();\n\t\t\t_browser?.Hide();\n\t\t\tif ( !_lobbyWaiting )\n\t\t\t{\n\t\t\t\tEnterClientWaiting();\n\t\t\t\tNetworkManager.RequestMatchState();\n\t\t\t}\n\t\t\t_lobby?.ShowClient();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !inSession \u0026\u0026 NetworkManager.IsReconnecting )\n\t\t{\n\t\t\t// \u6389\u7EBF\u81EA\u52A8\u91CD\u8FDE\u4E2D\uFF1A\u623F\u95F4\u9875\u7559\u5728\u539F\u5730\u663E\u793A \u0022CONNECTION LOST \u2014 RECONNECTING\u0022\uFF0C\n\t\t\t// \u91CD\u8BD5\u8017\u5C3D OnReconnectFailed \u624D\u56DE\u4E3B\u83DC\u5355\uFF08v0.6.4.6\uFF09\n\t\t\t_lobby?.ShowClient();\n\t\t\treturn;\n\t\t}\n\n\t\t_lobby?.Hide();\n\n\t\t// \u7EC8\u6001\u4FDD\u8BC1\uFF08v0.6.8.2\uFF09\uFF1A\u65E0\u4F1A\u8BDD\u4E14\u4E0D\u5728\u4EFB\u4F55\u6D41\u7A0B \u2192 **\u4E3B\u83DC\u5355\u5FC5\u987B\u53EF\u89C1**\u3002\n\t\t// \u4E4B\u524D\u53EA Hide \u623F\u95F4\u4E0D\u4EAE\u83DC\u5355\u2014\u2014\u4EFB\u4F55\u8DEF\u5F84\u6F0F\u6389 Show\uFF08\u70ED\u91CD\u8F7D\u91CD\u5EFA\u3001\u5F02\u5E38\u4E2D\u65AD\uFF09\u5C31\u662F\u5168\u7A7A\u5C4F\n\t\tif ( _browser.IsValid() \u0026\u0026 _browser.IsOpen ) return;   // \u623F\u95F4\u5217\u8868\u9875\u5F00\u7740\uFF1A\u4E3B\u83DC\u5355\u8BA9\u4F4D\n\t\tif ( _settings.IsValid() \u0026\u0026 _settings.IsOpen ) return;   // \u8BBE\u7F6E\u9875\u5F00\u7740\uFF1A\u4E3B\u83DC\u5355\u8BA9\u4F4D\uFF08v0.7.8.16\uFF09\n\t\tif ( _rankings.IsValid() \u0026\u0026 _rankings.IsOpen ) return;   // \u6392\u884C\u9875\u5F00\u7740\uFF1A\u4E3B\u83DC\u5355\u8BA9\u4F4D\uFF08v0.7.8.19\uFF09\n\t\tif ( _menu.IsValid() \u0026\u0026 !_menu.GameObject.Enabled ) _menu.Show();\n\t}\n\n\tvoid EnsureWorld()\n\t{\n\t\tif ( _worldReady ) return;\n\t\t_worldReady = true;\n\n\t\t// \u590D\u7528\u65E7\u7684\u4E16\u754C\u6839\uFF08\u70ED\u91CD\u8F7D\u65F6\u907F\u514D\u5806\u53E0\u591A\u5957\u4E16\u754C\uFF09\uFF0C\u5E76\u6E05\u6389\u5176\u52A8\u6001\u5B50\u7269\u4F53\u3002\n\t\t// \u6CE8\u610F\uFF1APlay \u505C\u6B62\u540E static \u5F15\u7528\u53EF\u80FD\u6307\u5411\u5DF2\u5378\u8F7D\u7684\u65E7\u573A\u666F\u5BF9\u8C61\uFF08IsValid \u4ECD\u4E3A\u771F\uFF09\uFF0C\n\t\t// \u5FC5\u987B\u6821\u9A8C\u573A\u666F\u5F52\u5C5E\uFF0C\u5426\u5219\u5411\u65E7\u573A\u666F\u6839\u6302\u5B50\u7269\u4F53\u76F4\u63A5\u65AD\u8A00\u5931\u8D25\n\t\tif ( _sharedRoot.IsValid() \u0026\u0026 _sharedRoot.Scene == Scene )\n\t\t{\n\t\t\t_root = _sharedRoot;\n\t\t\tforeach ( var child in _root.Children.ToArray() )\n\t\t\t{\n\t\t\t\tif ( child.IsValid() ) child.Destroy();\n\t\t\t}\n\t\t\t_balls.Clear();\n\t\t\t_lastPos.Clear();\n\t\t\t_cells.Clear();\n\t\t\t_steerDir.Clear();\n\t\t\t_sinceEject.Clear();\n\t\t\t_nextCellId = 1;\n\t\t\tLocalBall = null;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t_sharedRoot = null;\n\t\t\t// \u547D\u540D\u907F\u5F00 \u0022CircleroyaleGame\u0022\uFF1A\u5BA2\u6237\u7AEF\u4E0A\u5FEB\u7167\u4E16\u754C\u91CC host \u7684\u4E16\u754C\u6839\u4E5F\u53EB\u8FD9\u4E2A\u540D\n\t\t\t_root = new GameObject( true, \u0022CircleroyaleGameLocal\u0022 );\n\t\t\t_sharedRoot = _root;\n\t\t}\n\n\t\tif ( NetworkManager.IsAuthority )\n\t\t{\n\t\t\t// \u5C4F\u853D\u573A\u666F\u91CC\u6A21\u677F\u7559\u4E0B\u7684\u6240\u6709\u5BF9\u8C61\uFF08\u793A\u4F8B\u76F8\u673A\u3001\u65B9\u5757\u7B49\uFF09\uFF0C\u53EA\u8DD1\u6211\u4EEC\u751F\u6210\u7684\u4E16\u754C\u3002\n\t\t\t// Scene \u672C\u8EAB\u5C31\u662F\u6839 GameObject\uFF0C\u9876\u5C42\u5BF9\u8C61\u90FD\u5728\u5B83\u7684 Children \u91CC\n\t\t\tforeach ( var go in Scene.Children )\n\t\t\t{\n\t\t\t\tif ( go.IsValid() \u0026\u0026 go != _root ) go.Enabled = false;\n\t\t\t}\n\n\t\t\tCreateBackdrop();\n\t\t\tCreateNeon();\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// \u5BA2\u6237\u7AEF\uFF1A**\u76F4\u63A5\u542F\u7528\u5FEB\u7167\u4E16\u754C\u6839**\uFF08host \u7684\u4E16\u754C\uFF0C\u7403\u90FD\u5728\u91CC\u9762\uFF09\uFF0C\u53EA\u5265\u6389\u5B83\u81EA\u5E26\u7684\n\t\t\t// \u76F8\u673A/HUD\uFF08\u7528\u672C\u673A\u7684\uFF0C\u907F\u514D\u53CC\u76F8\u673A\u53CC HUD\uFF09\u3002\u26A0\uFE0F \u4E0D\u80FD\u8D70\u0022\u628A\u7403 reparent \u5230\u672C\u673A\u6839\u0022\n\t\t\t// \u7684\u8DEF\u7EBF\u2014\u2014\u5F15\u64CE\u4E0D\u5141\u8BB8\u5BA2\u6237\u7AEF\u6539 host \u62E5\u6709\u5BF9\u8C61\u7684\u5C42\u7EA7\uFF0CParent= \u9759\u9ED8\u5931\u6548\uFF0C\u7403\u6C38\u8FDC\n\t\t\t// Active=false\uFF1A\u5934\u50CF\u4E0D\u6E32\u67D3\u3001OnUpdate \u4E0D\u8DD1\uFF08\u6B7B\u4EA1\u5934\u50CF\u9690\u85CF\u5931\u6548\u2192\u0022\u5C38\u4F53\u5708\u0022\uFF09\n\t\t\t// \u2014\u20142026-09-05 \u63A2\u9488\u5B9E\u6D4B\uFF08actFalse=32\uFF0C\u81EA\u6108\u626B\u63CF\u6BCF\u79D2\u8DD1\u4E5F\u65E0\u6D4E\u4E8E\u4E8B\uFF09\u3002\n\t\t\tAdoptSnapshotWorld();\n\t\t}\n\n\t\tCreateCamera();\n\t\tCreateMenu();\n\t\tCreateLobbyPanel();\n\t\tCreateRoomBrowser();\n\t\tCreateSettingsPanel();\n\t\tCreateRankingsPanel();\n\n\t\t// \u98DF\u7269\u7BA1\u7406\u4E0E\u8054\u673A\u7BA1\u7406\uFF1A**\u6BCF\u53F0\u673A\u5668\u4E00\u4EFD\u672C\u5730\u5B9E\u4F8B\uFF0C\u81EA\u8EAB\u4E0D\u8054\u7F51**\u3002\n\t\t// \u98DF\u7269\u72B6\u6001\u7ECF\u9759\u6001 RPC \u540C\u6B65\uFF08FoodFull \u5B9A\u5411\u5168\u91CF / FoodEaten / FoodRespawned \u5E7F\u64AD\u589E\u91CF\uFF09\uFF1B\n\t\t// \u6B64\u524D\u6302\u5728\u7F51\u7EDC\u5BF9\u8C61\uFF08GameNet\uFF09\u4E0A\u7684 NetList \u65E0\u6CD5\u590D\u5236\u5230\u5BA2\u6237\u7AEF\uFF0C\u98DF\u7269/\u91CD\u751F\u5168\u5E9F\u2014\u2014\u5B9E\u6D4B\u3002\n\t\t// M4 \u4E3B\u83DC\u5355\uFF1A\u58F3\u5230\u6B64\u4E3A\u6B62\u2014\u2014\u5EFA\u5927\u5385\u8FD8\u662F\u8FDE\u4EBA\u7531\u83DC\u5355\u70B9\u51FB\u51B3\u5B9A\uFF08\u65E7\u6D41\u7A0B\u5F00\u673A\u81EA\u52A8\u5EFA\u5927\u5385\uFF0C\n\t\t// \u53CC\u5F00\u7B2C\u4E8C\u5B9E\u4F8B\u5FC5\u987B\u8D76\u5728 CreateLobby \u5B8C\u6210\u524D\u8FDE\u4E0A\u624D\u6709\u0022\u5BA2\u6237\u7AEF\u0022\u8EAB\u4EFD\uFF0C\u5929\u7136\u7ADE\u6001\uFF09\u3002\n\t\t_food = new FoodManager();\n\t\t_power = new PowerUpManager();\n\t\t_net = _root.AddComponent\u003CNetworkManager\u003E();\n\t\tGameMusic.PlayMenu();   // \u83DC\u5355\u66F2\uFF08\u5F00\u5C40\u5728 StartGame \u5207\u6218\u6597\u66F2\uFF09\n\n\t\t// \u4E3B\u83DC\u5355\u80CC\u666F\u6F14\u793A\u8D5B\uFF08v0.6.3.0\uFF09\uFF1A\u79BB\u7EBF authority \u8DD1\u4E00\u573A\u7EAF bot \u5BF9\u6218\u5F53\u80CC\u666F\n\t\tif ( NetworkManager.IsAuthority \u0026\u0026 !Networking.IsActive )\n\t\t{\n\t\t\tStartMenuDemo();\n\t\t}\n\t\telse if ( !NetworkManager.IsAuthority \u0026\u0026 Networking.IsActive )\n\t\t{\n\t\t\t// \u5BA2\u6237\u7AEF\u5728\u4F1A\u8BDD\u91CC\uFF08\u91CD\u8FDE/\u70ED\u91CD\u8F7D/\u81EA\u52A8\u91CD\u8FDE\u540E\u91CD\u5EFA\u4E16\u754C\uFF09\uFF1A\u4E0D\u4EAE\u4E3B\u83DC\u5355\u2014\u2014\n\t\t\t// \u76F4\u63A5\u56DE\u623F\u95F4\u7B49\u5F85\u6216\u5BF9\u5C40\uFF08\u5411 host \u8981\u5F53\u524D\u72B6\u6001\uFF0C\u89C1 RequestMatchState\uFF09\n\t\t\t_menu?.Hide();\n\t\t\tEnterClientWaiting();\n\t\t\t_lobby?.ShowClient();\n\t\t\tNetworkManager.RequestMatchState();\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u4E3B\u83DC\u5355\u52A8\u4F5C\uFF080=\u5EFA\u670D 1=\u5FEB\u901F\u52A0\u5165 2=\u4EBA\u673A\u5BF9\u6218 3=\u9000\u51FA\uFF09\u3002\n\t/// HOST / VS BOTS \u90FD\u5148\u8FDB**\u623F\u95F4**\uFF08M5 \u4E8C\u7EA7\u754C\u9762\uFF09\uFF1A\u8C03\u53C2\u3001\u770B\u73A9\u5BB6\u3001\u70B9 START \u624D\u5F00\u5C40 \u003C/summary\u003E\n\tpublic void MenuAction( int index )\n\t{\n\t\tif ( _gameStarted || _lobbyOpen ) return;\n\n\t\tswitch ( index )\n\t\t{\n\t\t\tcase 0:   // HOST\uFF1A\u672C\u673A\u5F53\u623F\u4E3B\uFF08\u5EFA\u516C\u5F00\u5927\u5385\uFF0C\u522B\u4EBA\u53EF\u52A0\u5165\uFF09\u2192 \u8FDB\u623F\u95F4\n\t\t\t\tStopMenuDemo();\n\t\t\t\tOpenHostLobby();\n\t\t\t\tGameLog.Info( \u0022[menu] host game (room)\u0022 );\n\t\t\t\t_net.RunHostFlow();\n\t\t\t\tbreak;\n\n\t\t\tcase 1:   // JOIN\uFF1A\u623F\u95F4\u5217\u8868\u9875\uFF08v0.6.7.0\uFF09\u2014\u2014\u5217\u51FA Steam \u516C\u5F00\u5927\u5385\u70B9\u9009\u52A0\u5165\uFF1BJOIN LOCAL \u662F\u540C\u673A\u8C03\u8BD5\u515C\u5E95\n\t\t\t\tStopMenuDemo();\n\t\t\t\t_menu?.Hide();\n\t\t\t\t_browser?.Show();\n\t\t\t\tbreak;\n\n\t\t\tcase 2:   // VS BOTS\uFF1A\u4EBA\u673A\u5BF9\u6218\uFF08\u4E0D\u5EFA\u5927\u5385\uFF0C\u79BB\u7EBF\u623F\u95F4\uFF0C\u4E00\u6837\u8C03\u53C2\u540E START\uFF09\n\t\t\t\tStopMenuDemo();\n\t\t\t\tOpenHostLobby();\n\t\t\t\tGameLog.Info( \u0022[menu] vs bots (offline room)\u0022 );\n\t\t\t\t_net.RunHostFlow( createLobby: false );\n\t\t\t\tbreak;\n\n\t\t\tcase 3:   // QUIT\uFF1A\u7F16\u8F91\u5668\u91CC\u9000\u4E0D\u51FA\u5E94\u7528\uFF0C\u53EA\u63D0\u793A\n\t\t\t\tif ( Game.IsEditor )\n\t\t\t\t\t_menu?.SetStatus( \u0022QUIT IS STANDALONE-ONLY (THIS IS THE EDITOR)\u0022 );\n\t\t\t\telse\n\t\t\t\t\tGame.Close();\n\t\t\t\tbreak;\n\n\t\t\tcase 4:   // SETTINGS\uFF1A\u8BBE\u7F6E\u9875\uFF08\u6CDB\u5149\u5F00\u5173/\u97F3\u4E50\u97F3\u91CF\uFF1BBACK \u56DE\u83DC\u5355\uFF09\n\t\t\t\t_menu?.Hide();\n\t\t\t\t_settings?.Show();\n\t\t\t\tbreak;\n\n\t\t\tcase 5:   // RANKINGS\uFF1A\u5168\u7403 XP \u6392\u884C\u9875\uFF08v0.7.8.19\uFF1BBACK \u56DE\u83DC\u5355\uFF09\n\t\t\t\t_menu?.Hide();\n\t\t\t\t_rankings?.Show();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u4E3B\u83DC\u5355\u80CC\u666F\u6F14\u793A\u8D5B\uFF08v0.6.3.0 \u7528\u6237\u9700\u6C42\uFF09\uFF1Aauthority \u79BB\u7EBF\u8DD1\u4E00\u573A\u7EAF bot \u5BF9\u6218\u5F53\u83DC\u5355\u80CC\u666F\uFF0C\n\t/// \u955C\u5934\u89C2\u6218\u6700\u5927 bot\uFF1B\u89C4\u5219\u968F\u83DC\u5355\u8BBE\u7F6E\uFF0C\u65F6\u95F4\u5230\u5C31\u5730\u91CD\u5F00\u3002\u8FDB\u623F\u95F4/\u52A0\u5165\u65F6 StopMenuDemo \u6536\u573A\u3002\n\t/// \u003C/summary\u003E\n\tvoid StartMenuDemo()\n\t{\n\t\tif ( _menuDemo ) return;\n\t\t_menuDemo = true;\n\n\t\tGameConfig.ArenaHalfSize = Math.Clamp( GameConfig.ConvarArenaSize, 1024f, 12288f );\n\t\tMatchState.ActivateForMatch();   // \u6309\u5F53\u524D\u83DC\u5355\u8BBE\u7F6E\u8DD1\uFF08\u65E0\u8FDE\u63A5\uFF0CRPC \u7A7A\u53D1\u65E0\u5BB3\uFF09\n\t\t_food.Init();\n\t\tGenerateSpikes();\n\t\t_power.Init();\n\n\t\tvar target = Math.Clamp( MatchState.PlayerTarget, 2, 64 );\n\t\tfor ( int i = 0; i \u003C target; i\u002B\u002B )\n\t\t{\n\t\t\tSpawnBall( RandomSpawnPos(), isBot: true, owner: null );\n\t\t}\n\t\tGameLog.Info( $\u0022[menu] demo bots started ({target})\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u6536\u6F14\u793A\u8D5B\uFF1A\u6E05 bot/\u5206\u8EAB\uFF0C\u91CD\u7F6E\u521D\u59CB\u5316\u6807\u8BB0\uFF08\u771F\u5B9E\u5F00\u5C40\u7684 OnLobbyOpened \u4F1A\u91CD\u65B0\u6295\u98DF\uFF09 \u003C/summary\u003E\n\tvoid StopMenuDemo()\n\t{\n\t\tif ( !_menuDemo ) return;\n\t\t_menuDemo = false;\n\n\t\tforeach ( var b in _balls.ToArray() )\n\t\t{\n\t\t\tif ( b.IsValid() ) b.GameObject.Destroy();\n\t\t}\n\t\t_balls.Clear();\n\t\t_cells.Clear();\n\t\t_lastPos.Clear();\n\t\t_steerDir.Clear();\n\t\t_sinceEject.Clear();\n\t\t_power?.Reset();\n\t\t_networkReady = false;\n\t\tGameLog.Info( \u0022[menu] demo stopped\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E host \u5F00\u623F\uFF1A\u85CF\u83DC\u5355\u3001\u4EAE\u623F\u95F4\u754C\u9762\uFF08\u86F0\u4F0F\u7403\u7531 OnLobbyOpened \u5728\u4F1A\u8BDD\u5C31\u7EEA\u540E\u751F\u6210\uFF09 \u003C/summary\u003E\n\tvoid OpenHostLobby()\n\t{\n\t\t_lobbyOpen = true;\n\n\t\t// \u5F00\u65B0\u623F\u9ED8\u8BA4 3 \u4EBA\u56E2\u961F\u8D5B\uFF08v0.7.8.0 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u663E\u5F0F\u5F52\u4F4D\u800C\u4E0D\u662F\u53EA\u9760\u9759\u6001\u521D\u59CB\u5316\u5668\u2014\u2014\n\t\t// \u70ED\u91CD\u8F7D\u4FDD\u7559\u65E7\u9759\u6001\u503C\uFF08\u6B8B\u7559 Ffa\uFF09\uFF0C\u5F00\u623F\u65F6\u91CD\u8BBE\u624D\u80FD\u81EA\u6108\uFF1B\u540C\u4E00\u623F\u95F4\u8FDE\u7EED\u591A\u5C40\n\t\t// \uFF08P \u9000\u5C40/\u7ED3\u7B97\u56DE\u623F\uFF09\u4E0D\u8D70\u8FD9\u91CC\uFF0C\u73A9\u5BB6\u6539\u8FC7\u7684\u9009\u62E9\u4FDD\u7559\n\t\tMatchState.PendingMode = MatchState.Mode.Team3;\n\n\t\t_menu?.Hide();\n\t\t_lobby?.ShowHost();\n\t}\n\n\t/// \u003Csummary\u003E \u83DC\u5355\u72B6\u6001\u884C\uFF08NetworkManager \u7684\u5F02\u6B65\u6D41\u7A0B\u4E5F\u7528\u5B83\u62A5\u8FDB\u5EA6\uFF09 \u003C/summary\u003E\n\tpublic void MenuStatus( string text ) =\u003E _menu?.SetStatus( text );\n\n\t/// \u003Csummary\u003E \u5F00\u5C40\u516C\u5171\u5C3E\u5DF4\uFF1A\u85CF\u83DC\u5355\u3001\u4E0A HUD\uFF08\u56DE\u83DC\u5355\u540E\u518D\u5F00\u5C40\u590D\u7528\u5DF2\u6709 HUD\uFF0C\u4E0D\u91CD\u590D\u5EFA\uFF09\u3001\u5207\u6218\u6597\u66F2 \u003C/summary\u003E\n\tvoid StartGame()\n\t{\n\t\t_joinRequested = false;\n\t\t_menu?.Hide();\n\n\t\tif ( _hud.IsValid() )\n\t\t{\n\t\t\t_hud.GameObject.Enabled = true;\n\t\t\tif ( _endBoard.IsValid() ) _endBoard.Hide();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCreateHud();\n\t\t}\n\n\t\tGameMusic.PlayBattle();\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u5BA2\u6237\u7AEF\uFF1A\u542F\u7528\u5FEB\u7167\u4E16\u754C\u6839\uFF08\u5E42\u7B49\uFF1BEnsureWorld \u8C03\u4E00\u6B21 \u002B ScanBalls \u6BCF\u79D2\u81EA\u6108\u2014\u2014\n\t/// \u9996\u6B21 Tick \u53EF\u80FD\u65E9\u4E8E\u5FEB\u7167\u5E94\u7528\u5B8C\u6210\uFF0C\u6251\u7A7A\u540E\u9760\u81EA\u6108\u515C\u4F4F\uFF09\u3002\n\t/// \u542B\u7403\u5BF9\u8C61\u7684\u9876\u5C42 GO \u89C6\u4E3A\u5FEB\u7167\u4E16\u754C\uFF1A\u542F\u7528\u4E4B\uFF0C\u5E76\u5265\u6389\u5176\u76F8\u673A/HUD \u5B50\u7269\u4F53\uFF1B\u5176\u4F59\u9876\u5C42\u5BF9\u8C61\n\t/// \uFF08\u6A21\u677F\u6B8B\u7559\uFF09\u7167\u65E7\u7981\u7528\u3002\n\t/// \u003C/summary\u003E\n\tvoid AdoptSnapshotWorld()\n\t{\n\t\tforeach ( var go in Scene.Children.ToArray() )\n\t\t{\n\t\t\tif ( !go.IsValid() || go == _root ) continue;\n\n\t\t\tvar hasBalls = false;\n\t\t\tforeach ( var b in go.GetComponentsInChildren\u003CBall\u003E( true ) )\n\t\t\t{\n\t\t\t\thasBalls = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif ( !hasBalls )\n\t\t\t{\n\t\t\t\tgo.Enabled = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( !go.Enabled )\n\t\t\t{\n\t\t\t\tgo.Enabled = true;\n\t\t\t\tGameLog.Info( \u0022[game] snapshot world root enabled\u0022 );\n\t\t\t}\n\n\t\t\tforeach ( var child in go.Children.ToArray() )\n\t\t\t{\n\t\t\t\tif ( !child.IsValid() ) continue;\n\t\t\t\t// \u5265\u6389 host \u5FEB\u7167\u91CC\u81EA\u5E26\u7684\u76F8\u673A\u4E0E\u5168\u90E8 UI \u9762\u677F\uFF08MainMenu/LobbyPanel/EndBoard \u4F1A\u968F\u5FEB\u7167\n\t\t\t\t// \u590D\u5236\u8FC7\u6765\uFF0C\u5BA2\u6237\u7AEF\u7559\u7740\u5C31\u662F\u53CC\u4EFD\u754C\u9762/host \u89C6\u89D2\u7684\u623F\u95F4\u9875\uFF09\u2014\u2014\u53EA\u7528\u672C\u673A\u81EA\u5DF1\u5EFA\u7684\u90A3\u5957\n\t\t\t\tif ( child.Name == \u0022Camera\u0022 || child.Name == \u0022Hud\u0022 || child.Name == \u0022MainMenu\u0022\n\t\t\t\t\t|| child.Name == \u0022LobbyPanel\u0022 || child.Name == \u0022EndBoard\u0022 )\n\t\t\t\t{\n\t\t\t\t\tvar name = child.Name;\n\t\t\t\t\tchild.Destroy();\n\t\t\t\t\tGameLog.Info( $\u0022[game] stripped snapshot \u0027{name}\u0027\u0022 );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u623F\u95F4\u5C31\u7EEA\uFF08host \u5EFA\u5B8C\u5927\u5385 / \u79BB\u7EBF\u623F\u95F4\u65F6\u8C03\u7528\uFF0CM5\uFF09\uFF1A\u751F\u6210**\u86F0\u4F0F\u7403**\u2014\u2014host \u7403 \u002B \u6EE1\u7F16\u9884\u5907 bot\uFF0C\n\t/// Alive=false \u4E0D\u53EF\u89C1\u4E0D\u6A21\u62DF\u3002\u5F15\u64CE\u7EA6\u675F\uFF1A\u0022\u8FDB\u884C\u4E2D NetworkSpawn \u4E0D\u590D\u5236\u5230\u5DF2\u8FDE\u63A5\u5BA2\u6237\u7AEF\u0022\uFF08\u5B9E\u6D4B\uFF09\uFF0C\n\t/// \u6240\u6709\u7403\u5FC5\u987B\u5728\u5404\u7AEF\u521D\u59CB\u5FEB\u7167\u524D\u5C31\u5B58\u5728\uFF0C\u5F00\u5C40\uFF08StartMatchFromLobby\uFF09\u53EA\u6FC0\u6D3B\u3001\u4E0D\u518D\u65B0\u5EFA\u3002\n\t/// \u98DF\u7269/\u5C16\u523A\u662F\u7EAF RPC \u6570\u636E\uFF08\u4E0D\u7ECF\u5FEB\u7167\uFF09\uFF0C\u5F00\u5C40\u65F6\u624D\u751F\u6210\u3002\u5BA2\u6237\u7AEF\u4E0D\u8C03\u7528\u3002\n\t/// \u003C/summary\u003E\n\tpublic void OnLobbyOpened()\n\t{\n\t\tif ( _networkReady ) return;\n\t\t_networkReady = true;\n\n\t\tGameLog.Info( $\u0022[game] CircleRoyale {GameConfig.Version} lobby opened auth={NetworkManager.IsAuthority}\u0022 );\n\t\tif ( !NetworkManager.IsAuthority ) return;\n\n\t\t// \u623F\u95F4\u8BBE\u7F6E\uFF1Aconvar \u8986\u5199\u9ED8\u8BA4\u503C\uFF08arena \u7ECF Replicated \u540C\u6B65\u7ED9\u5BA2\u6237\u7AEF\uFF09\n\t\tGameConfig.ArenaHalfSize = Math.Clamp( GameConfig.ConvarArenaSize, 1024f, 12288f );\n\n\t\tSpawnBall( Vector3.Zero, isBot: false, owner: null, dormant: true );\n\t\tfor ( int i = 1; i \u003C GameConfig.LobbyReserveBalls; i\u002B\u002B )\n\t\t{\n\t\t\tSpawnBall( RandomSpawnPos(), isBot: true, owner: null, dormant: true );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u623F\u95F4\u5185 START\uFF08host\uFF09\uFF1A\u89C4\u5219\u751F\u6548\u3001\u6295\u98DF/\u5C16\u523A\u3001\u6FC0\u6D3B\u86F0\u4F0F\u7403\uFF08\u8FDC\u7A0B\u771F\u4EBA/host/bot \u8865\u9F50\u5230\u76EE\u6807\u6570\uFF09\u3001\n\t/// \u7ED9\u7B49\u5728\u623F\u95F4\u91CC\u7684\u5BA2\u6237\u7AEF\u5B9A\u5411\u8865\u53D1\u98DF\u7269\u5C16\u523A\u3001\u5E7F\u64AD\u5F00\u5C40\u3002\u961F\u4F0D\u5206\u914D\u4E5F\u5728\u8FD9\u91CC\u505A\uFF08\u6B64\u523B\u6A21\u5F0F\u624D\u786E\u5B9A\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tpublic void StartMatchFromLobby()\n\t{\n\t\tif ( !_lobbyOpen || _gameStarted ) return;\n\t\t_lobbyOpen = false;\n\t\t_gameStarted = true;\n\n\t\tMatchState.ActivateForMatch();\n\t\t_teamSeq = 0;\n\n\t\t_food.Init();\n\t\tGenerateSpikes();\n\t\t_power.Init();\n\n\t\t// \u6BCF\u5C40\u7403\u7EC4\u590D\u4F4D\uFF08\u7403\u5BF9\u8C61\u6C38\u4E0D\u9500\u6BC1\u2014\u2014v0.6.4.2 \u8FDE\u7EED\u591A\u5C40\uFF09\uFF1A\u771F\u4EBA\u7403\uFF08\u8FDC\u7A0B\u002Bhost\uFF09\u5168\u90E8\u590D\u4F4D\uFF0C\n\t\t// bot \u6309\u5269\u4F59\u540D\u989D\u6FC0\u6D3B\u3001\u591A\u4F59\u7684\u86F0\u4F0F\uFF1B\u4E0A\u4E00\u5C40\u7684\u8D28\u91CF/\u751F\u6B7B\u6001\u4E00\u5E76\u6E05\u96F6\n\t\tint remotes = 0;\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() ) continue;\n\t\t\tif ( !b.IsRemoteOwned \u0026\u0026 b.IsBot ) continue;   // bot \u8D70\u4E0B\u9762\u7684\u540D\u989D\u8865\u9F50\n\n\t\t\tRespawnBall( b, NextTeamIndex(), cue: false );\n\t\t\tif ( b.IsRemoteOwned ) remotes\u002B\u002B;\n\t\t}\n\n\t\tint botsWanted = Math.Clamp( MatchState.PlayerTarget - 1 - remotes, 0, GameConfig.LobbyReserveBalls - 1 );\n\t\tint bots = 0;\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.IsBot ) continue;\n\n\t\t\tif ( bots \u003C botsWanted )\n\t\t\t{\n\t\t\t\tRespawnBall( b, NextTeamIndex(), cue: false );\n\t\t\t\tbots\u002B\u002B;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tb.Alive = false;   // \u591A\u4F59 bot \u86F0\u4F0F\uFF08\u4E0D\u53EF\u89C1\u4E0D\u6A21\u62DF\uFF09\n\t\t\t\tb.Dormant = true;\n\t\t\t}\n\t\t}\n\n\t\t// \u7B49\u5728\u623F\u95F4\u91CC\u7684\u5BA2\u6237\u7AEF\u6CA1\u6536\u5230\u8FC7\u98DF\u7269/\u5C16\u523A\uFF08OnActive \u65F6\u8FD8\u6CA1\u751F\u6210\uFF09\uFF1A\u9010\u4E00\u5B9A\u5411\u8865\u53D1\n\t\tvar foods = _food.Foods;\n\t\tvar spikes = _spikes;\n\t\tvar powerWires = _power.ToWireForSync();\n\t\tforeach ( var conn in Connection.All )\n\t\t{\n\t\t\tif ( conn is null || !conn.IsActive ) continue;\n\t\t\tif ( Connection.Local is not null \u0026\u0026 conn.Id == Connection.Local.Id ) continue;\n\n\t\t\tusing ( Rpc.FilterInclude( conn ) )\n\t\t\t{\n\t\t\t\tif ( foods is not null ) NetworkManager.FoodFull( foods );\n\t\t\t\tif ( spikes is not null ) NetworkManager.SpikesFull( spikes );\n\t\t\t\tif ( powerWires is not null ) NetworkManager.PowerUpFull( powerWires );\n\t\t\t}\n\t\t}\n\n\t\tif ( Networking.IsActive ) NetworkManager.MatchStarted();   // \u65E0\u4F1A\u8BDD\u4E0D\u5E7F\u64AD\n\t\tOnMatchStartedLocal();\n\n\t\tGameLog.Info( $\u0022[match] started \u2014 mode={MatchState.CurrentMode} target={MatchState.PlayerTarget} remotes={remotes} bots={bots}\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u56E2\u961F\u8D5B\u6309\u5757\u5206\u961F\uFF08v0.6.5.0 \u4FEE bug\uFF1A\u539F %TeamSize \u8F6E\u8F6C=\u53EA\u6709 TeamSize \u4E2A\u961F\u3001\u6BCF\u961F\u5341\u51E0\u4EBA\uFF1B\n\t/// \u7528\u6237\u8BED\u4E49\u662F\u0022\u6BCF\u961F N \u4EBA\u0022\u2192 48 \u4EBA 3 \u4EBA\u961F\u5E94\u5206\u51FA 16 \u961F\uFF09\u3002\u666E\u901A\u8D5B\u6052 -1 \u003C/summary\u003E\n\tint NextTeamIndex() =\u003E MatchState.IsTeam ? _teamSeq\u002B\u002B / MatchState.TeamSize : -1;\n\n\t/// \u003Csummary\u003E \u4E24\u9897\u7403\u662F\u5426\u540C\u961F\uFF08TeamIndex \u5C0F\u4E8E 0 = \u666E\u901A\u8D5B/\u672A\u5206\u961F\uFF0C\u4E0D\u7B97\u540C\u961F\uFF09 \u003C/summary\u003E\n\tstatic bool SameTeam( Ball a, Ball b ) =\u003E\n\t\ta.TeamIndex \u003E= 0 \u0026\u0026 a.TeamIndex == b.TeamIndex;\n\n\t/// \u003Csummary\u003E \u63A8\u6324\u7528\uFF1A\u4E24\u4E2A SteamId \u662F\u5426\u540C\u9635\u8425\uFF08\u540C\u4E00 SteamId=\u81EA\u5DF1\u7684\u8EAB\u4F53\uFF1B\u56E2\u961F\u8D5B\u6309\u7403\u4E0A\u7684 TeamIndex \u67E5\uFF09\u3002\n\t/// \u540C\u9635\u8425\u4E0D\u63A8\u6324\uFF08BoB \u60EF\u4F8B\uFF1A\u81EA\u5DF1\u548C\u961F\u53CB\u53EF\u91CD\u53E0\uFF09 \u003C/summary\u003E\n\tpublic bool SameTeamSteam( long a, long b )\n\t{\n\t\tif ( a == b ) return true;\n\t\tif ( !MatchState.IsTeam ) return false;\n\t\tvar ta = TeamIndexOfSteam( a );\n\t\treturn ta \u003E= 0 \u0026\u0026 ta == TeamIndexOfSteam( b );\n\t}\n\n\tint TeamIndexOfSteam( long sid )\n\t{\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( b.IsValid() \u0026\u0026 b.OwnerSteamId == sid \u0026\u0026 b.TeamIndex \u003E= 0 ) return b.TeamIndex;\n\t\t}\n\t\treturn -1;\n\t}\n\n\t/// \u003Csummary\u003E eater \u4E0E\u5B62\u5B50\u4E3B\u4EBA\u662F\u5426\u540C\u9635\u8425\uFF08v0.7.8.23\uFF09\uFF1A\u540C\u4E00 SteamId\uFF08\u81EA\u5DF1\uFF09\uFF0C\n\t/// \u6216\u56E2\u961F\u8D5B\u4E0B\u67E5 teamOf \u540C\u961F\u3002\u540C\u9635\u8425\u8D70 0.2s \u5FEB\u6863\uFF0C\u654C\u4EBA 0.5s\u3002\n\t/// \u6CE8\uFF1Abot \u7684 OwnerSteamId \u5168\u662F 0\u2014\u2014bot \u4E4B\u95F4\u7684 blob \u6052\u5224\u0022\u81EA\u5DF1\u0022\u5FEB\u6863\uFF08\u65E2\u6709 SteamId 0 \u8BBE\u8BA1\u7684\u65E2\u6709\u6A21\u7CCA\uFF0C\u65E0\u5BB3\uFF09 \u003C/summary\u003E\n\tstatic bool SameSide( long blobOwner, long eater, Dictionary\u003Clong, int\u003E teamOf )\n\t{\n\t\tif ( eater == blobOwner ) return true;\n\t\treturn MatchState.IsTeam\n\t\t\t\u0026\u0026 teamOf.TryGetValue( eater, out var et )\n\t\t\t\u0026\u0026 teamOf.TryGetValue( blobOwner, out var bt )\n\t\t\t\u0026\u0026 et == bt;\n\t}\n\n\t/// \u003Csummary\u003E \u5F00\u5C40\u843D\u5730\uFF08\u672C\u673A\uFF09\uFF1A\u51FA\u623F\u95F4\u3001\u4E0A HUD\u3001\u5207\u6218\u6597\u66F2\uFF08host \u76F4\u8C03\uFF1B\u5BA2\u6237\u7AEF\u7ECF RPC\uFF09\u3002\n\t/// \u5BA2\u6237\u7AEF\u5FC5\u987B\u5728\u8FD9\u91CC\u7F6E _gameStarted\u2014\u2014host \u7684\u7F6E\u4F4D\u5728 MenuAction \u91CC\uFF0C\u5BA2\u6237\u7AEF\u6CA1\u6709\u90A3\u6761\u8DEF\u5F84\uFF0C\n\t/// \u4E0D\u7F6E\u4F4D\u7684\u8BDD Tick \u7684\u0022\u8FDE\u4E0A\u4E86\u5C31\u8FDB\u623F\u95F4\u0022\u5224\u5B9A\u4F1A\u5728 MatchStarted \u4E4B\u540E\u628A\u623F\u95F4\u9875\u76D6\u56DE\u6E38\u620F\u4E0A\uFF08\u5B9E\u6D4B\uFF09\u3002\u003C/summary\u003E\n\tvoid OnMatchStartedLocal()\n\t{\n\t\t_lobbyWaiting = false;\n\t\t_lobbyHeartbeatArmed = false;   // \u51FA\u623F\u95F4\uFF1A\u623F\u95F4\u5FC3\u8DF3\u8BA9\u4F4D\u7ED9\u5BF9\u5C40\u5FC3\u8DF3\uFF08MatchTick\uFF09\n\t\t_lobby?.Hide();\n\t\tStartGame();\n\t\t_gameStarted = true;\n\t\t_joinRequested = false;\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\uFF1A\u623F\u4E3B\u70B9\u4E86 START\uFF08host \u56DE\u58F0\u7528 IsAuthority \u6321\u6389\u2014\u2014host \u5DF2\u76F4\u8C03\uFF09 \u003C/summary\u003E\n\tpublic void OnMatchStartedRemote()\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\tGameLog.Info( \u0022[match] host started the game\u0022 );\n\n\t\t// \u7528\u6237\u5B9A\u7A3F\uFF08v0.6.4.1\uFF09\uFF1A\u5728\u623F\u95F4\u91CC\u7684\u5BA2\u6237\u7AEF**\u4E0D\u81EA\u52A8\u8FDB\u5C40**\u2014\u2014\u623F\u95F4\u9875\u4EAE\u8D77 JOIN GAME \u6309\u94AE\uFF0C\n\t\t// \u7531\u73A9\u5BB6\u70B9\u51FB\u540E\u8D70 3 \u79D2\u52A0\u5165\u5012\u8BA1\u65F6\u518D\u8FDB\u5165\uFF1B\u4E0D\u5728\u623F\u95F4\uFF08\u5F02\u5E38\u6001\uFF09\u5219\u76F4\u63A5\u8FDB\n\t\tif ( _lobbyWaiting )\n\t\t{\n\t\t\t_matchRunning = true;\n\t\t\t_lobby?.SetMatchRunning();\n\t\t\treturn;\n\t\t}\n\t\tOnMatchStartedLocal();\n\t}\n\n\t/// \u003Csummary\u003E host \u53D6\u6D88\u5BF9\u5C40\uFF08P \u952E\u5FEB\u901F\u9000\u5C40\u7684\u5E7F\u64AD\u843D\u5730\uFF0C\u4EC5\u5BA2\u6237\u7AEF\uFF09\uFF1A\u56DE\u623F\u95F4\u7B49\u5F85\u9875 \u003C/summary\u003E\n\tpublic void OnMatchCancelledRemote()\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;   // host \u81EA\u5DF1\u5DF2\u76F4\u63A5\u5904\u7406\n\t\tGameLog.Info( \u0022[match] host cancelled the match \u2014 back to lobby\u0022 );\n\t\tResetToLobby();\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u70B9\u51FB\u623F\u95F4\u9875\u7684 JOIN GAME\uFF1A\u5BF9\u5C40\u8FDB\u884C\u4E2D \u2192 3 \u79D2\u52A0\u5165\u5012\u8BA1\u65F6 \u2192 \u8FDB\u5165 \u003C/summary\u003E\n\tpublic void ClientJoinMatch()\n\t{\n\t\tif ( !_matchRunning || _gameStarted || _joinCountdown \u003E= 0f ) return;\n\t\tif ( !_lobbyWaiting ) return;\n\n\t\t_joinCountdown = GameConfig.JoinCountdownSeconds;\n\t\t_lobby?.ShowJoinCountdown( GameConfig.JoinCountdownSeconds );\n\t\tGameLog.Info( \u0022[match] client joining via button\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u81EA\u52A8\u91CD\u8FDE watchdog \u53D1\u8D77\u91CD\u8FDE\uFF08NetworkManager \u8C03\u7528\uFF09\uFF1A\u91CD\u65B0\u8D70\u6807\u51C6\u52A0\u5165\u6D41 \u003C/summary\u003E\n\tpublic void NotifyReconnectStarted()\n\t{\n\t\t_joinRequested = true;\n\t\t_sinceJoinRequest = 0;\n\t}\n\n\t// ---- \u52A0\u5165\u6D41\u8F85\u52A9\uFF08\u623F\u95F4\u5217\u8868\u9875 v0.6.7.0\uFF09----\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u53D1\u8D77\u52A0\u5165\uFF08\u70B9\u623F\u95F4/\u5FEB\u901F\u52A0\u5165/\u672C\u5730\u76F4\u8FDE\u5171\u7528\uFF09\uFF1A\u7F6E\u8BF7\u6C42\u6807\uFF0CTick \u6355\u83B7\u4F1A\u8BDD\u6FC0\u6D3B\u6536\u5C3E \u003C/summary\u003E\n\tpublic void OnJoinAttemptStarted()\n\t{\n\t\t_joinRequested = true;\n\t\t_sinceJoinRequest = 0;\n\t}\n\n\t/// \u003Csummary\u003E \u52A0\u5165\u5931\u8D25\u63D0\u793A\uFF1A\u6D4F\u89C8\u5668\u9875\u5F00\u7740\u663E\u793A\u5728\u90A3\uFF0C\u4E3B\u83DC\u5355\u72B6\u6001\u884C\u662F\u515C\u5E95 \u003C/summary\u003E\n\tpublic void OnJoinAttemptFailed( string message ) =\u003E _browser?.SetStatus( message );\n\n\t/// \u003Csummary\u003E \u540C\u673A\u8C03\u8BD5\u76F4\u8FDE\uFF08\u623F\u95F4\u5217\u8868\u9875 JOIN LOCAL\uFF09\uFF1Alocal \u5730\u5740 = \u672C\u673A host \u003C/summary\u003E\n\tpublic void JoinLocalRoom()\n\t{\n\t\tOnJoinAttemptStarted();\n\t\t_net?.StartJoin( GameConfig.DefaultJoinAddress );\n\t\t_browser?.SetStatus( \u0022CONNECTING TO LOCAL HOST ...\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u623F\u95F4\u5217\u8868\u9875\u8FD4\u56DE\u4E3B\u83DC\u5355\uFF08BACK\uFF09 \u003C/summary\u003E\n\tpublic void CloseBrowser()\n\t{\n\t\t_browser?.Hide();\n\t\t_menu?.Show();\n\t}\n\n\t/// \u003Csummary\u003E \u8BBE\u7F6E\u9875\u6536\u8D77\uFF08SettingsPanel BACK \u8C03\u7528\uFF09\uFF1A\u56DE\u4E3B\u83DC\u5355 \u003C/summary\u003E\n\tpublic void CloseSettings()\n\t{\n\t\t_settings?.Hide();\n\t\t_menu?.Show();\n\t}\n\n\t/// \u003Csummary\u003E \u6392\u884C\u9875\u6536\u8D77\uFF08RankingsPanel BACK \u8C03\u7528\uFF09\uFF1A\u56DE\u4E3B\u83DC\u5355 \u003C/summary\u003E\n\tpublic void CloseRankings()\n\t{\n\t\t_rankings?.Hide();\n\t\t_menu?.Show();\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u6389\u7EBF\uFF0C\u81EA\u52A8\u91CD\u8FDE\u5F00\u59CB\uFF08NetworkManager \u8C03\u7528\uFF09\uFF1A\u754C\u9762\u7559\u5728\u539F\u5730 \u002B \u623F\u95F4\u9875\u72B6\u6001\u884C\u63D0\u793A \u003C/summary\u003E\n\tpublic void OnDisconnectRecoveryStarted()\n\t{\n\t\tif ( _lobbyWaiting ) _lobby?.SetConnectionLost();\n\t\tLog.Warning( \u0022[net] connection lost \u2014 staying put, auto reconnecting\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u81EA\u52A8\u91CD\u8FDE\u8017\u5C3D\uFF08NetworkManager \u8C03\u7528\uFF09\uFF1A\u56DE\u4E3B\u83DC\u5355\uFF08v0.6.4.6 \u7528\u6237\u5B9A\u7A3F\uFF09 \u003C/summary\u003E\n\tpublic void OnReconnectFailed()\n\t{\n\t\tResetToMenu();\n\t\t_menu?.SetStatus( \u0022CONNECTION LOST \u2014 COULD NOT REACH HOST\u0022 );\n\t}\n\n\t// ---- \u623F\u95F4\u4FE1\u606F\u540C\u6B65\uFF08host \u2192 \u7B49\u5F85\u4E2D\u7684\u5BA2\u6237\u7AEF\uFF09----\n\n\t/// \u003Csummary\u003E \u623F\u95F4\u73A9\u5BB6\u540D\u5355\uFF1Ahost \u81EA\u5DF1 \u002B \u6240\u6709\u6D3B\u8DC3\u8FDE\u63A5\uFF08\u987A\u5E8F\u5373\u5217\u8868\u987A\u5E8F\uFF0C0 \u53F7\u662F\u623F\u4E3B\uFF09 \u003C/summary\u003E\n\tpublic List\u003Cstring\u003E LobbyPlayerNames()\n\t{\n\t\tvar list = new List\u003Cstring\u003E();\n\t\ttry\n\t\t{\n\t\t\tif ( Connection.Local is not null ) list.Add( Connection.Local.DisplayName );\n\n\t\t\tforeach ( var c in Connection.All )\n\t\t\t{\n\t\t\t\tif ( c is null || !c.IsActive ) continue;\n\t\t\t\tif ( Connection.Local is not null \u0026\u0026 c.Id == Connection.Local.Id ) continue;\n\t\t\t\tlist.Add( c.DisplayName );\n\t\t\t}\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\t// \u4F1A\u8BDD\u672A\u5C31\u7EEA\u65F6\u7ED9\u7A7A\u540D\u5355\uFF0C\u754C\u9762\u663E\u793A\u5360\u4F4D\n\t\t}\n\t\treturn list;\n\t}\n\n\t/// \u003Csummary\u003E \u540D\u5355/\u53C2\u6570\u53D8\u4E86\uFF08\u7F51\u7EDC\u7EBF\u7A0B\u53EA\u7F6E\u810F\u6807\uFF0C\u4E3B\u7EBF\u7A0B Tick \u7EDF\u4E00\u5E7F\u64AD\uFF09 \u003C/summary\u003E\n\tpublic void MarkLobbyDirty() =\u003E _lobbyListDirty = true;\n\n\tvoid BroadcastLobbyState()\n\t{\n\t\tNetworkManager.LobbyState( (byte)MatchState.PendingMode, (int)MatchState.PendingDuration,\n\t\t\tMatchState.PendingPlayerTarget, LobbyPlayerNames().ToArray() );\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u5E94\u7528\u623F\u4E3B\u63A8\u9001\u7684\u623F\u95F4\u4FE1\u606F\uFF08host \u56DE\u58F0\u7528 IsAuthority \u6321\u6389\uFF09 \u003C/summary\u003E\n\tpublic void OnLobbyStateRemote( byte mode, int duration, int players, string[] names )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\n\t\t// \u623F\u95F4\u5FC3\u8DF3\uFF1Ahost \u6BCF 3s \u63A8\u4E00\u6B21\uFF08\u540D\u5355\u6CA1\u53D8\u4E5F\u63A8\uFF09\uFF0C\u6536\u5230\u5373\u5F52\u96F6\n\t\tif ( _lobbyWaiting )\n\t\t{\n\t\t\t_lobbyHeartbeatArmed = true;\n\t\t\t_sinceLobbyHeartbeat = 0;\n\t\t}\n\n\t\t_lobbyMode = mode;\n\t\t_lobbyDuration = duration;\n\t\t_lobbyPlayers = players;\n\t\t_lobbyNames = names ?? Array.Empty\u003Cstring\u003E();\n\t\t_lobby?.UpdateFromHost( mode, duration, players, names );\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\uFF1A\u4E3B\u7403\u88AB\u5403\uFF0C\u6700\u5927\u5206\u8EAB\u5DF2\u664B\u5347\u6210\u65B0\u4E3B\u7403\u2014\u2014\u628A\u7403\u77AC\u79FB\u5230\u664B\u5347\u4F4D\u7F6E\u7EE7\u7EED\u6A21\u62DF\n\t/// \uFF08\u8D28\u91CF\u7ECF [Sync] \u5230\u8FBE\uFF1Bhost \u56DE\u58F0\u7528 IsAuthority \u6321\u6389\uFF09\u3002 \u003C/summary\u003E\n\tpublic void OnMainPromotedRemote( float x, float y )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\n\t\tvar ball = LocalBall;\n\t\tif ( !ball.IsValid() ) return;\n\n\t\tball.TeleportTo( new Vector3( x, y, 0f ) );\n\t\tGameLog.Info( $\u0022[game] main promoted -\u003E continue at ({x:0},{y:0})\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u9759\u6001 RPC \u843D\u5730\uFF1A\u4E3A\u91CD\u8FDE/\u590D\u6D3B\u7684\u8FDE\u63A5\u539F\u5730\u590D\u6D3B\u5176\u7403\uFF08\u6309 OwnerSteamId \u5339\u914D\uFF09 \u003C/summary\u003E\n\tpublic void RespawnConnection( Connection conn )\n\t{\n\t\tif ( conn is null || !conn.IsActive ) return;\n\n\t\tvar steamId = conn.SteamId.Value;\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( b.IsValid() \u0026\u0026 !b.IsBot \u0026\u0026 !b.Alive \u0026\u0026 b.OwnerSteamId == steamId )\n\t\t\t{\n\t\t\t\tRespawnBall( b );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tGameLog.Info( $\u0022[net] respawn request: no dead ball for \u0027{conn.DisplayName}\u0027\u0022 );\n\t}\n\n\t// ---- \u98DF\u7269\u8FDC\u7AEF\u4E8B\u4EF6\u5E94\u7528\uFF08\u4EC5\u5BA2\u6237\u7AEF\uFF1Bhost \u7684\u5E7F\u64AD\u56DE\u58F0\u7528 IsAuthority \u6321\u6389\uFF09----\n\n\tpublic void OnFoodEatenRemote( int index, long eaterSteamId )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\n\t\t// \u5E94\u7528\u524D\u5148\u53D6\u4F4D\u7F6E/\u989C\u8272\u653E\u706B\u82B1\uFF08ApplyEaten \u4F1A\u7F6E Alive=false\uFF0C\u4F46\u6570\u636E\u4ECD\u5728\u2014\u2014\u53D6\u66F4\u4FDD\u9669\uFF09\n\t\tvar fs = _food?.Foods;\n\t\tif ( fs is not null \u0026\u0026 (uint)index \u003C (uint)fs.Length )\n\t\t{\n\t\t\tvar pos = new Vector3( fs[index].Pos.x, fs[index].Pos.y, 0f );\n\t\t\tNeonRenderer.Spark( pos, fs[index].ColorIndex );\n\t\t\tif ( GameSfx.IsMine( eaterSteamId ) )\n\t\t\t\tGameSfx.EatFood( pos );\n\t\t}\n\n\t\t_food?.ApplyEaten( index );\n\t}\n\n\tpublic void OnFoodRespawnedRemote( int index, FoodData food )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\t_food?.ApplyRespawned( index, food );\n\t}\n\n\tpublic void OnFoodFullRemote( FoodData[] foods )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\t_food?.ApplyFull( foods );\n\t\tGameLog.Info( $\u0022[game] food full sync received: {foods.Length}\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u5E94\u7528\u7EFF\u523A\u5168\u91CF\uFF08\u9759\u6001\u6570\u636E\uFF0C\u6536\u5230\u5373\u7528\uFF09 \u003C/summary\u003E\n\tpublic void OnSpikesFullRemote( Vector2[] spikes )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\t_spikes = spikes;\n\t\t_spikeFed = new float[spikes is null ? 0 : spikes.Length];   // \u5582\u98DF\u91CF\u955C\u50CF\u5F52\u96F6\uFF08\u540E\u7EED SpikeFed RPC \u9010\u6B65\u540C\u6B65\uFF09\n\t\t_spines.Clear();\n\t}\n\n\t// ---- \u5582\u523A/\u523A\u7206\u8FDC\u7AEF\u4E8B\u4EF6\uFF08v0.7.8.24\uFF09----\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u955C\u50CF\uFF1A\u67D0\u6735\u523A\u7684\u7D2F\u8BA1\u5582\u98DF\u91CF\uFF08host \u5DF2\u76F4\u63A5\u5199\u5165\uFF0C\u56DE\u58F0\u6321\u6389\uFF09 \u003C/summary\u003E\n\tpublic void OnSpikeFedRemote( int index, float fed )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\tif ( _spikeFed is not null \u0026\u0026 (uint)index \u003C (uint)_spikeFed.Length ) _spikeFed[index] = fed;\n\t}\n\n\t/// \u003Csummary\u003E \u5168\u7AEF\u7206\u6548\uFF1B\u5BA2\u6237\u7AEF\u7528\u4E0E host \u76F8\u540C\u7684\u786E\u5B9A\u6027\u89D2\u5EA6\u751F\u6210\u89C6\u89C9\u5F39\u5E55\uFF08host \u7684\u6743\u5A01\u5F39\u5E55\u5728 BurstSpike \u5DF2\u751F\u6210\uFF09 \u003C/summary\u003E\n\tpublic void OnSpikeBurstRemote( int index )\n\t{\n\t\tif ( _spikes is null || (uint)index \u003E= (uint)_spikes.Length ) return;\n\t\tvar pos = _spikes[index];\n\n\t\tif ( NetworkManager.IsAuthority )\n\t\t{\n\t\t\tGameSfx.Pop( new Vector3( pos.x, pos.y, 0f ) );   // host \u7206\u6548\u58F0\uFF08\u6743\u5A01\u5F39\u5E55\u5DF2\u5728 BurstSpike \u751F\u6210\uFF0C\u6E32\u67D3\u76F4\u63A5\u8BFB\uFF09\n\t\t\treturn;\n\t\t}\n\n\t\tif ( _spikeFed is not null \u0026\u0026 (uint)index \u003C (uint)_spikeFed.Length ) _spikeFed[index] = 0f;\n\t\tvar n = GameConfig.SpikeBurstCount;\n\t\tfor ( int k = 0; k \u003C n; k\u002B\u002B )\n\t\t{\n\t\t\t_spines.Add( new SpikeSpine\n\t\t\t{\n\t\t\t\tPos = new Vector2( pos.x, pos.y ),\n\t\t\t\tVel = SpineDir( k, n, index ) * GameConfig.SpineSpeed,\n\t\t\t\tSinceSpawn = 0,   // \u540C BurstSpike\uFF1ATimeSince \u5FC5\u987B\u663E\u5F0F\u5F52\u96F6\n\t\t\t} );\n\t\t}\n\t\tGameSfx.Pop( new Vector3( pos.x, pos.y, 0f ) );\n\t}\n\n\t/// \u003Csummary\u003E \u523A\u7206\u5F39\u5E55\u547D\u4E2D\u7279\u6548\uFF08\u5168\u7AEF\uFF1ABroadcast \u672C\u5730\u4E5F\u6267\u884C\uFF0Chost \u4E0D\u9884\u64AD\uFF09 \u003C/summary\u003E\n\tpublic void OnSpineHitRemote( float x, float y, byte colorIndex )\n\t{\n\t\tvar pos = new Vector3( x, y, 0f );\n\t\tNeonRenderer.Puff( pos, colorIndex );\n\t\tGameSfx.Pop( pos );\n\t}\n\n\t// ---- \u9053\u5177\u8FDC\u7AEF\u4E8B\u4EF6\u5E94\u7528\uFF08\u4EC5\u5BA2\u6237\u7AEF\uFF1Bhost \u7684\u5E7F\u64AD\u56DE\u58F0\u7528 IsAuthority \u6321\u6389\uFF09----\n\n\tpublic void OnPowerUpFullRemote( PowerUpWire[] wires )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\t_power?.ApplyFull( wires );\n\t}\n\n\tpublic void OnPowerUpPickedRemote( int slot, byte kind, long pickerSteamId )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\n\t\t// \u5E94\u7528\u524D\u5148\u53D6\u4F4D\u7F6E/\u79CD\u7C7B\u653E\u706B\u82B1\uFF08ApplyPicked \u4F1A\u7F6E\u7A7A\u69FD\uFF09\n\t\tif ( _power?.Ready ?? false )\n\t\t{\n\t\t\tvar slots = _power.Slots;\n\t\t\tif ( (uint)slot \u003C (uint)slots.Count )\n\t\t\t{\n\t\t\t\tvar s = slots[slot];\n\t\t\t\tNeonRenderer.Spark( new Vector3( s.Pos.x, s.Pos.y, 0f ), PowerUpManager.ColorOf( kind ) );\n\t\t\t\tif ( GameSfx.IsMine( pickerSteamId ) )\n\t\t\t\t{\n\t\t\t\t\tGameSfx.EatFood( new Vector3( s.Pos.x, s.Pos.y, 0f ) );\n\t\t\t\t\tGameAchievements.PowerStored();   // \u6210\u5C31\uFF1A\u7D2F\u8BA1\u6361 5 \u4E2A\u9053\u5177\u5165\u5305\uFF08\u5BA2\u6237\u7AEF\u4FA7\u8BA1\u6570\uFF09\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t_power?.ApplyPicked( slot );\n\t}\n\n\tpublic void OnPowerUpRespawnedRemote( int slot, byte kind, float x, float y )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\t_power?.ApplyRespawned( slot, kind, new Vector2( x, y ) );\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u4E2A\u4EBA\u9AD8\u5149\u6A2A\u5E45\u843D\u5730\uFF08host \u5DF2\u672C\u5730\u64AD\u8FC7\uFF0C\u56DE\u58F0\u6321\u6389\uFF1B\u53EA\u64AD\u0022\u662F\u6211\u7684\u0022\u90A3\u6761\uFF09 \u003C/summary\u003E\n\tpublic void OnPowerBannerRemote( byte kind, long ownerSteamId, byte mode )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\tif ( !GameSfx.IsMine( ownerSteamId ) ) return;\n\n\t\tvar text = mode == 2 ? $\u0022{PowerUpManager.NameOf( kind )} READY (Q)\u0022   // Q \u5145\u80FD\u5230\u70B9\uFF08v0.7.8.31\uFF09\n\t\t\t: mode == 0 ? $\u0022{PowerUpManager.NameOf( kind )} STORED\u0022 : GameHud.ActivationText( kind );\n\t\t_hud?.AddBanner( text, GameHud.KindColor( kind ) );\n\t\tGameSfx.Pickup();\n\n\t\tif ( mode == 1 ) GameAchievements.FeedTriggered( ownerSteamId );   // \u6210\u5C31\uFF1A\u9996\u6B21\u5582\u98DF\u89E6\u53D1\uFF08mode1=\u6FC0\u6D3B/\u5582\u517B\uFF09\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u5E94\u7528\u6B7B\u4EA1\u7206\u88C2\u7279\u6548\uFF08host \u5DF2\u5728\u672C\u5730\u751F\u6210\u8FC7\uFF0C\u56DE\u58F0\u7528 IsAuthority \u6321\u6389\uFF09\u3002\n\t/// \u89C6\u89C9\u5168\u573A\u64AD\uFF1B\u7206\u88C2\u97F3\u53EA\u64AD\u81EA\u5DF1\u6B7B\u4EA1/\u7206\u88C2\u7684\uFF08ownerSteamId \u5339\u914D\u672C\u673A\uFF09 \u003C/summary\u003E\n\tpublic void OnPoppedEffectRemote( Vector3 pos, byte colorIndex, float radius, long ownerSteamId )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\tNeonRenderer.Pop( pos, colorIndex, radius );\n\t\tif ( GameSfx.IsMine( ownerSteamId ) )\n\t\t\tGameSfx.Pop( pos );\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u541E\u7403\u4E8B\u4EF6\uFF08host \u56DE\u58F0\u7528 IsAuthority \u6321\u6389\u2014\u2014host \u5DF2\u5728 NotifyBallEaten \u76F4\u8C03\u8FC7\uFF09 \u003C/summary\u003E\n\tpublic void OnBallEatenRemote( long eaterSteamId, long eatenSteamId, float massGained )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\tOnBallEaten( eaterSteamId, eatenSteamId, massGained );\n\t}\n\n\tvoid CreateBackdrop()\n\t{\n\t\tvar go = new GameObject( true, \u0022GridBackdrop\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CGridBackdrop\u003E();\n\t}\n\n\t/// \u003Csummary\u003E \u5168\u573A\u666F\u4E00\u4E2A\u7684\u9713\u8679\u6279\u91CF\u753B\u7B14\uFF1A\u6BCF\u5E27\u628A\u6240\u6709\u7403\u5199\u8FDB\u4E24\u7EC4\u9876\u70B9 \u003C/summary\u003E\n\tvoid CreateNeon()\n\t{\n\t\tvar go = new GameObject( true, \u0022Neon\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CNeonRenderer\u003E();\n\t\tgo.AddComponent\u003CPieceAvatarLayer\u003E();   // \u5206\u8EAB\u5934\u50CF\uFF08\u6570\u636E\u5B9E\u4F53\u6CA1\u6709 GO\uFF0C\u672C\u5730\u56FE\u5C42\u8DDF\u968F\u6E32\u67D3\uFF09\n\t}\n\n\tvoid CreateCamera()\n\t{\n\t\t// \u9632\u5FA1\uFF1A\u70ED\u91CD\u8F7D/\u91CD\u5165\u53EF\u80FD\u6B8B\u7559\u65E7\u76F8\u673A\u2014\u2014\u4E24\u4E2A IsMainCamera \u4F1A\u62A2\u6E32\u67D3\uFF0C\u800C\u65E7\u7684\u90A3\u4E2A\n\t\t// \u59FF\u6001\u5DF2\u6B7B\uFF08NeonCamera \u7F13\u5B58\u7684 _target \u5931\u6548\u65E9\u9000\uFF09\uFF0C\u6E32\u67D3\u9009\u4E2D\u5B83\u5C31\u662F\u753B\u9762\u50F5\u6B7B\u4E0D\u8DDF\u968F\n\t\tforeach ( var c in Scene.GetAllComponents\u003CCameraComponent\u003E().ToArray() )\n\t\t{\n\t\t\tif ( c.IsValid() \u0026\u0026 c.GameObject.IsValid() )\n\t\t\t{\n\t\t\t\tGameLog.Info( \u0022[game] destroying stale camera\u0022 );\n\t\t\t\tc.GameObject.Destroy();\n\t\t\t}\n\t\t}\n\n\t\tvar go = new GameObject( true, \u0022Camera\u0022 );\n\t\tgo.Parent = _root;\n\n\t\tvar cam = go.AddComponent\u003CCameraComponent\u003E();\n\t\tcam.IsMainCamera = true;\n\t\tcam.Priority = -100;\n\t\tcam.Orthographic = true;\n\t\tcam.OrthographicHeight = GameConfig.CameraBaseView;\n\t\tcam.ClearFlags = ClearFlags.All;\n\t\tcam.BackgroundColor = new Color( 0.012f, 0.020f, 0.050f );   // \u8FD1\u9ED1\u6DF1\u84DD\uFF0C\u886C\u6258\u9713\u8679\n\t\tcam.EnablePostProcessing = true;\n\n\t\tvar bloom = go.AddComponent\u003CBloom\u003E();\n\t\tbloom.Mode = SceneCamera.BloomAccessor.BloomMode.Additive;\n\t\tbloom.Strength = 1.5f;      // v0.7.8.15 \u7528\u6237\u53CD\u9988\u8C03\u67D4\uFF08\u539F 2.6/0.30 \u5934\u50CF\u7167\u7247\u4EAE\u90E8\u6CDB\u5149\u523A\u773C\uFF09\n\t\tbloom.Threshold = 0.55f;    // \u62AC\u9AD8\u9608\u503C\uFF1A\u5934\u50CF\u666E\u901A\u4EAE\u5EA6\u4E0D\u518D\u53D1\u5149\uFF0C\u9713\u8679\u4EAE\u7EBF\u4FDD\u7559\u5149\u6655\n\n\t\tvar tonemapping = go.AddComponent\u003CTonemapping\u003E();\n\t\ttonemapping.Mode = Tonemapping.TonemappingMode.ACES;\n\n\t\t_camera = go.AddComponent\u003CNeonCamera\u003E();\n\t\t_camera.Cam = cam;\n\t}\n\n\tvoid CreateHud()\n\t{\n\t\tvar go = new GameObject( true, \u0022Hud\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CScreenPanel\u003E();\n\t\t_hud = go.AddComponent\u003CGameHud\u003E();\n\n\t\t// \u7ED3\u7B97\u9762\u677F\uFF08M5\uFF09\uFF1A\u72EC\u7ACB ScreenPanel \u53E0\u5728 HUD \u4E4B\u4E0A\uFF0C\u5E73\u65F6\u9690\u85CF\n\t\tvar ego = new GameObject( true, \u0022EndBoard\u0022 );\n\t\tego.Parent = _root;\n\t\tego.AddComponent\u003CScreenPanel\u003E();\n\t\t_endBoard = ego.AddComponent\u003CEndBoard\u003E();\n\t}\n\n\t/// \u003Csummary\u003E \u4E3B\u83DC\u5355\uFF08\u5F00\u5C40\u540E\u7531 StartGame \u7981\u7528\u6574\u4E2A GameObject\uFF09 \u003C/summary\u003E\n\tvoid CreateMenu()\n\t{\n\t\tvar go = new GameObject( true, \u0022MainMenu\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CScreenPanel\u003E();\n\t\t_menu = go.AddComponent\u003CMainMenu\u003E();\n\t}\n\n\t/// \u003Csummary\u003E \u6E38\u620F\u623F\u95F4\uFF08M5 \u4E8C\u7EA7\u754C\u9762\uFF09\uFF1AHOST/VS BOTS \u5F00\u623F\u540E\u663E\u793A\uFF0CSTART/\u8FDB\u5C40\u540E\u9690\u85CF \u003C/summary\u003E\n\tvoid CreateLobbyPanel()\n\t{\n\t\tvar go = new GameObject( true, \u0022LobbyPanel\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CScreenPanel\u003E();\n\t\t_lobby = go.AddComponent\u003CLobbyPanel\u003E();\n\t}\n\n\t/// \u003Csummary\u003E \u8BBE\u7F6E\u9875\uFF08v0.7.8.16 \u4E8C\u7EA7\u754C\u9762\uFF09\uFF1A\u4E3B\u83DC\u5355 [4] \u8FDB\u5165\u2014\u2014\u6CDB\u5149\u5F00\u5173/\u97F3\u4E50\u97F3\u91CF\uFF0CBACK \u56DE\u83DC\u5355 \u003C/summary\u003E\n\tvoid CreateSettingsPanel()\n\t{\n\t\tvar go = new GameObject( true, \u0022SettingsPanel\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CScreenPanel\u003E();\n\t\t_settings = go.AddComponent\u003CSettingsPanel\u003E();\n\t}\n\n\t/// \u003Csummary\u003E \u6392\u884C\u9875\uFF08v0.7.8.19 \u4E8C\u7EA7\u754C\u9762\uFF09\uFF1A\u4E3B\u83DC\u5355 [5] \u8FDB\u5165\u2014\u2014\u5168\u7403 XP \u699C\uFF0CBACK \u56DE\u83DC\u5355 \u003C/summary\u003E\n\tvoid CreateRankingsPanel()\n\t{\n\t\tvar go = new GameObject( true, \u0022RankingsPanel\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CScreenPanel\u003E();\n\t\t_rankings = go.AddComponent\u003CRankingsPanel\u003E();\n\t}\n\n\t/// \u003Csummary\u003E \u623F\u95F4\u5217\u8868\u9875\uFF08v0.6.7.0\uFF09\uFF1A\u4E3B\u83DC\u5355 [2] JOIN GAME \u6253\u5F00\uFF0C\u5217\u51FA Steam \u516C\u5F00\u5927\u5385 \u003C/summary\u003E\n\tvoid CreateRoomBrowser()\n\t{\n\t\tvar go = new GameObject( true, \u0022RoomBrowser\u0022 );\n\t\tgo.Parent = _root;\n\t\tgo.AddComponent\u003CScreenPanel\u003E();\n\t\t_browser = go.AddComponent\u003CRoomBrowser\u003E();\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u751F\u6210\u4E00\u9897\u7403\u3002M0 \u76F4\u63A5\u4EE3\u7801\u5EFA\u5B9E\u4F53\uFF1BM2 \u8D77\u771F\u4EBA\u7403\u4F18\u5148\u7528 Ball \u9884\u5236\u4F53\n\t/// \uFF08\u7528\u6237\u5728\u7F16\u8F91\u5668\u521B\u5EFA\uFF0C\u4EE3\u7801\u6309\u8DEF\u5F84 Clone\uFF0C\u7F3A\u5931\u65F6\u56DE\u9000\u4EE3\u7801\u6784\u5EFA\uFF09\u3002\n\t/// \u003C/summary\u003E\n\t/// \u003Csummary\u003E\n\t/// \u751F\u6210\u4E00\u9897\u7403\uFF08\u4EC5\u6743\u5A01\u7AEF\u8C03\u7528\uFF09\uFF1A\u4F18\u5148\u514B\u9686\u7528\u6237\u521B\u5EFA\u7684 ball.prefab\uFF08\u6839\u8282\u70B9\u9700\u6302 Ball\uFF09\uFF0C\n\t/// \u7F3A\u5931\u56DE\u9000\u7EAF\u4EE3\u7801\u6784\u5EFA\u3002owner=null \u2192 host \u62E5\u6709\uFF08\u623F\u4E3B\u81EA\u5DF1/bot\uFF09\uFF1B\n\t/// \u4F20\u8FDE\u63A5 \u2192 \u8BE5\u5BA2\u6237\u7AEF\u62E5\u6709\uFF0C\u79FB\u52A8\u7531\u5176\u672C\u5730\u6A21\u62DF\uFF08owner \u6A21\u62DF\u67B6\u6784\uFF09\u3002\n\t/// dormant=true\uFF1A\u623F\u95F4\u86F0\u4F0F\u7403\uFF08Alive=false \u4E0D\u53EF\u89C1\uFF0C\u5F00\u5C40\u7531 StartMatchFromLobby \u6FC0\u6D3B\uFF09\u2014\u2014\n\t/// \u5F15\u64CE\u0022\u8FDB\u884C\u4E2D NetworkSpawn \u4E0D\u590D\u5236\u5230\u5DF2\u8FDE\u63A5\u5BA2\u6237\u7AEF\u0022\uFF0C\u7403\u5FC5\u987B\u5728\u5404\u7AEF\u5FEB\u7167\u524D\u5B58\u5728\u3002\n\t/// \u003C/summary\u003E\n\tpublic Ball SpawnBall( Vector3 position, bool isBot, Connection owner = null, bool dormant = false )\n\t{\n\t\t// \u7ED9\u8FDC\u7A0B\u8FDE\u63A5\u751F\u6210\u7684\u7403\u5FC5\u987B\u7F51\u7EDC\u5316\u624D\u6709\u610F\u4E49\uFF1A\u4F1A\u8BDD\u672A\u6FC0\u6D3B\u65F6 NetworkSpawn \u4F1A\u88AB\u8DF3\u8FC7\uFF0C\n\t\t// \u7403\u6CA6\u4E3A host \u672C\u5730\u5E7D\u7075\u7403\uFF08\u4E0D\u5E7F\u64AD \u2192 \u5BA2\u6237\u7AEF\u6536\u4E0D\u5230\uFF1Bproxy=false \u2192 \u5E72\u6270\u672C\u673A\u7403\u5224\u5B9A\uFF09\u2014\u2014\u5B9E\u6D4B\u5927\u5751\u3002\n\t\t// OnActive \u7ADE\u6001\uFF08\u5927\u5385\u91CD\u5EFA\u4E2D\u89E6\u53D1\u56DE\u8C03\uFF09\u5C31\u843D\u5728\u8FD9\u91CC\uFF0C\u76F4\u63A5\u62D2\u7EDD\u751F\u6210\u3002\n\t\tif ( owner is not null \u0026\u0026 !Networking.IsActive )\n\t\t{\n\t\t\tLog.Warning( \u0022[game] SpawnBall: session not active, refuse ghost ball for remote owner\u0022 );\n\t\t\treturn null;\n\t\t}\n\n\t\tGameObject go = null;\n\t\tBall ball = null;\n\n\t\tvar prefab = PrefabFile.Load( GameConfig.BallPrefabPath );\n\t\tif ( prefab is not null )\n\t\t{\n\t\t\tgo = GameObject.Clone( GameConfig.BallPrefabPath, new Transform( position ), _root );\n\t\t\tball = go.Components.Get\u003CBall\u003E();\n\t\t\tif ( ball is null )\n\t\t\t{\n\t\t\t\tgo.Destroy();   // \u9884\u5236\u4F53\u6839\u8282\u70B9\u6CA1\u6302 Ball\uFF0C\u89C6\u4E3A\u65E0\u6548\u914D\u7F6E\uFF0C\u56DE\u9000\u4EE3\u7801\u6784\u5EFA\n\t\t\t\tgo = null;\n\t\t\t}\n\t\t}\n\n\t\tif ( go is null )\n\t\t{\n\t\t\tgo = new GameObject( true, isBot ? \u0022Bot\u0022 : \u0022Player\u0022 );\n\t\t\tgo.Parent = _root;\n\t\t\tgo.WorldPosition = position;\n\t\t\tball = go.AddComponent\u003CBall\u003E();\n\t\t}\n\n\t\tball.PlayerName = isBot ? NextBotName() : ( owner?.DisplayName ?? LocalPlayerName() );\n\t\tball.OwnerSteamId = owner?.SteamId.Value ?? ( isBot ? NextBotSteamId() : Game.SteamId.Value );\n\t\tball.IsRemoteOwned = owner is not null;   // host \u4FA7\uFF1A\u8FDC\u7A0B\u8FDE\u63A5\u7684\u7403\u4E0D\u53C2\u4E0E\u672C\u673A\u7403\u5224\u5B9A\n\t\t// \u961F\u4F0D\u5206\u914D\uFF1A\u86F0\u4F0F\u7403\uFF08\u623F\u95F4\u9636\u6BB5\uFF09\u4E0D\u5360\u961F\u53F7\uFF0C\u5F00\u5C40\u6FC0\u6D3B\u65F6\u7EDF\u4E00\u6309\u5757\u5206\u961F\uFF1B\n\t\t// \u5BF9\u5C40\u4E2D\u9014\u52A0\u5165\u7684\u7403\uFF08dormant=false \u4E14\u6BD4\u8D5B\u5DF2\u5F00\u59CB\uFF09\u8865\u5206\u5230\u4E0B\u4E00\u4E2A\u5757\uFF08\u56E2\u961F\u8D5B\uFF0Cv0.6.5.0\uFF09\n\t\tball.Init( isBot, !dormant \u0026\u0026 _gameStarted \u0026\u0026 MatchState.IsTeam ? NextTeamIndex() : -1 );\n\t\tball.Dormant = dormant;\n\t\tif ( dormant ) ball.Alive = false;   // \u5FC5\u987B\u5728 NetworkSpawn \u4E4B\u524D\u7F6E false\uFF0C\u968F\u521D\u59CB\u5FEB\u7167\u4E0B\u53D1\n\n\t\tif ( isBot ) go.AddComponent\u003CBotBrain\u003E();\n\n\t\t// host \u4FA7\u76F4\u63A5\u6CE8\u518C\uFF08Ball.OnStart \u5728\u7F51\u7EDC\u5BF9\u8C61\u4E0A\u4E0D\u89E6\u53D1\uFF0C\u89C1 ScanBalls \u515C\u5E95\u6CE8\u91CA\uFF09\n\t\t_balls.Add( ball );\n\t\tif ( !isBot \u0026\u0026 owner is null )\n\t\t\tSetLocalBall( ball );\n\n\t\tgo.NetworkMode = NetworkMode.Object;\n\t\tif ( Networking.IsActive )\n\t\t\tgo.NetworkSpawn( owner );\n\n\t\treturn ball;\n\t}\n\n\t/// \u003Csummary\u003E \u53D6\u4E00\u4E2A\u672A\u88AB\u5360\u7528\u7684 bot \u540D\uFF08\u4ECE\u6E38\u6807\u8D77\u626B\uFF0C\u8DF3\u8FC7\u573A\u4E0A\u4EFB\u4F55\u7403\u2014\u2014\u542B\u86F0\u4F0F/\u5C38\u4F53\u2014\u2014\u6B63\u7528\u7740\u7684\uFF1B\n\t/// 48 \u6EE1\u7F16\u5C0F\u4E8E 64 \u540D\u5B57\u6C60\uFF0C\u6B63\u5E38\u6C38\u8FDC\u53D6\u5F97\u5230\uFF1B\u6C60\u5B50\u771F\u88AB\u5360\u6EE1\u624D\u5FAA\u73AF\u590D\u7528\u515C\u5E95\uFF09 \u003C/summary\u003E\n\tstring NextBotName()\n\t{\n\t\tfor ( int i = 0; i \u003C GameConfig.BotNames.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar name = GameConfig.BotNames[( _botNameIndex \u002B i ) % GameConfig.BotNames.Length];\n\t\t\tif ( FindBallByPlayerName( name ) is null )\n\t\t\t{\n\t\t\t\t_botNameIndex \u002B= i \u002B 1;\n\t\t\t\treturn name;\n\t\t\t}\n\t\t}\n\t\treturn GameConfig.BotNames[_botNameIndex\u002B\u002B % GameConfig.BotNames.Length];\n\t}\n\n\t/// \u003Csummary\u003E \u6309\u540D\u5B57\u627E\u7403\uFF08bot \u53D6\u540D\u67E5\u91CD\u7528\uFF09 \u003C/summary\u003E\n\tpublic Ball FindBallByPlayerName( string name )\n\t{\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( b.IsValid() \u0026\u0026 b.PlayerName == name ) return b;\n\t\t}\n\t\treturn null;\n\t}\n\n\tint _botSeq;   // bot \u72EC\u7ACB\u5047 SteamId \u5E8F\u5217\uFF08\u914D\u5408 BotSteamIdBase\uFF09\n\n\t/// \u003Csummary\u003E \u5206\u914D\u4E00\u4E2A\u672A\u88AB\u5360\u7528\u7684 bot \u5047 SteamId\uFF08\u70ED\u91CD\u8F7D\u4F1A\u91CD\u7F6E _botSeq\uFF0C\u626B\u63CF\u67E5\u91CD\u9632\u78B0\u649E\uFF09 \u003C/summary\u003E\n\tlong NextBotSteamId()\n\t{\n\t\tvar id = GameConfig.BotSteamIdBase \u002B _botSeq\u002B\u002B;\n\t\twhile ( FindBallBySteamId( id ) is not null )\n\t\t\tid = GameConfig.BotSteamIdBase \u002B _botSeq\u002B\u002B;\n\t\treturn id;\n\t}\n\n\t/// \u003Csummary\u003E \u6309 SteamId \u627E\u7403\uFF08\u5206\u8EAB\u5934\u50CF/\u540D\u724C\u5F52\u5C5E\u89E3\u6790\u7528\uFF1Bbot \u73B0\u5728\u5404\u6709\u72EC\u7ACB\u5047 SteamId\uFF09 \u003C/summary\u003E\n\tpublic Ball FindBallBySteamId( long steamId )\n\t{\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( b.IsValid() \u0026\u0026 b.OwnerSteamId == steamId ) return b;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/// \u003Csummary\u003E \u5168\u573A\u603B\u8D28\u91CF\uFF08\u4E3B\u7403\u002B\u5206\u8EAB\uFF09\u6700\u9AD8\u7684\u6D3B\u7403\u2014\u2014\u83DC\u5355\u6F14\u793A\u8D5B\u89C2\u6218\u955C\u5934/\u6B7B\u4EA1\u89C2\u6218\u7528 \u003C/summary\u003E\n\tpublic Ball TopMassBall()\n\t{\n\t\tBall best = null;\n\t\tfloat bestMass = -1f;\n\t\tforeach ( var b in _balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.Alive ) continue;\n\n\t\t\tvar m = TotalMassFor( b );\n\t\t\tif ( m \u003C= bestMass ) continue;\n\n\t\t\tbestMass = m;\n\t\t\tbest = b;\n\t\t}\n\t\treturn best;\n\t}\n\n\t/// \u003Csummary\u003E \u7403\u9500\u6BC1\u65F6\u7531 Ball.OnDestroy \u56DE\u8C03\u6CE8\u9500 \u003C/summary\u003E\n\tpublic void RemoveBall( Ball ball )\n\t{\n\t\t_balls.Remove( ball );\n\t\t_lastPos.Remove( ball.Id );\n\t\tif ( LocalBall == ball ) LocalBall = null;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u7ED3\u7B97\u7ED3\u675F\uFF08\u81EA\u52A8 10 \u79D2 / \u7A7A\u683C\u63D0\u524D\uFF09\uFF1A\u591A\u4EBA\u56DE **host \u7684\u623F\u95F4**\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF0C\u7B49\u4E0B\u4E00\u5C40\uFF09\uFF0C\n\t/// \u5355\u673A\u56DE\u4E3B\u83DC\u5355\u3002\n\t/// \u003C/summary\u003E\n\tpublic void AfterSettlement()\n\t{\n\t\tif ( Networking.IsActive )\n\t\t\tResetToLobby();\n\t\telse\n\t\t\tResetToMenu();\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u56DE\u5230\u623F\u95F4\uFF08\u591A\u4EBA\uFF0Cv0.6.4.2 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A**\u4F1A\u8BDD\u4FDD\u7559\u4E0D\u6E05**\u2014\u2014host \u56DE\u623F\u95F4\u63A7\u5236\u7AEF\n\t/// \uFF08\u53EF\u8C03\u53C2\u3001\u518D START \u5F00\u4E0B\u4E00\u5C40\uFF09\uFF0C\u5BA2\u6237\u7AEF\u56DE\u623F\u95F4\u7B49\u5F85\u9875\uFF08RequestMatchState \u62C9\u73A9\u5BB6\u5217\u8868\uFF09\u3002\n\t/// \u4E0A\u4E00\u5C40\u7684\u7403\u7559\u7740\u5F53\u80CC\u666F\uFF08\u4E0B\u5C40 START \u65F6\u7EDF\u4E00\u9500\u6BC1\u91CD\u5EFA\uFF09\uFF0C\u6A21\u62DF\u5DF2\u51BB\u7ED3\u4E0D\u4F1A\u7EE7\u7EED\u5403\u3002\n\t/// \u003C/summary\u003E\n\tpublic void ResetToLobby()\n\t{\n\t\tMatchState.Reset();\n\t\t_matchRunning = false;\n\t\t_joinCountdown = -1f;\n\t\t_gameStarted = false;\n\n\t\t_endBoard?.Hide();\n\t\tif ( _hud.IsValid() ) _hud.GameObject.Enabled = false;\n\t\t_menu?.Hide();\n\n\t\tif ( NetworkManager.IsAuthority )\n\t\t{\n\t\t\t_lobbyOpen = true;\n\t\t\t_lobby?.ShowHost();\n\t\t\t_net?.ResetSession();\n\t\t\tMarkLobbyDirty();   // \u5E7F\u64AD\u623F\u95F4\u73A9\u5BB6\u5217\u8868\n\t\t}\n\t\telse\n\t\t{\n\t\t\tEnterClientWaiting();\n\t\t\t_browser?.Hide();\n\t\t\t_lobby?.ShowClient();\n\t\t\tNetworkManager.RequestMatchState();\n\t\t}\n\n\t\tGameMusic.PlayMenu();\n\t\tGameLog.Info( $\u0022[match] back to lobby (auth={NetworkManager.IsAuthority})\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u7ED3\u7B97\u540E\u56DE\u4E3B\u83DC\u5355\uFF08M5\uFF09\uFF1A\u65AD\u7F51\u3001\u6E05\u573A\uFF08\u7403\u9500\u6BC1/\u5206\u8EAB\u6E05\u7A7A/\u6BD4\u8D5B\u6001\u590D\u4F4D\uFF09\u3001\u83DC\u5355\u91CD\u51FA\uFF1B\n\t/// \u4E16\u754C\u58F3\uFF08\u7F51\u683C/\u76F8\u673A\uFF09\u7559\u7740\u5F53\u83DC\u5355\u80CC\u666F\uFF0C\u4E0B\u6B21\u5F00\u5C40\u6CBF\u7528\uFF08StartGame \u590D\u7528 HUD\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tpublic void ResetToMenu()\n\t{\n\t\ttry\n\t\t{\n\t\t\tif ( Networking.IsActive ) Networking.Disconnect();\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\t// \u65AD\u4E0D\u5F00\u4E5F\u65E0\u59A8\uFF1Ahost \u4FA7\u4E0B\u5C40\u6CBF\u7528\u65E7\u4F1A\u8BDD\u7167\u5E38\u80FD\u5F00\n\t\t}\n\n\t\tforeach ( var b in _balls.ToArray() )\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tif ( b.IsValid() ) b.GameObject.Destroy();\n\t\t\t}\n\t\t\tcatch ( Exception e )\n\t\t\t{\n\t\t\t\t// \u5BA2\u6237\u7AEF\u9500\u6BC1 host \u62E5\u6709\u7684\u5BF9\u8C61\u53EF\u80FD\u88AB\u5F15\u64CE\u62D2\u7EDD\u2014\u2014\u4E0D\u80FD\u8BA9\u5B83\u4E2D\u65AD\u56DE\u83DC\u5355\u6D41\u7A0B\uFF08\u5B9E\u6D4B\u8FC7\u5361\u4F4F\u4E0D\u8FD4\u56DE\uFF09\n\t\t\t\tLog.Warning( $\u0022[menu] destroy ball failed: {e.Message}\u0022 );\n\t\t\t}\n\t\t}\n\t\t_balls.Clear();\n\t\t_cells.Clear();\n\t\t_lastPos.Clear();\n\t\t_steerDir.Clear();\n\t\t_sinceEject.Clear();\n\t\t_power?.Reset();\n\t\t_teamSeq = 0;\n\t\tLocalBall = null;\n\n\t\t_gameStarted = false;\n\t\t_lobbyOpen = false;\n\t\t_lobbyWaiting = false;\n\t\t_lobbyHeartbeatArmed = false;\n\t\t_lobbyListDirty = false;\n\t\t_joinRequested = false;\n\t\t_networkReady = false;\n\t\t_joinCountdown = -1f;\n\t\t_matchRunning = false;\n\t\tMatchState.Reset();\n\n\t\t_net?.ResetSession();\n\t\t_lobby?.Hide();\n\t\t_browser?.Hide();\n\t\t_endBoard?.Hide();\n\t\tif ( _hud.IsValid() ) _hud.GameObject.Enabled = false;\n\t\t_menu?.Show();\n\t\tGameMusic.PlayMenu();\n\t\tGameLog.Info( \u0022[menu] back to menu\u0022 );\n\n\t\t// \u83DC\u5355\u80CC\u666F\u6F14\u793A\u8D5B\u91CD\u65B0\u5F00\u8D77\u6765\n\t\tif ( NetworkManager.IsAuthority \u0026\u0026 !Networking.IsActive )\n\t\t\tStartMenuDemo();\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"Rendering/NeonCamera.cs","FileName":"NeonCamera.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// \u4FEF\u89C6\u8DDF\u968F\u76F8\u673A\uFF1A\u6B63\u4EA4\u6295\u5F71\u3001\u6307\u6570\u5E73\u6ED1\u8DDF\u968F\u672C\u5730\u7403\uFF1B\u89C6\u53E3\u9AD8\u5EA6\u968F\u8D28\u91CF\u589E\u5927\uFF08\u955C\u5934\u62C9\u8FDC\uFF09\u3002\n/// \u76F8\u673A\u671D\u5411\u56FA\u5B9A\u5782\u76F4\u5411\u4E0B\uFF0C\u9F20\u6807\u2192\u4E16\u754C\u65B9\u5411\u7684\u6620\u5C04\u89C1 BallInput\u3002\n/// \u003C/summary\u003E\npublic sealed class NeonCamera : Component\n{\n\tconst float FollowSpeed = 6f;\n\tconst float ZoomSpeed = 2.5f;\n\n\t/// \u003Csummary\u003E \u7531 CircleroyaleGame \u6CE8\u5165\uFF08\u907F\u514D\u6BCF\u5E27 GetComponent\uFF09 \u003C/summary\u003E\n\tpublic CameraComponent Cam { get; set; }\n\n\tBall _target;\n\tBall _spectate;      // \u6B7B\u4EA1\u89C2\u6218\u76EE\u6807\uFF08\u672C\u5730\u7403\u6D3B\u7740\u65F6\u6052\u4E3A null\uFF09\n\tVector3 _focus;\n\tfloat _shakeAmp;     // \u5C4F\u5E55\u9707\u52A8\u5E45\u5EA6\uFF08\u51FB\u6740/\u653E\u6280\u80FD\u89E6\u53D1\uFF0C\u6307\u6570\u8870\u51CF\uFF0Cv0.7.7.0\uFF09\n\tBloom _bloom;        // \u6CDB\u5149\u7EC4\u4EF6\uFF08cr_bloom convar \u5373\u65F6\u5F00\u5173\uFF0Cv0.7.8.15\uFF09\n\n\t/// \u003Csummary\u003E \u89E6\u53D1\u5C4F\u5E55\u8F7B\u5FAE\u9707\u52A8\uFF08\u51FB\u6740\u8FDE\u64AD/\u653E\u6280\u80FD\u7684\u9AD8\u5149\u53CD\u9988\uFF1B\u5E45\u5EA6\u6309\u4E8B\u4EF6\u5B9A\uFF0C\u81EA\u52A8\u6307\u6570\u8870\u51CF\uFF09 \u003C/summary\u003E\n\tpublic void Shake( float amplitude ) =\u003E _shakeAmp = MathF.Max( _shakeAmp, amplitude );\n\n\tprotected override void OnStart()\n\t{\n\t\tbase.OnStart();\n\n\t\t// \u7ACB\u5373\u6446\u597D\u9ED8\u8BA4\u4FEF\u89C6\u59FF\u6001\uFF08\u7126\u70B9=\u573A\u5730\u4E2D\u5FC3\uFF09\u3002\u6CA1\u6709\u672C\u673A\u7403\u4E4B\u524D OnUpdate \u4F1A\u56E0\n\t\t// !_target.IsValid() \u76F4\u63A5 return\u2014\u2014\u76F8\u673A\u4FDD\u6301\u5F15\u64CE\u9ED8\u8BA4\u59FF\u6001\uFF08\u539F\u70B9\u6C34\u5E73\u770B\uFF09\uFF0C\n\t\t// \u6574\u4E2A\u573A\u5730\u5E73\u9762\u4FA7\u89C6\u584C\u6210\u4E00\u6761\u7EBF\uFF1A\u4E3B\u83DC\u5355\u80CC\u666F\u5B9E\u6D4B\u5C31\u662F\u8FD9\u6837\uFF08\u5BA2\u6237\u7AEF\u7ADE\u6001\u671F\u540C\u6837\u4E2D\u62DB\uFF09\n\t\tApplyPose();\n\t\tif ( Cam.IsValid() )\n\t\t{\n\t\t\tCam.OrthographicHeight = GameConfig.CameraBaseView;\n\t\t\t_bloom = Cam.GetComponent\u003CBloom\u003E();\n\t\t}\n\t}\n\n\tpublic void SetTarget( Ball ball )\n\t{\n\t\tif ( _target == ball ) return;   // \u540C\u4E00\u9897\u7403\uFF1A\u4E0D\u91CD\u590D SnapTo\uFF08\u626B\u63CF\u4F1A\u53CD\u590D\u65AD\u8A00\uFF09\n\t\t_target = ball;\n\t\tSnapTo();\n\t}\n\n\tpublic void SnapTo()\n\t{\n\t\tif ( !_target.IsValid() ) return;\n\t\t_focus = _target.WorldPosition;\n\t\t_focus.z = 0f;\n\t\tApplyPose();\n\t\tif ( Cam.IsValid() ) Cam.OrthographicHeight = ViewForMass( _target.Mass );\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\t// \u6CDB\u5149\u5F00\u5173\uFF08v0.7.8.15\uFF09\uFF1A\u63A7\u5236\u53F0 cr_bloom 0/1 \u5373\u65F6\u751F\u6548\uFF08\u5E42\u7B49\u8D4B\u503C\uFF0C\u65E0\u6296\u52A8\uFF09\uFF1B\n\t\t// \u70ED\u91CD\u8F7D\u6362\u7A0B\u5E8F\u96C6\u540E\u7F13\u5B58\u5931\u6548 \u2192 \u60F0\u6027\u91CD\u53D6\n\t\tif ( Cam.IsValid() )\n\t\t{\n\t\t\tif ( !_bloom.IsValid() ) _bloom = Cam.GetComponent\u003CBloom\u003E();\n\t\t\tif ( _bloom.IsValid() ) _bloom.Enabled = GameConfig.ConvarBloom;\n\t\t}\n\n\t\t// \u81EA\u6108\uFF1A\u70ED\u91CD\u8F7D\u4F1A\u628A\u573A\u666F\u7EC4\u4EF6\u6362\u6210\u65B0\u7A0B\u5E8F\u96C6\u5BF9\u8C61\uFF0C\u7F13\u5B58\u7684 _target \u53EF\u80FD\u53D8\u6210\u5931\u6548\u5F15\u7528\u2014\u2014\n\t\t// OnUpdate \u9996\u884C\u65E9\u9000\u4F1A\u8BA9\u76F8\u673A\u6C38\u4E45\u51BB\u7ED3\uFF08\u5B9E\u6D4B\uFF1A\u5BA2\u6237\u7AEF\u590D\u6D3B\u7740\u3001\u5206\u88C2\u7740\uFF0C\u955C\u5934\u50F5\u6B7B\u4E0D\u8DDF\uFF09\u3002\n\t\t// \u4ECE\u6E38\u620F\u7CFB\u7EDF\u91CD\u65B0\u8BA4\u9886\u672C\u673A\u7403\uFF1B\u8FDE\u672C\u673A\u7403\u90FD\u6CA1\u6709\u4E5F\u4FDD\u6301\u4FEF\u89C6\u59FF\u6001\uFF0C\u7EDD\u4E0D\u50F5\u6B7B\n\t\tif ( !_target.IsValid() )\n\t\t{\n\t\t\tvar local = CircleroyaleGame.Current?.LocalBall;\n\t\t\tif ( local.IsValid() )\n\t\t\t{\n\t\t\t\tSetTarget( local );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// \u83DC\u5355\u80CC\u666F\u6F14\u793A\u8D5B\uFF08v0.6.3.0\uFF09\uFF1A\u6CA1\u6709\u672C\u673A\u7403\u65F6\u89C2\u6218\u5168\u573A\u6700\u5927\u8EAB\u4F53\u7684 bot\u3002\n\t\t\t\t// \u7C98\u6EDE\u540C\u6B7B\u4EA1\u89C2\u6218\uFF1A\u5F53\u524D\u5BF9\u8C61\u8FD8\u6D3B\u7740\u4E14\u6311\u6218\u8005\u6CA1\u9886\u5148 30% \u2192 \u4E0D\u6362\u955C\u5934\n\t\t\t\tvar g = CircleroyaleGame.Current;\n\t\t\t\tvar best = g?.TopMassBall();\n\n\t\t\t\tif ( _spectate.IsValid() \u0026\u0026 _spectate.Alive\n\t\t\t\t\t\u0026\u0026 best.IsValid() \u0026\u0026 best != _spectate \u0026\u0026 g is not null )\n\t\t\t\t{\n\t\t\t\t\tif ( g.TotalMassFor( best ) \u003C= g.TotalMassFor( _spectate ) * GameConfig.SpectateOvertakeRatio )\n\t\t\t\t\t\tbest = _spectate;\n\t\t\t\t}\n\n\t\t\t\tif ( best.IsValid() )\n\t\t\t\t{\n\t\t\t\t\t_spectate = best;\n\t\t\t\t\tvar tp = 1f - MathF.Exp( -FollowSpeed * Time.Delta );\n\t\t\t\t\tvar fp = g.FocusPositionOf( best, out var fm );\n\t\t\t\t\t_focus = Vector3.Lerp( _focus, fp, tp );\n\t\t\t\t\t_focus.z = 0f;\n\t\t\t\t\tApplyPose();\n\t\t\t\t\tUpdateTagPositions( g );\n\t\t\t\t\tif ( Cam.IsValid() )\n\t\t\t\t\t{\n\t\t\t\t\t\tCam.OrthographicHeight = MathX.Lerp( Cam.OrthographicHeight, ViewForMass( fm ),\n\t\t\t\t\t\t\t1f - MathF.Exp( -ZoomSpeed * Time.Delta ) );\n\t\t\t\t\t\tUpdateTagPositions( g );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tApplyPose();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// \u6B7B\u4EA1\u89C2\u6218\uFF08M4\uFF09\uFF1A\u672C\u5730\u7403\u9635\u4EA1\u540E\u955C\u5934\u8DDF\u4F4F\u5F53\u524D\u699C\u9996\uFF0C\u91CD\u751F\u77AC\u95F4\u5F39\u56DE\u81EA\u5DF1\n\t\tvar cur = SpectateTarget();\n\n\t\t// \u89C6\u89D2\u8DDF\u968F\u6700\u5927\u7684\u5206\u8EAB\uFF08v0.6.2.0 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u7126\u70B9\u4E0E\u53D8\u7126\u57FA\u51C6\u90FD\u53D6\n\t\t// \u0022\u4E3B\u7403 \u002B \u81EA\u5DF1\u5206\u8EAB\u0022\u4E2D\u8D28\u91CF\u6700\u5927\u7684\u8EAB\u4F53\n\t\tvar game = CircleroyaleGame.Current;\n\t\tVector3 focusPos = cur.WorldPosition;\n\t\tfloat followMass = cur.Mass;\n\t\tif ( game is not null )\n\t\t\tfocusPos = game.FocusPositionOf( cur, out followMass );\n\n\t\tvar t = 1f - MathF.Exp( -FollowSpeed * Time.Delta );\n\t\t_focus = Vector3.Lerp( _focus, focusPos, t );\n\t\t_focus.z = 0f;\n\t\tApplyPose();\n\t\tUpdateTagPositions( game );\n\n\t\tif ( !Cam.IsValid() ) return;\n\t\tvar tz = 1f - MathF.Exp( -ZoomSpeed * Time.Delta );\n\t\tCam.OrthographicHeight = MathX.Lerp( Cam.OrthographicHeight, ViewWithCells( cur, followMass ), tz );\n\t\tUpdateTagPositions( game );   // \u53D8\u7126\u4E5F\u6539\u4E86\u76F8\u673A\u53C2\u6570\uFF0C\u91CD\u6295\u4E00\u6B21\u4FDD\u8BC1\u672C\u5E27\u4E00\u81F4\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u6B7B\u4EA1\u89C2\u6218\u76EE\u6807\uFF1A\u56E2\u961F\u8D5B\u4F18\u5148\u8DDF**\u6700\u5927\u7684\u5B58\u6D3B\u961F\u53CB**\uFF08v0.7.8.28 \u7528\u6237\u5B9A\u7A3F\u2014\u2014\u0022\u6211\u6B7B\u4E86\u961F\u4F0D\u8FD8\u5728\u6253\u0022\uFF0C\n\t/// \u8DDF\u7740\u5927\u54E5\u770B\u6218\u5C40\uFF1B\u961F\u53CB\u5168\u706D\u624D\u56DE\u843D\u5168\u573A\u699C\u9996\uFF09\u3002\u5E26\u7C98\u6EDE\uFF08v0.6.3.1\uFF09\u2014\u2014\u51E0\u4E2A\u76EE\u6807\u4EA4\u66FF\u9886\u5148\u65F6\n\t/// \u4E0D\u518D\u6BCF\u6B21\u53CD\u8D85\u90FD\u5207\u955C\u5934\uFF0C\u6311\u6218\u8005\u987B\u9886\u5148 SpectateOvertakeRatio \u624D\u63A5\u7BA1\u3002\n\t/// \u003C/summary\u003E\n\tBall SpectateTarget()\n\t{\n\t\tif ( _target.Alive )\n\t\t{\n\t\t\tif ( _spectate.IsValid() )\n\t\t\t{\n\t\t\t\t_spectate = null;\n\t\t\t\tSpectateName = null;\n\t\t\t\tSnapTo();   // \u91CD\u751F\uFF1A\u955C\u5934\u77AC\u79FB\u56DE\u81EA\u5DF1\uFF08\u4E0D\u4ECE\u699C\u9996\u4F4D\u7F6E\u957F\u8DDD\u79BB\u6ED1\u8FC7\u6765\uFF09\n\t\t\t}\n\t\t\treturn _target;\n\t\t}\n\n\t\tvar game = CircleroyaleGame.Current;\n\t\tBall best = null;\n\n\t\t// \u56E2\u961F\u8D5B\u961F\u53CB\u4F18\u5148\uFF08\u6700\u5927\u5B58\u6D3B\u961F\u53CB\uFF09\uFF1B\u6CA1\u6709\u624D\u770B\u5168\u573A\u699C\u9996\n\t\tif ( MatchState.IsTeam \u0026\u0026 _target.TeamIndex \u003E= 0 \u0026\u0026 game is not null )\n\t\t{\n\t\t\tfloat bestMass = -1f;\n\t\t\tforeach ( var b in game.Balls )\n\t\t\t{\n\t\t\t\tif ( !b.IsValid() || !b.Alive || b == _target ) continue;\n\t\t\t\tif ( b.TeamIndex != _target.TeamIndex ) continue;\n\t\t\t\tvar m = game.TotalMassFor( b );\n\t\t\t\tif ( m \u003E bestMass ) { bestMass = m; best = b; }\n\t\t\t}\n\t\t}\n\t\tif ( !best.IsValid() )\n\t\t\tbest = game?.TopMassBall();\n\n\t\t// \u7C98\u6EDE\uFF1A\u5F53\u524D\u89C2\u6218\u5BF9\u8C61\u8FD8\u6D3B\u7740\uFF0C\u4E14\u6311\u6218\u8005\u6CA1\u9886\u5148\u5230\u9608\u503C \u2192 \u4E0D\u6362\u955C\u5934\n\t\tif ( _spectate.IsValid() \u0026\u0026 _spectate.Alive\n\t\t\t\u0026\u0026 best.IsValid() \u0026\u0026 best != _spectate \u0026\u0026 game is not null )\n\t\t{\n\t\t\tif ( game.TotalMassFor( best ) \u003C= game.TotalMassFor( _spectate ) * GameConfig.SpectateOvertakeRatio )\n\t\t\t\treturn _spectate;\n\t\t}\n\n\t\t_spectate = best.IsValid() ? best : _target;\n\t\tSpectateName = _spectate.IsValid() \u0026\u0026 _spectate != _target ? _spectate.PlayerName : null;\n\t\treturn _spectate;\n\t}\n\n\t/// \u003Csummary\u003E \u5F53\u524D\u6B7B\u4EA1\u89C2\u6218\u76EE\u6807\u540D\uFF08\u672C\u673A\u6D3B\u7740=null\uFF09\uFF0CHUD \u6B7B\u4EA1\u9762\u677F\u63D0\u793A\u7528 \u003C/summary\u003E\n\tpublic string SpectateName { get; private set; }\n\n\t/// \u003Csummary\u003E\n\t/// \u89C6\u53E3\u9AD8\u5EA6\uFF1A\u6309\u0022\u6700\u5927\u8EAB\u4F53\u0022\u8D28\u91CF\u53D6\u57FA\u51C6\uFF1B\u6709\u81EA\u5BB6\u5206\u8EAB\u65F6\u62C9\u8FDC\u5230\u80FD\u76D6\u4F4F\u6700\u8FDC\u7684\u90A3\u4E2A\n\t/// \uFF08\u5206\u88C2\u540E\u7EC6\u80DE\u51B2\u51FA\u5C4F\u5E55\u5916\u5C31\u770B\u4E0D\u89C1\u4E86\uFF0C\u53D6\u666F\u8981\u628A\u81EA\u5BB6\u5206\u8EAB\u5305\u8FDB\u6765\uFF09\u3002\n\t/// \u5BA2\u6237\u7AEF\u5339\u914D\u7528 OwnerSteamId\uFF1A\u5206\u8EAB\u4E0E\u4E3B\u7403\u5E26\u540C\u4E00\u4E2A\u540C\u6B65\u503C\uFF0C\u53CC\u5F00\u5408\u6210 ID \u4E5F\u80FD\u5BF9\u4E0A\u3002\n\t/// \u003C/summary\u003E\n\tfloat ViewWithCells( Ball cur, float baseMass )\n\t{\n\t\tvar view = ViewForMass( baseMass );\n\n\t\tvar game = CircleroyaleGame.Current;\n\t\tif ( game is null ) return view;\n\n\t\tvar sid = cur.OwnerSteamId;\n\t\tfloat maxDist = 0f;\n\t\tforeach ( var c in game.Cells )\n\t\t{\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece || c.OwnerSteamId != sid ) continue;\n\t\t\tmaxDist = MathF.Max( maxDist, cur.WorldPosition.Distance( c.DrawPos ) );\n\t\t}\n\n\t\tif ( maxDist \u003E 0f )\n\t\t\tview = Math.Clamp( ( cur.Radius \u002B maxDist ) * 2.6f, view, GameConfig.CameraViewMax );\n\n\t\treturn view;\n\t}\n\n\tvoid ApplyPose()\n\t{\n\t\t// \u5C4F\u5E55\u9707\u52A8\uFF08v0.7.7.0\uFF09\uFF1A\u6BCF\u6B21\u6446\u4F4D\u53E0\u52A0\u4E00\u4E2A\u968F\u673A\u65B9\u5411\u504F\u79FB\uFF0C\u5E45\u5EA6\u6307\u6570\u8870\u51CF\u5230\u96F6\u2014\u2014\n\t\t// \u6446\u4F4D\u672C\u8EAB\u9010\u5E27\u91CD\u5199\uFF08\u4E0D\u542B\u6B8B\u91CF\uFF09\uFF0C\u9707\u5B8C\u81EA\u52A8\u5F52\u4F4D\uFF1B\u540D\u724C\u6295\u5F71\u7528\u540C\u4E00\u76F8\u673A\u4F4D\u7F6E\uFF0C\u6296\u52A8\u540C\u6B65\u4E00\u81F4\n\t\tvar shake = Vector3.Zero;\n\t\tif ( _shakeAmp \u003E 0.1f )\n\t\t{\n\t\t\tvar a = Game.Random.Float( 0f, MathF.PI * 2f );\n\t\t\tshake = new Vector3( MathF.Cos( a ), MathF.Sin( a ), 0f )\n\t\t\t\t* _shakeAmp * Game.Random.Float( 0.4f, 1f );\n\t\t\t_shakeAmp *= MathF.Exp( -6f * Time.Delta );\n\t\t}\n\n\t\tGameObject.WorldPosition = _focus \u002B Vector3.Up * GameConfig.CameraHeight \u002B shake;\n\t\tGameObject.WorldRotation = Rotation.LookAt( Vector3.Down, Vector3.Forward );\n\t}\n\n\t/// \u003Csummary\u003E \u76F8\u673A\u59FF\u6001\u521A\u5199\u5B8C\uFF0C\u7ACB\u523B\u8BA9 HUD \u7528\u540C\u4E00\u4EFD\u76F8\u673A\u6570\u636E\u91CD\u6295\u540D\u724C\uFF08\u96F6\u5E27\u95F4\u5DEE\uFF09 \u003C/summary\u003E\n\tvoid UpdateTagPositions( CircleroyaleGame game )\n\t{\n\t\tif ( game is null ) return;\n\t\tvar ortho = Cam.IsValid() ? Cam.OrthographicHeight : 0f;\n\t\tgame.Hud?.UpdateTagPositions( game, GameObject.WorldPosition, ortho );\n\t}\n\n\tstatic float ViewForMass( float mass )\n\t{\n\t\treturn Math.Clamp( GameConfig.CameraBaseView * MathF.Sqrt( mass / GameConfig.StartMass ),\n\t\t\tGameConfig.CameraViewMin, GameConfig.CameraViewMax );\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"Rendering/NeonRenderer.cs","FileName":"NeonRenderer.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// \u9713\u8679\u6279\u6E32\u67D3\uFF1A\u6BCF\u5E27\u91CD\u5199\u4E24\u7EC4\u9876\u70B9\uFF08\u7EBF\u6BB5 / \u4E09\u89D2\u5F62\uFF09\uFF0C\u5168\u573A\u5B9E\u4F53\u4E00\u5E27\u4E24\u4E2A DrawCall\u3002\n/// \u7403\u7684\u6784\u56FE\u6309\u7528\u6237\u8981\u6C42\uFF1A\u003Cb\u003E\u73A9\u5BB6\u5934\u50CF\u4E3A\u5706\u5FC3\uFF0C\u79D1\u5E7B\u7EBF\u6761\u4E3A\u5916\u4FA7\u003C/b\u003E\u2014\u2014\n/// M0 \u5185\u82AF\u7528\u4F4E\u900F\u660E\u5EA6\u8272\u76D8\u5360\u4F4D\uFF0CM3 \u6362\u6210 Steam \u5934\u50CF\u56DB\u8FB9\u5F62\uFF08Texture.LoadAvatar\uFF0Cbot \u7528\u51E0\u4F55\u56FE\u6848\uFF09\u3002\n/// \u003C/summary\u003E\n\tpublic sealed class NeonRenderer : Component\n\t{\n\t\tSceneDynamicObject _lines;\n\t\tSceneDynamicObject _tris;\n\n\t\t/// \u003Csummary\u003E \u5168\u5C40\u5F15\u7528\uFF08\u7279\u6548\u751F\u6210\u5165\u53E3 NeonRenderer.Pop/Spark/Puff \u8D70\u9759\u6001\u8C03\u7528\uFF09 \u003C/summary\u003E\n\t\tpublic static NeonRenderer Instance { get; private set; }\n\n\t\tprotected override void OnStart()\n\t\t{\n\t\t\tbase.OnStart();\n\n\t\t\tInstance = this;\n\t\t\t_lines = CreateBatch();\n\t\t\t_tris = CreateBatch();\n\t\t}\n\n\t\tprotected override void OnDestroy()\n\t\t{\n\t\t\tbase.OnDestroy();\n\t\t\tif ( Instance == this ) Instance = null;\n\t\t}\n\n\tSceneDynamicObject CreateBatch()\n\t{\n\t\tvar dyno = new SceneDynamicObject( Scene.SceneWorld );\n\t\tdyno.Material = Material.FromShader( \u0022shaders/line.shader\u0022 );   // \u9876\u70B9\u8272 \u002B \u52A0\u8272\u6DF7\u5408\n\t\tdyno.Flags.CastShadows = false;\n\t\tdyno.RenderLayer = SceneRenderLayer.OverlayWithDepth;\n\t\tdyno.Attributes.SetCombo( \u0022D_BLEND\u0022, 1 );                       // SrcAlpha/One \u52A0\u8272\uFF0C\u53D1\u5149\u611F\n\t\treturn dyno;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\t\tif ( !Game.IsPlaying ) return;\n\n\t\tvar game = CircleroyaleGame.Current;\n\t\tif ( game is null || _lines is null || _tris is null ) return;\n\n\t\t_lines.Clear();\n\t\t_lines.Init( Graphics.PrimitiveType.Lines );\n\t\t_tris.Clear();\n\t\t_tris.Init( Graphics.PrimitiveType.Triangles );\n\n\t\tUpdateParticles();\n\n\t\t// \u7D2B\u8272\uFF08\u7528\u6237\u5B9A\u7A3F\u7684\u523A\u65CF\u8272\uFF09\uFF1A\u5C16\u523A\u5206\u8EAB/\u91CE\u523A/\u523A\u7206\u5F39\u5E55\u5171\u7528\uFF08v0.7.8.24 \u63D0\u524D\u5230\u65B9\u6CD5\u7EA7\uFF09\n\t\tvar purple = new Color( 0.85f, 0.45f, 1f );\n\n\t\tforeach ( var ball in game.Balls )\n\t\t{\n\t\t\tif ( !ball.IsValid() || !ball.Alive ) continue;\n\t\t\tWriteBall( game, ball );\n\n\t\t\t// \u7784\u51C6\u7BAD\u5934\uFF08v0.7.0.0 \u7528\u6237\u9700\u6C42\uFF09\uFF1A\u672C\u673A\u7403\u5916\u7F18\u5C0F\u4E09\u89D2\u6307\u5411\u5149\u6807\u2014\u2014\u5206\u88C2/\u5410\u5B62\u5B50\u65B9\u5411\u6307\u793A\u3002\n\t\t\t// \u4EC5\u5BF9\u5C40\u4E2D\u663E\u793A\uFF08\u623F\u95F4\u9875\u505C\u6CCA\u7684\u7403\u4E0D\u753B\uFF0C\u7BAD\u5934\u65B9\u5411\u5DF2\u51BB\u7ED3\u6CA1\u6709\u610F\u4E49\uFF09\n\t\t\tif ( game.IsMatchStarted \u0026\u0026 ball == game.LocalBall \u0026\u0026 ball.AimDir.Length \u003E 0.01f )\n\t\t\t\tWriteAimArrow( ball );\n\t\t}\n\n\t\t// \u5206\u8EAB/\u5B62\u5B50\uFF08\u7EAF\u6570\u636E\u5B9E\u4F53\uFF0C\u975E GameObject\uFF09\uFF1A\u5206\u8EAB=\u65E0\u5934\u50CF\u8FF7\u4F60\u7403\uFF0C\u5B62\u5B50=\u5C0F\u76D8\n\t\tforeach ( var c in game.Cells )\n\t\t{\n\t\t\tvar color = GameConfig.Palette[Math.Clamp( c.ColorIndex, 0, GameConfig.Palette.Length - 1 )];\n\t\t\tvar pos = new Vector3( c.DrawPos.x, c.DrawPos.y, 0f );\n\t\t\tvar r = c.Radius;\n\n\t\t\t// \u51FA\u751F\u5F39\u8DF3\uFF08v0.7.8.28 juice\uFF09\uFF1A\u65B0 id \u9996\u5E27\u8BB0\u65F6\u523B\uFF0C0.3s \u5185\u7F29\u653E\u8109\u51B2\uFF080.55\u21921.14\u21921.0\uFF09\u3002\n\t\t\t// \u5BA2\u6237\u7AEF\u955C\u50CF\u4E0D\u5E26 SinceSpawn\uFF08\u5FEB\u7167\u6301\u4E45\u66F4\u65B0\uFF09\uFF0C\u7528\u0022\u9996\u6B21\u89C1\u5230\u8BE5 id\u0022\u5224\u5B9A\uFF0C\u53CC\u7AEF\u7EDF\u4E00\n\t\t\tif ( !_seenCells.Contains( c.Id ) )\n\t\t\t{\n\t\t\t\t_seenCells.Add( c.Id );\n\t\t\t\t_cellBorn[c.Id] = 0;\n\t\t\t\tif ( _seenCells.Count \u003E 512 ) PruneCellFx( game );\n\t\t\t}\n\t\t\tif ( _cellBorn.TryGetValue( c.Id, out var born ) \u0026\u0026 born \u003C 0.3f )\n\t\t\t{\n\t\t\t\tvar phase = born / 0.3f;\n\t\t\t\tr *= phase \u003C 0.3f\n\t\t\t\t\t? MathX.Lerp( 0.55f, 1.14f, phase / 0.3f )\n\t\t\t\t\t: MathX.Lerp( 1.14f, 1f, ( phase - 0.3f ) / 0.7f );\n\t\t\t}\n\n\t\t\tif ( c.PieceKind == CellPiece.Kind.EjectedMass )\n\t\t\t{\n\t\t\t\tAddDisc( _tris, pos, r * 0.85f, color * 0.5f, 0.9f, 10 );\n\t\t\t\tAddRing( _lines, pos, r, color * 0.7f, 14 );\n\t\t\t}\n\t\t\telse if ( c.PieceKind == CellPiece.Kind.SpikeMinion )\n\t\t\t{\n\t\t\t\t// \u5C16\u523A\u5206\u8EAB\uFF08v0.7.4.0\uFF09\uFF1A\u7D2B\u661F\uFF08\u91CE\u523A\u540C\u65CF=\u662F\u523A\uFF09\u002B \u4E3B\u4EBA\u8272\u63CF\u8FB9\u73AF\uFF08=\u6211\u65B9\u6B66\u5668\uFF09\n\t\t\t\t// purple \u7528\u65B9\u6CD5\u7EA7\u58F0\u660E\uFF08v0.7.8.24 \u63D0\u5230\u5C16\u523A\u7ED8\u5236\u6BB5\u4E4B\u524D\uFF0C\u6B64\u5904\u4E0D\u518D\u91CD\u590D\u58F0\u660E\uFF09\n\t\t\t\tWriteStar( pos \u002B Vector3.Up * 0.9f, MathF.Max( r, 40f ), purple, purple * 0.8f );\n\t\t\t\tAddRing( _lines, pos, MathF.Max( r, 40f ) * 1.25f, color * 0.8f, 20 );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tAddDisc( _tris, pos, r * 0.92f, color * 0.32f, 0.9f, GameConfig.DiscSegments );\n\t\t\t\tAddRing( _lines, pos, r, color * 0.9f, GameConfig.RingSegments );\n\t\t\t}\n\t\t}\n\n\t\t// \u5C16\u523A\uFF1A12 \u89D2\u661F\u5F62\uFF08\u649E\u523A\u70B8\u88C2\u7684\u969C\u788D\uFF1B\u5C0F\u7684\u4ECE\u5E95\u4E0B\u7A7F\u8FC7\uFF09\u3002**\u7D2B\u8272\u2014\u2014\u7528\u6237\u5B9A\u7A3F**\uFF1A\n\t\t// \u7EFF\u8272\u9876\u70B9\u8272\u5728\u6B64\u7BA1\u7EBF\u91CC\u5E27\u95F4\u8868\u73B0\u4E0D\u7A33\u5B9A\uFF08\u540C\u4E00\u4EE3\u7801\u4E00\u4F1A\u513F\u7EFF\u4E00\u4F1A\u513F\u54C1\u7EA2\uFF09\uFF0C\u7D2B/\u54C1\u7EA2\u65CF\u59CB\u7EC8\u7A33\u5B9A\n\t\tvar spikes = game.Spikes;\n\t\tif ( spikes is not null )\n\t\t{\n\t\t\tfor ( int i = 0; i \u003C spikes.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\t// \u5582\u523A\u957F\u5927\uFF08v0.7.8.24 \u7403\u7403\u5927\u4F5C\u6218\u5F0F\uFF09\uFF1A\u7D2F\u8BA1\u5582\u98DF\u91CF\u5411 SpikeGrowTarget \u6DA8\u5230 1\u002BSpikeGrowVisual \u500D\n\t\t\t\tvar grow = 1f \u002B GameConfig.SpikeGrowVisual * Math.Clamp( game.SpikeFedAt( i ) / GameConfig.SpikeGrowTarget, 0f, 1f );\n\t\t\t\t// \u5B9E\u5FC3\uFF08\u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u586B\u5145\u62C9\u6EE1\uFF0C\u52A0\u8272\u6DF7\u5408\u4E0B\u661F\u5F62\u5185\u90E8\u5B8C\u6574\u53D1\u4EAE\uFF0C\u4E0D\u518D\u53EA\u662F\u63CF\u8FB9\u002B\u6DE1\u82AF\n\t\t\t\tWriteStar( new Vector3( spikes[i].x, spikes[i].y, 0f ), GameConfig.SpikeRadius * grow, purple, purple * 0.85f );\n\t\t\t}\n\t\t}\n\n\t\t// \u523A\u7206\u5F39\u5E55\uFF08v0.7.8.24\uFF09\uFF1A\u7EC6\u957F\u4E09\u89D2\u5C16\u523A\u6CBF\u901F\u5EA6\u65B9\u5411\u98DE\uFF08host=\u6743\u5A01\u5F39\u5E55\u3001\u5BA2\u6237\u7AEF=\u89C6\u89C9\u955C\u50CF\uFF0C\u540C\u4E00\u5217\u8868\u540C\u753B\u6CD5\uFF09\uFF0C\n\t\t// \u968F\u5269\u4F59\u5BFF\u547D\u6DE1\u51FA\n\t\tforeach ( var sp in game.Spines )\n\t\t{\n\t\t\tvar remain = 1f - Math.Clamp( sp.SinceSpawn / GameConfig.SpineLife, 0f, 1f );\n\t\t\tvar dir = sp.Vel;\n\t\t\tvar len = dir.Length;\n\t\t\tif ( len \u003C 0.001f ) continue;\n\t\t\tdir /= len;\n\t\t\tvar tip = new Vector3( sp.Pos.x \u002B dir.x * 30f, sp.Pos.y \u002B dir.y * 30f, 0f );\n\t\t\tvar b1 = new Vector3( sp.Pos.x - dir.y * 3.5f, sp.Pos.y \u002B dir.x * 3.5f, 0f );\n\t\t\tvar b2 = new Vector3( sp.Pos.x \u002B dir.y * 3.5f, sp.Pos.y - dir.x * 3.5f, 0f );\n\t\t\tAddTri( _tris, b1, b2, tip, purple * ( 0.35f \u002B 0.65f * remain ) );\n\t\t}\n\n\t\t// \u9053\u5177\uFF08v0.7.3.0\uFF09\uFF1A\u65CB\u8F6C\u83F1\u5F62 \u002B \u547C\u5438\u5149\u6655\uFF0C\u79CD\u7C7B\u8272\u533A\u5206\uFF08\u5C3A\u5BF8 26 \u8FDC\u5927\u4E8E\u98DF\u7269 8\uFF0C\u4E00\u773C\u53EF\u8FA8\uFF09\u3002\n\t\t// \u5F62\u72B6\u5FAE\u5DEE\u8F85\u52A9\u8272\u5F31\u533A\u5206\uFF1A\u62A4\u76FE\u53CC\u73AF / \u78C1\u94C1\u5341\u5B57\u82AF / \u8D28\u91CF\u7F50\u5B9E\u5FC3\u5927\u82AF / \u52A0\u901F\u62D6\u5C3E\u5C16\u89D2\n\t\tvar power = game.PowerUps;\n\t\tif ( power is not null \u0026\u0026 power.Ready )\n\t\t{\n\t\t\tvar spin = Time.Now * 1.6f;\n\t\t\tvar breathe = 1f \u002B MathF.Sin( Time.Now * 3f ) * 0.12f;\n\n\t\t\tforeach ( var s in power.Slots )\n\t\t\t{\n\t\t\t\tif ( !s.Alive ) continue;\n\n\t\t\t\tvar pos = new Vector3( s.Pos.x, s.Pos.y, 0f );\n\t\t\t\tvar kindColor = PowerUpManager.ColorOf( s.Kind );\n\t\t\t\tvar r = GameConfig.PowerUpRadius * breathe;\n\n\t\t\t\tWriteDiamond( pos, r, spin, kindColor );\n\n\t\t\t\tswitch ( (PowerUpManager.Kind)s.Kind )\n\t\t\t\t{\n\t\t\t\t\tcase PowerUpManager.Kind.Shield:\n\t\t\t\t\t\tAddRing( _lines, pos, r * 1.45f, kindColor * 0.45f, 18 );   // \u53CC\u73AF=\u9632\u62A4\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase PowerUpManager.Kind.Magnet:\n\t\t\t\t\t\tWriteCross( pos, r * 0.55f, kindColor * 0.9f );             // \u5341\u5B57=\u5438\u9644\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase PowerUpManager.Kind.Mass:\n\t\t\t\t\t\tAddDisc( _tris, pos, r * 0.5f, kindColor * 0.85f, 1.2f, 10 );   // \u5B9E\u5FC3\u5927\u82AF=\u589E\u91CD\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase PowerUpManager.Kind.Speed:\n\t\t\t\t\t\tWriteDiamond( pos \u002B new Vector3( -r * 0.9f, 0f, 0f ), r * 0.45f, spin, kindColor * 0.4f );   // \u62D6\u5C3E=\u51B2\u523A\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase PowerUpManager.Kind.Spike:\n\t\t\t\t\t\tWriteStar( pos \u002B Vector3.Up * 1.4f, r * 0.5f, kindColor, kindColor * 0.6f );   // \u5185\u661F=\u523A\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// \u98DF\u7269\uFF1A\u4F4E\u6A21\u5C0F\u5706\u76D8\uFF08\u9053\u5177 M4\u002B \u6309 Type \u6362\u5F62\u72B6/\u56FE\u6807\uFF09\n\t\tvar foods = game.Food?.Foods;\n\t\tif ( foods is not null )\n\t\t{\n\t\t\t// \u89C6\u53E3\u5254\u9664\uFF08v0.6.3.0\uFF09\uFF1A3 \u500D\u573A\u5730 4140 \u9897\u98DF\u7269\u53EA\u6709 ~20% \u5728\u753B\u9762\u91CC\u2014\u2014\n\t\t\t// \u7528\u76F8\u673A\u4E16\u754C\u5305\u56F4\u76D2\uFF08\u6B63\u4EA4\u534A\u9AD8 \u002B \u7AD6\u5411\u4F59\u91CF\u6309\u5BBD\u9AD8\u6BD4\u6298\u7B97\uFF09\u8DF3\u8FC7\u753B\u9762\u5916\u7684\n\t\t\tvar palette = GameConfig.Palette;\n\t\t\tvar fr = GameConfig.FoodRadius;\n\t\t\tvar cullCx = Scene.Camera.WorldPosition.x;\n\t\t\tvar cullCy = Scene.Camera.WorldPosition.y;\n\t\t\tvar cullHw = Scene.Camera.OrthographicHeight * 0.5f * 2.4f \u002B 64f;   // \u5BBD\u4F59\u91CF\u6309\u6700\u5927\u5BBD\u9AD8\u6BD4\n\t\t\tvar cullHh = Scene.Camera.OrthographicHeight * 0.5f \u002B 64f;\n\n\t\t\tfor ( int i = 0; i \u003C foods.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tif ( !foods[i].Alive ) continue;\n\n\t\t\t\tvar fx = foods[i].Pos.x;\n\t\t\t\tvar fy = foods[i].Pos.y;\n\t\t\t\tif ( MathF.Abs( fx - cullCx ) \u003E cullHw || MathF.Abs( fy - cullCy ) \u003E cullHh ) continue;\n\n\t\t\t\tvar c = palette[foods[i].ColorIndex % palette.Length] * 0.55f;\n\t\t\t\tAddDisc( _tris, new Vector3( fx, fy, 0f ), fr, c, 0.75f, GameConfig.FoodSegments );\n\t\t\t}\n\t\t}\n\n\t\tDrawParticles();\n\t\tDrawEdgeArrows();\n\t}\n\n\t// ---- \u5C4F\u5E55\u8FB9\u7F18\u961F\u53CB\u6307\u793A\u7BAD\u5934\uFF08v0.7.8.12\uFF09\uFF1AGameHud \u540D\u724C\u91CD\u6295\u65F6\u5199\u5165\u8BF7\u6C42\uFF0C\u672C\u7EC4\u4EF6 OnUpdate \u672B\u5C3E\u6D88\u8D39 ----\n\n\tsealed class EdgeArrowReq\n\t{\n\t\tpublic Vector3 Pos;      // \u8D34\u8FB9\u70B9\uFF08\u4E16\u754C\uFF09\n\t\tpublic Vector2 Dir;      // \u6307\u5411\u961F\u53CB\u771F\u5B9E\u65B9\u4F4D\uFF08\u4E16\u754C\uFF0C\u5355\u4F4D\u5411\u91CF\uFF09\n\t\tpublic Color Color;\n\t\tpublic float Size;       // \u4E16\u754C\u5355\u4F4D\uFF08\u6309\u6BCF\u50CF\u7D20\u4E16\u754C\u5355\u4F4D\u6298\u7B97\uFF0C\u4FDD\u6301\u50CF\u7D20\u5927\u5C0F\u6052\u5B9A\uFF09\n\t}\n\n\tstatic readonly List\u003CEdgeArrowReq\u003E _edgeArrows = new();\n\n\t/// \u003Csummary\u003E \u8BF7\u6C42\u4E00\u652F\u8FB9\u7F18\u7BAD\u5934\uFF08\u8D34\u8FB9\u4E16\u754C\u70B9 \u002B \u6307\u5411 \u002B \u989C\u8272\uFF09\u3002OnUpdate \u6D88\u8D39\u540E\u6E05\u7A7A\uFF0C\u665A\u4E00\u5E27\u65E0\u611F \u003C/summary\u003E\n\tpublic static void EdgeArrow( Vector3 pos, Vector2 dir, Color color, float size )\n\t{\n\t\t_edgeArrows.Add( new EdgeArrowReq { Pos = pos, Dir = dir, Color = color, Size = size } );\n\t}\n\n\t/// \u003Csummary\u003E \u5B9E\u5FC3\u4E09\u89D2\u7BAD\u5934 \u002B \u77ED\u5C3E\u6746\uFF0C\u671D\u5411 Dir\uFF1B\u52A0\u8272\u6279\u6B21\u4E0B\u4E0E\u9713\u8679\u98CE\u683C\u4E00\u81F4 \u003C/summary\u003E\n\tvoid DrawEdgeArrows()\n\t{\n\t\tfor ( int i = 0; i \u003C _edgeArrows.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar r = _edgeArrows[i];\n\t\t\tvar d = new Vector3( r.Dir.x, r.Dir.y, 0f );\n\t\t\tvar perp = new Vector3( -r.Dir.y, r.Dir.x, 0f );\n\t\t\tvar len = r.Size;\n\t\t\tvar halfW = len * 0.5f;\n\n\t\t\tvar tip = r.Pos \u002B d * len;\n\t\t\tvar b1 = r.Pos \u002B perp * halfW;\n\t\t\tvar b2 = r.Pos - perp * halfW;\n\n\t\t\tAddTri( _tris, b1, b2, tip, r.Color * 0.9f );\n\n\t\t\t// \u5C3E\u6746\uFF1A\u4ECE\u7BAD\u5934\u5411\u5C4F\u5185\u5EF6\u4F38\u4E00\u5C0F\u6BB5\uFF0C\u63D0\u793A\u0022\u65B9\u5411\u0022\u800C\u975E\u0022\u4F4D\u7F6E\u70B9\u0022\n\t\t\tvar t0 = r.Pos - d * len * 0.6f;\n\t\t\t_lines.AddVertex( new Vertex( t0, r.Color * 0.5f ) );\n\t\t\t_lines.AddVertex( new Vertex( r.Pos, r.Color * 0.5f ) );\n\t\t}\n\t\t_edgeArrows.Clear();\n\t}\n\n\t// ---- CPU \u7C92\u5B50\uFF08M3 \u8868\u73B0\u529B\uFF09\uFF1A\u8D70\u540C\u4E00\u9713\u8679\u52A0\u8272\u6279\u6B21\uFF0C\u98CE\u683C\u7EDF\u4E00\u3001\u96F6\u8D44\u4EA7\u4F9D\u8D56 ----\n\n\t/// \u003Csummary\u003E \u7784\u51C6\u7BAD\u5934\uFF08v0.7.0.0\uFF09\uFF1A\u672C\u673A\u7403\u5916\u7F18\u7684\u5B9E\u5FC3\u5C0F\u4E09\u89D2\uFF0C\u6307\u5411\u5149\u6807\uFF08\u5206\u88C2/\u5410\u5B62\u5B50\u65B9\u5411\uFF09\u3002\n\t/// agar \u5F0F\u8D34\u7740\u7403\u7F18\u5916\u4FA7\uFF0C\u767D\u8272\u534A\u900F\u660E\uFF1B\u5C3A\u5BF8\u968F\u7403\u534A\u5F84\u5FAE\u7F29\u653E\uFF0C\u4E0D\u968F\u7403\u8F6C \u003C/summary\u003E\n\tvoid WriteAimArrow( Ball ball )\n\t{\n\t\tvar dir = ball.AimDir;\n\t\tvar c = ball.WorldPosition;\n\t\tvar r = ball.Radius;\n\n\t\tvar len = Math.Clamp( r * 0.22f, 18f, 64f );   // \u7BAD\u5934\u957F\u5EA6\u968F\u7403\u5F84\u5FAE\u7F29\u653E\n\t\tvar halfW = len * 0.42f;\n\t\tvar gap = r * 1.04f;\n\n\t\tvar dir3 = new Vector3( dir.x, dir.y, 0f );\n\t\tvar perp = new Vector3( -dir.y, dir.x, 0f );\n\n\t\tvar baseC = c \u002B dir3 * gap;\n\t\tvar tip = c \u002B dir3 * ( gap \u002B len );\n\t\tvar p1 = baseC \u002B perp * halfW;\n\t\tvar p2 = baseC - perp * halfW;\n\n\t\tAddTri( _tris, p1, p2, tip, new Color( 1f, 1f, 1f, 0.7f ) );\n\t}\n\n\t/// \u003Csummary\u003E \u5355\u4E2A\u5B9E\u5FC3\u4E09\u89D2 \u003C/summary\u003E\n\tvoid AddTri( SceneDynamicObject batch, Vector3 a, Vector3 b, Vector3 c, Color color )\n\t{\n\t\tbatch.AddVertex( new Vertex( a, color ) );\n\t\tbatch.AddVertex( new Vertex( b, color ) );\n\t\tbatch.AddVertex( new Vertex( c, color ) );\n\t}\n\n\tsealed class Particle\n\t{\n\t\tpublic Vector3 Pos;\n\t\tpublic Vector3 Vel;\n\t\tpublic float Life;\n\t\tpublic float MaxLife;\n\t\tpublic float Size;\n\t\tpublic Color Color;\n\t\tpublic Vector3 Target;   // Seek=true\uFF1A\u5438\u5F15\u76EE\u6807\uFF08\u541E\u7403\u6ED1\u8FDB\u5403\u8005\u5634\u91CC\uFF09\n\t\tpublic bool Seek;\n\t}\n\n\treadonly List\u003CParticle\u003E _particles = new();\n\treadonly HashSet\u003Cint\u003E _seenCells = new();                    // \u51FA\u751F\u5F39\u8DF3\uFF1A\u89C1\u8FC7\u7684\u5206\u8EAB/\u5B62\u5B50 id\uFF08\u5BA2\u6237\u7AEF\u955C\u50CF\u4E0D\u5E26 SinceSpawn\uFF0C\u7528\u0022\u9996\u89C1\u0022\u53CC\u7AEF\u7EDF\u4E00\u5224\u5B9A\uFF09\n\treadonly Dictionary\u003Cint, TimeSince\u003E _cellBorn = new();\n\n\t/// \u003Csummary\u003E \u7206\u88C2\uFF08\u6B7B\u4EA1/\u649E\u523A\uFF09\uFF1A\u4E2D\u5927\u7C92\u5B50\u5F84\u5411\u98DE\u6563 \u003C/summary\u003E\n\tpublic static void Pop( Vector3 pos, int colorIndex, float radius )\n\t{\n\t\tSpawn( pos, colorIndex, 26, 90f \u002B radius * 0.9f, 0.45f, 0.9f, 3f, 9f );\n\t}\n\n\t/// \u003Csummary\u003E \u706B\u82B1\uFF08\u5403\u98DF\u7269\uFF09\uFF1A\u51E0\u9897\u5C0F\u788E\u5C51\u4E00\u95EA \u003C/summary\u003E\n\tpublic static void Spark( Vector3 pos, int colorIndex )\n\t{\n\t\tSpawn( pos, colorIndex, 5, 100f, 0.2f, 0.4f, 2f, 4f );\n\t}\n\n\t/// \u003Csummary\u003E \u706B\u82B1\uFF08\u81EA\u5B9A\u4E49\u8272\uFF0C\u9053\u5177\u62FE\u53D6\u7528\uFF09\uFF1A\u79CD\u7C7B\u8272\u4E0D\u5728\u8C03\u8272\u677F\u91CC\uFF0C\u76F4\u63A5\u7ED9 Color \u003C/summary\u003E\n\tpublic static void Spark( Vector3 pos, Color color )\n\t{\n\t\tSpawnColored( pos, color, 10, 140f, 0.25f, 0.5f, 2f, 5f );\n\t}\n\n\t/// \u003Csummary\u003E \u55B7\u53D1\uFF08\u5206\u88C2/\u5410\u5B62\u5B50\u751F\u6210\u77AC\u95F4\uFF09 \u003C/summary\u003E\n\tpublic static void Puff( Vector3 pos, int colorIndex )\n\t{\n\t\tSpawn( pos, colorIndex, 8, 150f, 0.25f, 0.5f, 2f, 5f );\n\t}\n\n\t/// \u003Csummary\u003E \u541E\u7403\u6ED1\u5165\uFF08v0.7.8.28 juice\uFF09\uFF1A\u88AB\u5403\u8005\u7F29\u6210\u4E00\u56E2\u3001\u52A0\u901F\u6ED1\u8FDB\u5403\u8005\u5634\u91CC\uFF0C\u5C3A\u5BF8\u968F\u5BFF\u547D\u6536\u7F29\u5F52\u96F6 \u003C/summary\u003E\n\tpublic static void Swallow( Vector3 pos, int colorIndex, Vector3 target, float radius )\n\t{\n\t\tvar inst = Instance;\n\t\tif ( inst is null || !inst.IsValid() ) return;\n\n\t\tif ( inst._particles.Count \u003E= GameConfig.FxMaxParticles )\n\t\t\tinst._particles.RemoveAt( 0 );\n\n\t\tvar life = 0.3f;\n\t\tinst._particles.Add( new Particle\n\t\t{\n\t\t\tPos = pos,\n\t\t\tTarget = target,\n\t\t\tSeek = true,\n\t\t\tLife = life,\n\t\t\tMaxLife = life,\n\t\t\tSize = MathF.Max( 10f, radius * 0.9f ),\n\t\t\tColor = GameConfig.Palette[Math.Clamp( colorIndex, 0, GameConfig.Palette.Length - 1 )],\n\t\t} );\n\t\tSpawn( pos, colorIndex, 4, 120f, 0.15f, 0.3f, 2f, 4f );   // \u5C11\u91CF\u788E\u5C51\u70B9\u7F00\n\t}\n\n\tstatic void Spawn( Vector3 pos, int colorIndex, int count, float maxSpeed, float minLife, float maxLife, float minSize, float maxSize )\n\t{\n\t\tSpawnColored( pos, GameConfig.Palette[Math.Clamp( colorIndex, 0, GameConfig.Palette.Length - 1 )],\n\t\t\tcount, maxSpeed, minLife, maxLife, minSize, maxSize );\n\t}\n\n\tstatic void SpawnColored( Vector3 pos, Color color, int count, float maxSpeed, float minLife, float maxLife, float minSize, float maxSize )\n\t{\n\t\tvar inst = Instance;\n\t\tif ( inst is null || !inst.IsValid() ) return;\n\n\t\tfor ( int i = 0; i \u003C count; i\u002B\u002B )\n\t\t{\n\t\t\tif ( inst._particles.Count \u003E= GameConfig.FxMaxParticles )\n\t\t\t\tinst._particles.RemoveAt( 0 );   // \u6324\u6389\u6700\u8001\u7684\uFF0C\u4FDD\u5E27\u7387\u4F18\u5148\n\n\t\t\tvar a = Game.Random.Float( 0f, MathF.PI * 2f );\n\t\t\tvar sp = maxSpeed * Game.Random.Float( 0.35f, 1f );\n\t\t\tvar life = Game.Random.Float( minLife, maxLife );\n\t\t\tinst._particles.Add( new Particle\n\t\t\t{\n\t\t\t\tPos = pos \u002B new Vector3( Game.Random.Float( -6f, 6f ), Game.Random.Float( -6f, 6f ), 0f ),\n\t\t\t\tVel = new Vector3( MathF.Cos( a ) * sp, MathF.Sin( a ) * sp, 0f ),\n\t\t\t\tLife = life,\n\t\t\t\tMaxLife = life,\n\t\t\t\tSize = Game.Random.Float( minSize, maxSize ),\n\t\t\t\tColor = color,\n\t\t\t} );\n\t\t}\n\t}\n\n\tvoid UpdateParticles()\n\t{\n\t\tvar dt = Time.Delta;\n\t\tfor ( int i = _particles.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar p = _particles[i];\n\t\t\tp.Life -= dt;\n\t\t\tif ( p.Life \u003C= 0f )\n\t\t\t{\n\t\t\t\t_particles.RemoveAt( i );\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( p.Seek )\n\t\t\t{\n\t\t\t\tp.Pos = Vector3.Lerp( p.Pos, p.Target, 1f - MathF.Exp( -11f * dt ) );   // \u6307\u6570\u5438\u5411\u5403\u8005\u5634\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tp.Pos \u002B= p.Vel * dt;\n\t\t\t\tp.Vel *= MathF.Exp( -3.5f * dt );   // \u963B\u5C3C\uFF0C\u98DE\u6563\u540E\u51CF\u901F\u60AC\u6D6E\u6D88\u6563\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid DrawParticles()\n\t{\n\t\tforeach ( var p in _particles )\n\t\t{\n\t\t\tvar a = p.Life / p.MaxLife;\n\t\t\tif ( p.Seek )\n\t\t\t{\n\t\t\t\tAddDisc( _tris, p.Pos, p.Size * a, p.Color * ( 0.35f \u002B 0.65f * a ), 1.1f, 10 );\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tAddDisc( _tris, p.Pos, p.Size * ( 0.5f \u002B a * 0.5f ), p.Color * ( 0.25f \u002B a * 0.75f ), 1.2f, 6 );\n\t\t}\n\t}\n\n\tvoid WriteBall( CircleroyaleGame game, Ball ball )\n\t{\n\t\tvar c = ball.WorldPosition;\n\t\tvar r = ball.Radius;\n\t\tvar color = ball.NeonColor;\n\n\t\t// \u63A8\u6324\u6324\u538B\uFF08v0.7.8.28 juice\uFF09\uFF1A\u4E0E\u90BB\u4F53\u91CD\u53E0\u65F6\u73AF\u4F53\u8F7B\u5FAE\u6536\u7F29 \u002B \u63A5\u89E6\u65B9\u5411\u4EAE\u8D77\u538B\u5F27\u3002\n\t\t// \u7EAF\u6E32\u67D3\u5C42\u8BA1\u7B97\uFF08host \u6743\u5A01\u4F4D\u7F6E / \u5BA2\u6237\u7AEF\u955C\u50CF\u540C\u6E90\uFF09\uFF0C\u53CC\u7AEF\u4E00\u81F4\uFF1B\u5934\u50CF\u5728\u5185\u4E0D\u53D7\u5F71\u54CD\n\t\tVector2 contactDir = Vector2.Zero;\n\t\tfloat contactK = 0f;\n\t\tforeach ( var o in game.Balls )\n\t\t{\n\t\t\tif ( o is null || !o.IsValid() || !o.Alive || o == ball ) continue;\n\t\t\tvar dx = o.WorldPosition.x - c.x;\n\t\t\tvar dy = o.WorldPosition.y - c.y;\n\t\t\tvar d = MathF.Sqrt( dx * dx \u002B dy * dy );\n\t\t\tvar overlap = r \u002B o.Radius - d;\n\t\t\tif ( overlap \u003C= 0f || d \u003C 0.001f ) continue;\n\t\t\tvar k = overlap / r;\n\t\t\tif ( k \u003E contactK ) { contactK = k; contactDir = new Vector2( dx / d, dy / d ); }\n\t\t}\n\t\tforeach ( var pc in game.Cells )\n\t\t{\n\t\t\tif ( pc.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\tvar dx = pc.DrawPos.x - c.x;\n\t\t\tvar dy = pc.DrawPos.y - c.y;\n\t\t\tvar d = MathF.Sqrt( dx * dx \u002B dy * dy );\n\t\t\tvar overlap = r \u002B pc.Radius - d;\n\t\t\tif ( overlap \u003C= 0f || d \u003C 0.001f ) continue;\n\t\t\tvar k = overlap / r;\n\t\t\tif ( k \u003E contactK ) { contactK = k; contactDir = new Vector2( dx / d, dy / d ); }\n\t\t}\n\t\tvar squash = MathF.Min( 0.12f, contactK * 0.45f );\n\t\tvar rr = r * ( 1f - squash );\n\n\t\t// \u51FA\u751F\u4FDD\u62A4\uFF1A\u6574\u7403\u4E00\u95EA\u4E00\u95EA\uFF08\u7528\u6237\u5B9A\u7A3F\uFF09\u2014\u2014\u65B9\u6CE2\u95EA\u70C1\uFF0C\u6697\u76F8\u4FDD\u7559 35% \u4E0D\u81F3\u4E8E\u770B\u4E0D\u89C1\n\t\tvar blink = 1f;\n\t\tif ( ball.IsProtected )\n\t\t\tblink = MathF.Sin( Time.Now * GameConfig.SpawnBlinkHz * MathF.PI * 2f ) \u003E 0f ? 1f : 0.35f;\n\n\t\t// \u5185\u82AF\uFF1A\u5B9E\u5FC3\u5706\u80CC\u666F\u586B\u5145\uFF0C\u989C\u8272\u53D6\u81EA\u73A9\u5BB6\u5934\u50CF\u4E3B\u8272\uFF08bot \u7528\u9713\u8679\u914D\u8272\uFF09\u3002\n\t\t// z \u62AC\u9AD8\u4FDD\u8BC1\u76D6\u5728\u7F51\u683C\u4E4B\u4E0A\u3001\u5916\u73AF\u4E4B\u4E0B\n\t\tAddDisc( _tris, c, rr * 0.92f, ball.BackgroundColor * ( ball.IsProtected ? 0.25f * blink : 0.45f ), 1f, GameConfig.DiscSegments );\n\n\t\t// \u5916\u73AF\uFF1A\u9713\u8679\u7EBF\u5708\n\t\tAddRing( _lines, c, rr, color * 0.9f * ( ball.IsProtected ? blink : 1f ), GameConfig.RingSegments );\n\n\t\t// \u538B\u5F27\uFF1A\u6324\u5F97\u8D8A\u72E0\u8D8A\u4EAE\uFF08\u538B\u529B\u70B9\u9AD8\u5149\uFF09\n\t\tif ( squash \u003E 0.02f \u0026\u0026 contactDir != Vector2.Zero )\n\t\t\tDrawPressureArc( c, rr, contactDir, color * MathF.Min( 1.6f, 0.6f \u002B squash * 6f ) );\n\n\t\t// \u62A4\u76FE\u865A\u73AF\uFF1A\u51FA\u751F\u4FDD\u62A4 = \u95EA\u70C1\u4EAE\u73AF\uFF08v0.6.2.0 \u8D77\u5206\u8EAB\u4E0D\u518D\u62A4\u4F53\uFF0C\u62A4\u4F53\u6697\u73AF\u79FB\u9664\uFF09\n\t\tif ( ball.IsProtected )\n\t\t{\n\t\t\tDrawShieldRing( c, r, color * 0.5f * blink );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u63A8\u6324\u538B\u5F27\uFF1A\u63A5\u89E6\u65B9\u5411\u7684\u4E00\u6BB5\u4EAE\u5F27\uFF08\u52A0\u8272\u6DF7\u5408\u4E0B\u50CF\u6324\u51FA\u7684\u9AD8\u5149\uFF0Cv0.7.8.28\uFF09 \u003C/summary\u003E\n\tvoid DrawPressureArc( Vector3 c, float r, Vector2 dir, Color color )\n\t{\n\t\tvar baseAngle = MathF.Atan2( dir.y, dir.x );\n\t\tconst int seg = 4;\n\t\tconst float half = 0.55f;   // \u5F27\u5BBD \u00B131\u00B0\n\t\tfor ( int i = 0; i \u003C seg; i\u002B\u002B )\n\t\t{\n\t\t\tvar a0 = baseAngle - half \u002B 2f * half * i / seg;\n\t\t\tvar a1 = baseAngle - half \u002B 2f * half * ( i \u002B 1 ) / seg;\n\t\t\t_lines.AddVertex( new Vertex( RingPoint( c, r, a0 ), color ) );\n\t\t\t_lines.AddVertex( new Vertex( RingPoint( c, r, a1 ), color ) );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u5F39\u8DF3\u6CE8\u518C\u8868\u8D85\u9650\u65F6\u6574\u4F53\u91CD\u5EFA\uFF08\u5F53\u524D\u6D3B\u7740\u7684 id \u91CD\u65B0\u7ACB\u518C\uFF0C\u91CD\u7F6E\u77AC\u95F4\u8F7B\u5FAE\u518D\u5F39\u4E00\u6B21\uFF0C\u7F55\u89C1\u53EF\u63A5\u53D7\uFF09 \u003C/summary\u003E\n\tvoid PruneCellFx( CircleroyaleGame game )\n\t{\n\t\t_seenCells.Clear();\n\t\t_cellBorn.Clear();\n\t\tforeach ( var c in game.Cells )\n\t\t\t_seenCells.Add( c.Id );\n\t}\n\n\tvoid DrawShieldRing( Vector3 c, float r, Color shield )\n\t{\n\t\tvar r2 = r * 1.25f;\n\t\tvar seg = GameConfig.RingSegments;\n\t\tfor ( int i = 0; i \u003C seg; i \u002B= 2 )\n\t\t{\n\t\t\tvar a0 = i * MathF.PI * 2f / seg;\n\t\t\tvar a1 = ( i \u002B 1 ) * MathF.PI * 2f / seg;\n\t\t\t_lines.AddVertex( new Vertex( RingPoint( c, r2, a0 ), shield ) );\n\t\t\t_lines.AddVertex( new Vertex( RingPoint( c, r2, a1 ), shield ) );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u9053\u5177\u83F1\u5F62\uFF1A\u65CB\u8F6C\u7684\u6B63\u65B9\u5F62\uFF08\u5B9E\u5FC3\u586B\u5145 \u002B \u4EAE\u63CF\u8FB9\uFF09 \u003C/summary\u003E\n\tvoid WriteDiamond( Vector3 c, float r, float spin, Color color )\n\t{\n\t\tvar pts = new Vector3[4];\n\t\tfor ( int i = 0; i \u003C 4; i\u002B\u002B )\n\t\t{\n\t\t\tvar a = spin \u002B i * MathF.PI * 0.5f;\n\t\t\tpts[i] = c \u002B new Vector3( MathF.Cos( a ) * r, MathF.Sin( a ) * r, 1.2f );\n\t\t}\n\n\t\tfor ( int i = 0; i \u003C 4; i\u002B\u002B )\n\t\t{\n\t\t\tAddTri( _tris, c \u002B Vector3.Up * 1.2f, pts[i], pts[( i \u002B 1 ) % 4], color * 0.35f );\n\t\t\t_lines.AddVertex( new Vertex( pts[i], color ) );\n\t\t\t_lines.AddVertex( new Vertex( pts[( i \u002B 1 ) % 4], color ) );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u9053\u5177\u5341\u5B57\u82AF\uFF08\u78C1\u94C1\uFF09\uFF1A\u4E24\u6761\u4EA4\u53C9\u7EBF \u003C/summary\u003E\n\tvoid WriteCross( Vector3 c, float r, Color color )\n\t{\n\t\t_lines.AddVertex( new Vertex( c \u002B new Vector3( -r, -r, 1.4f ), color ) );\n\t\t_lines.AddVertex( new Vertex( c \u002B new Vector3( r, r, 1.4f ), color ) );\n\t\t_lines.AddVertex( new Vertex( c \u002B new Vector3( -r, r, 1.4f ), color ) );\n\t\t_lines.AddVertex( new Vertex( c \u002B new Vector3( r, -r, 1.4f ), color ) );\n\t}\n\n\t/// \u003Csummary\u003E \u7EFF\u523A\u661F\u5F62\uFF1A12 \u89D2\u5185\u5C16\u661F\uFF0C\u63CF\u8FB9 \u002B \u4F4E\u900F\u660E\u5EA6\u586B\u5145\uFF08\u56FA\u5B9A\u76F8\u4F4D\u4E0D\u65CB\u8F6C\uFF09 \u003C/summary\u003E\n\tvoid WriteStar( Vector3 c, float r, Color edge, Color fill )\n\t{\n\t\tconst int points = 12;\n\t\tconst float inner = 0.55f;\n\t\tvar n = points * 2;\n\t\tvar center = c \u002B Vector3.Up * 0.85f;\n\t\tvar pts = new Vector3[n];\n\t\tfor ( int i = 0; i \u003C n; i\u002B\u002B )\n\t\t{\n\t\t\tvar rr = ( i % 2 == 0 ) ? r : r * inner;\n\t\t\tvar a = i * MathF.PI * 2f / n \u002B 0.26f;\n\t\t\tpts[i] = c \u002B new Vector3( MathF.Cos( a ) * rr, MathF.Sin( a ) * rr, 0.85f );\n\t\t}\n\n\t\tfor ( int i = 0; i \u003C n; i\u002B\u002B )\n\t\t{\n\t\t\tvar p2 = pts[( i \u002B 1 ) % n];\n\t\t\t_tris.AddVertex( new Vertex( center, fill ) );\n\t\t\t_tris.AddVertex( new Vertex( pts[i], fill ) );\n\t\t\t_tris.AddVertex( new Vertex( p2, fill ) );\n\n\t\t\t_lines.AddVertex( new Vertex( pts[i], edge ) );\n\t\t\t_lines.AddVertex( new Vertex( p2, edge ) );\n\t\t}\n\t}\n\n\tstatic Vector3 RingPoint( Vector3 c, float r, float a )\n\t{\n\t\treturn c \u002B new Vector3( MathF.Cos( a ) * r, MathF.Sin( a ) * r, 2f );\n\t}\n\n\t/// \u003Csummary\u003E \u9713\u8679\u5706\u73AF\uFF08\u7EBF\u6BB5\u6279\uFF09\uFF1A\u7403\u5916\u73AF / \u5206\u8EAB\u5916\u73AF / \u5B62\u5B50\u63CF\u8FB9\u516C\u7528 \u003C/summary\u003E\n\tstatic void AddRing( SceneDynamicObject dyno, Vector3 c, float r, Color color, int segments )\n\t{\n\t\tsegments = Math.Max( 3, segments );\n\t\tvar prev = RingPoint( c, r, 0f );\n\t\tfor ( int i = 1; i \u003C= segments; i\u002B\u002B )\n\t\t{\n\t\t\tvar p = RingPoint( c, r, i * MathF.PI * 2f / segments );\n\t\t\tdyno.AddVertex( new Vertex( prev, color ) );\n\t\t\tdyno.AddVertex( new Vertex( p, color ) );\n\t\t\tprev = p;\n\t\t}\n\t}\n\n\tstatic void AddDisc( SceneDynamicObject dyno, Vector3 c, float r, Color color, float z, int segments )\n\t{\n\t\tsegments = Math.Max( 3, segments );\n\t\tvar center = c \u002B Vector3.Up * z;\n\t\tvar prev = c \u002B new Vector3( r, 0f, z );\n\t\tfor ( int i = 1; i \u003C= segments; i\u002B\u002B )\n\t\t{\n\t\t\tvar p = RingPoint( c, r, i * MathF.PI * 2f / segments );\n\t\t\tp.z = z;\n\t\t\tdyno.AddVertex( new Vertex( center, color ) );\n\t\t\tdyno.AddVertex( new Vertex( prev, color ) );\n\t\t\tdyno.AddVertex( new Vertex( p, color ) );\n\t\t\tprev = p;\n\t\t}\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"Player/BotAvatar.cs","FileName":"BotAvatar.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\n\n/// \u003Csummary\u003E\n/// bot \u51E0\u4F55\u56FE\u6848\u5934\u50CF\uFF08M3\uFF09\uFF1A\u6309 bot \u540D\u5B57 \u002B \u8272\u53F7\u54C8\u5E0C**\u786E\u5B9A\u6027\u751F\u6210**\u2014\u2014\u540C\u4E00 bot \u5728 host \u4E0E\n/// \u5404\u5BA2\u6237\u7AEF\u7B97\u51FA\u5B8C\u5168\u76F8\u540C\u7684\u56FE\u6848\uFF0C\u96F6\u540C\u6B65\u6210\u672C\u3002\u7EAF\u6570\u5B66\u56FE\u6837\uFF08\u540C\u5FC3\u73AF/\u8F90\u5C04\u6761/\u70B9\u9635/\u6CE2\u7EB9\uFF09\uFF0C\n/// \u7ECF Texture.Create \u7A0B\u5E8F\u5316\u751F\u6210\uFF0C\u989C\u8272\u53D6\u9713\u8679\u8C03\u8272\u677F\uFF1B\u80CC\u666F\u900F\u660E\uFF08\u900F\u51FA\u7403\u4F53\u7684\u9713\u8679\u5E95\u8272\uFF09\u3002\n/// \u003C/summary\u003E\npublic static class BotAvatar\n{\n\tconst int Size = 128;\n\n\tstatic readonly Dictionary\u003Cstring, Texture\u003E _cache = new();\n\n\t/// \u003Csummary\u003E \u53D6 bot \u5934\u50CF\uFF1A\u540C key \u5FC5\u5F97\u540C\u4E00\u5F20\uFF1B\u7F13\u5B58\u8D85\u9650\u6574\u4F53\u6E05\u7A7A\uFF08bot \u6570\u6709\u9650\uFF0C\u5B9E\u9645\u5230\u4E0D\u4E86\uFF09 \u003C/summary\u003E\n\tpublic static Texture GetOrCreate( string botName, int colorIndex )\n\t{\n\t\tvar key = $\u0022{botName}#{colorIndex}\u0022;\n\t\tif ( _cache.TryGetValue( key, out var tex ) \u0026\u0026 tex.IsValid() ) return tex;\n\n\t\tif ( _cache.Count \u003E 64 ) _cache.Clear();\n\n\t\ttex = Generate( botName ?? \u0022\u0022, colorIndex );\n\t\t_cache[key] = tex;\n\t\treturn tex;\n\t}\n\n\tstatic Texture Generate( string botName, int colorIndex )\n\t{\n\t\t// \u7A33\u5B9A\u79CD\u5B50\uFF1AFNV-1a \u54C8\u5E0C\uFF08\u8DE8\u673A\u5668\u3001\u8DE8\u8FDB\u7A0B\u4E00\u81F4\uFF09\n\t\tulong hash = 14695981039346656037UL;\n\t\tforeach ( var ch in botName )\n\t\t{\n\t\t\thash ^= ch;\n\t\t\thash *= 1099511628211UL;\n\t\t}\n\t\tvar rng = new System.Random( (int)( hash \u0026 0x7fffffff ) );\n\n\t\tint pattern = rng.Next( 4 );\n\t\tint variant = rng.Next( 3 );   // \u9891\u7387/\u76F8\u4F4D\u5FAE\u8C03\uFF0C\u540C\u6B3E\u56FE\u6837\u4E5F\u4E0D\u91CD\u6837\n\t\tvar baseColor = GameConfig.Palette[Math.Clamp( colorIndex, 0, GameConfig.Palette.Length - 1 )];\n\n\t\tvar px = new byte[Size * Size * 4];\n\t\tint p = 0;\n\t\tfor ( int y = 0; y \u003C Size; y\u002B\u002B )\n\t\t{\n\t\t\tfor ( int x = 0; x \u003C Size; x\u002B\u002B )\n\t\t\t{\n\t\t\t\tfloat dx = ( x - Size * 0.5f ) / ( Size * 0.5f );\n\t\t\t\tfloat dy = ( y - Size * 0.5f ) / ( Size * 0.5f );\n\t\t\t\tfloat r = MathF.Sqrt( dx * dx \u002B dy * dy );\n\t\t\t\tfloat a = MathF.Atan2( dy, dx );\n\n\t\t\t\tfloat v = pattern switch\n\t\t\t\t{\n\t\t\t\t\t0 =\u003E Rings( r, variant ),\n\t\t\t\t\t1 =\u003E Spokes( r, a, variant ),\n\t\t\t\t\t2 =\u003E Dots( dx, dy, variant ),\n\t\t\t\t\t_ =\u003E Waves( dx, dy, variant ),\n\t\t\t\t};\n\n\t\t\t\t// \u5706\u5F62\u88C1\u5207 \u002B \u8FB9\u7F18 2px \u6297\u952F\u9F7F\uFF1B\u56FE\u6837\u8986\u76D6\u7387 \u2192 \u4E0D\u900F\u660E\u5EA6\n\t\t\t\tfloat alpha = Math.Clamp( ( 0.96f - r ) / 0.05f, 0f, 1f );\n\t\t\t\tfloat on = Math.Clamp( v, 0f, 1f ) * alpha;\n\t\t\t\tfloat shade = 1.15f - r * 0.5f;   // \u4E2D\u5FC3\u4EAE\u8FB9\u7F18\u6697\uFF0C\u5E26\u70B9\u7ACB\u4F53\u611F\n\n\t\t\t\tpx[p\u002B\u002B] = (byte)( Math.Clamp( baseColor.r * shade, 0f, 1f ) * on * 255f );\n\t\t\t\tpx[p\u002B\u002B] = (byte)( Math.Clamp( baseColor.g * shade, 0f, 1f ) * on * 255f );\n\t\t\t\tpx[p\u002B\u002B] = (byte)( Math.Clamp( baseColor.b * shade, 0f, 1f ) * on * 255f );\n\t\t\t\tpx[p\u002B\u002B] = (byte)( on * 255f );\n\t\t\t}\n\t\t}\n\n\t\treturn Texture.Create( Size, Size, ImageFormat.RGBA8888 )\n\t\t\t.WithData( px )\n\t\t\t.WithName( $\u0022botavatar_{(uint)hash:X8}\u0022 )\n\t\t\t.Finish();\n\t}\n\n\t/// \u003Csummary\u003E \u540C\u5FC3\u73AF\uFF1A\u5B9E\u5FC3\u6838\u5FC3 \u002B N \u9053\u73AF \u003C/summary\u003E\n\tstatic float Rings( float r, int variant )\n\t{\n\t\tif ( r \u003C 0.18f ) return 1f;\n\n\t\tvar freq = 2.6f \u002B variant * 1.3f;\n\t\tvar f = r * freq - variant * 0.33f;\n\t\treturn Fract( f ) \u003C 0.45f ? 1f : 0f;\n\t}\n\n\t/// \u003Csummary\u003E \u8F90\u5C04\u6761\uFF1A\u4ECE\u4E2D\u5FC3\u5C04\u51FA\u7684\u8F90\u6761 \u002B \u5B9E\u5FC3\u6838\u5FC3 \u003C/summary\u003E\n\tstatic float Spokes( float r, float a, int variant )\n\t{\n\t\tif ( r \u003C 0.2f ) return 1f;\n\n\t\tvar spokes = 6 \u002B variant * 2;\n\t\tvar f = ( a / ( MathF.PI * 2f ) \u002B 1f ) * spokes;\n\t\treturn Fract( f ) \u003C 0.4f ? 1f : 0f;\n\t}\n\n\t/// \u003Csummary\u003E \u70B9\u9635\uFF1A\u6B63\u5F26\u8C03\u5236\u7684\u5706\u70B9\u7F51\u683C \u003C/summary\u003E\n\tstatic float Dots( float dx, float dy, int variant )\n\t{\n\t\tvar freq = 7f \u002B variant * 3f;\n\t\tvar phase = variant * 2.1f;\n\t\tvar v = MathF.Sin( dx * freq \u002B phase ) * MathF.Sin( dy * freq - phase );\n\t\treturn v \u003E 0.35f ? 1f : 0f;\n\t}\n\n\t/// \u003Csummary\u003E \u6CE2\u7EB9\uFF1A\u6C34\u5E73\u6B63\u5F26\u6CE2\u5E26 \u003C/summary\u003E\n\tstatic float Waves( float dx, float dy, int variant )\n\t{\n\t\tvar freq = 5f \u002B variant * 2.5f;\n\t\tvar phase = variant * 1.7f;\n\t\tvar band = dy \u002B 0.32f * MathF.Sin( dx * freq \u002B phase );\n\t\treturn MathF.Abs( band ) \u003C 0.16f ? 1f : 0f;\n\t}\n\n\tstatic float Fract( float f ) =\u003E f - MathF.Floor( f );\n}\n"},{"Ident":"slfy.circleroyale","Path":"UI/EndBoard.cs","FileName":"EndBoard.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\nusing Sandbox.UI;\n\n/// \u003Csummary\u003E\n/// \u7ED3\u7B97\u9762\u677F\uFF08M5\uFF09\uFF1A\u6BD4\u8D5B\u7ED3\u675F\u5168\u5C4F\u7ED3\u7B97\u2014\u2014\u672C\u5C40\u6392\u884C\uFF08\u9010\u884C\u6ED1\u5165\u52A8\u753B\uFF09\u002B \u5168\u670D\u6392\u884C\n/// \uFF08\u4E91\u7AEF\u4F18\u5148 ScoreUploader\uFF0C\u7A7A\u4E86\u964D\u7EA7 LocalBoard \u672C\u5730\u699C\uFF09\u002B \u56DE\u83DC\u5355\u6309\u94AE\u3002\n/// \u7EAF C# Panel \u002B \u540C\u76EE\u5F55 EndBoard.cs.scss\uFF1B\u663E\u793A\u671F\u95F4\u5168\u573A\u6A21\u62DF\u51BB\u7ED3\uFF08MatchState.MatchOver\uFF09\u3002\n/// \u003C/summary\u003E\npublic sealed class EndBoard : PanelComponent\n{\n\tLabel _title;\n\tLabel _winner;\n\tLabel _matchHead;\n\tLabel[] _matchRows;\n\tLabel _selfRow;\n\tLabel _globalHead;\n\tLabel[] _globalRows;\n\tLabel _globalNote;\n\tLabel _back;\n\n\tbool _built;\n\tbool _shown;\n\tTimeSince _sinceShow;\n\n\tScoreWire[] _standings;\n\tlong _mySteamId;\n\n\t// \u9010\u884C reveal \u52A8\u753B\uFF1A\u884C\u7D22\u5F15 \u2192 \u51FA\u573A\u65F6\u523B\uFF08\u79D2\uFF09\uFF0C\u900F\u660E\u5EA6/\u4F4D\u79FB\u5728 0.25s \u5185\u6E10\u5165\n\tconst float MatchRowDelay = 0.45f;\n\tconst float MatchRowStagger = 0.22f;\n\tconst float GlobalDelay = 3.4f;\n\tconst float GlobalStagger = 0.16f;\n\n\tprotected override void OnStart()\n\t{\n\t\tbase.OnStart();\n\t\tBuildUi();\n\t\tHide();\n\t}\n\n\tvoid BuildUi()\n\t{\n\t\tvar root = Panel;\n\t\tif ( root is null ) return;\n\t\tif ( _built \u0026\u0026 _title.IsValid() \u0026\u0026 root.ChildrenCount \u003E 0 ) return;\n\n\t\troot.DeleteChildren( true );\n\n\t\troot.Style.Position = PositionMode.Absolute;\n\t\troot.Style.Left = 0f;\n\t\troot.Style.Top = 0f;\n\t\troot.Style.Right = 0f;\n\t\troot.Style.Bottom = 0f;\n\t\troot.Style.BackgroundColor = new Color( 0.008f, 0.012f, 0.05f, 0.88f );\n\t\troot.Style.PointerEvents = PointerEvents.All;\n\n\t\t// \u6807\u9898/\u51A0\u519B/\u56DE\u83DC\u5355\u5C45\u4E2D\uFF1B\u672C\u5C40\u6392\u884C\uFF08\u5DE6\u5217\uFF09\u4E0E\u5168\u670D\u6392\u884C\uFF08\u53F3\u5217\uFF09\u5E76\u6392\uFF08v0.6.3.0 \u7528\u6237\u5B9A\u7A3F\uFF09\n\t\t_title = NewRow( root, 88f, 360f, 1200f, \u0022eb-title\u0022 );\n\t\t_title.Text = \u0022MATCH OVER\u0022;\n\n\t\t_winner = NewRow( root, 166f, 360f, 1200f, \u0022eb-winner\u0022 );\n\n\t\t_matchHead = NewRow( root, 244f, 260f, 580f, \u0022eb-head\u0022 );\n\t\t_matchHead.Text = \u0022MATCH RANKING\u0022;\n\n\t\t// \u672C\u5C40\u6392\u884C\uFF1A10 \u884C \u002B \u699C\u5916\u81EA\u8EAB\u884C\uFF08\u5DE6\u5217\uFF09\n\t\t_matchRows = new Label[GameConfig.LeaderboardRows];\n\t\tfor ( int i = 0; i \u003C _matchRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\t_matchRows[i] = NewRow( root, 276f \u002B i * 28f, 260f, 580f, \u0022eb-row\u0022 );\n\t\t}\n\n\t\t_selfRow = NewRow( root, 276f \u002B _matchRows.Length * 28f, 260f, 580f, \u0022eb-row self\u0022 );\n\t\t_selfRow.Style.Display = DisplayMode.None;\n\n\t\t_globalHead = NewRow( root, 244f, 1060f, 580f, \u0022eb-head global\u0022 );\n\t\t_globalHead.Text = \u0022GLOBAL TOP 10\u0022;\n\n\t\t_globalRows = new Label[10];\n\t\tfor ( int i = 0; i \u003C _globalRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\t_globalRows[i] = NewRow( root, 276f \u002B i * 28f, 1060f, 580f, \u0022eb-row small\u0022 );\n\t\t}\n\n\t\t_globalNote = NewRow( root, 276f \u002B _globalRows.Length * 28f, 1060f, 580f, \u0022eb-note\u0022 );\n\n\t\t_back = NewRow( root, 620f, 360f, 1200f, \u0022eb-back\u0022 );\n\t\t_back.Text = Networking.IsActive ? \u0022RETURN TO LOBBY \u2014 [SPACE]\u0022 : \u0022BACK TO MENU \u2014 [SPACE]\u0022;\n\t\t_back.AddEventListener( \u0022onclick\u0022, () =\u003E OnBackClicked() );\n\n\t\t_built = true;\n\t}\n\n\t/// \u003Csummary\u003E \u5EFA\u4E00\u884C Label\uFF1A\u5217\u5185\u5C45\u4E2D\uFF08left..left\u002Bwidth\uFF09\uFF0C\u884C\u53F7\u5B9A top \u003C/summary\u003E\n\tLabel NewRow( Panel root, float top, float left, float width, string classes )\n\t{\n\t\tvar l = new Label() { Classes = classes };\n\t\tl.Style.Position = PositionMode.Absolute;\n\t\tl.Style.Left = left;\n\t\tl.Style.Width = width;\n\t\tl.Style.Top = top;\n\t\troot.AddChild( l );\n\t\treturn l;\n\t}\n\n\t/// \u003Csummary\u003E \u6BD4\u8D5B\u7ED3\u675F\u5C55\u793A\u7ED3\u7B97\uFF08CircleroyaleGame.OnMatchOverRemote \u8C03\u7528\uFF0C\u53CC\u7AEF\uFF09 \u003C/summary\u003E\n\tpublic void Show( ScoreWire[] standings, long mySteamId, string myName )\n\t{\n\t\t_standings = standings;\n\t\t_mySteamId = mySteamId;\n\n\t\tBuildUi();\n\t\tif ( !_built ) return;\n\n\t\t_sinceShow = 0;\n\t\t_shown = true;\n\t\tPanel.Style.Display = DisplayMode.Flex;\n\n\t\t_winner.Text = WinnerText( standings );\n\n\t\tFillMatchRows();\n\t\tFillGlobalPlaceholder();\n\t\tScoreUploader.FetchGlobal( ApplyGlobal );\n\t}\n\n\t/// \u003Csummary\u003E \u56DE\u5927\u5385/\u4E3B\u83DC\u5355\uFF08\u5B9E\u4F8B\u65B9\u6CD5\uFF1A\u70ED\u91CD\u8F7D\u53EF\u91CD\u6620\u5C04\uFF09 \u003C/summary\u003E\n\tvoid OnBackClicked() =\u003E CircleroyaleGame.Current?.AfterSettlement();\n\n\t/// \u003Csummary\u003E \u56DE\u5927\u5385\u65F6\u6536\u8D77\uFF08\u9762\u677F\u968F GameObject \u590D\u7528\uFF0C\u4E0B\u6B21\u5F00\u5C40\u518D\u5C55\u793A\uFF09 \u003C/summary\u003E\n\tpublic void Hide()\n\t{\n\t\t_shown = false;\n\t\tif ( Panel.IsValid() ) Panel.Style.Display = DisplayMode.None;\n\t}\n\n\t// ---- \u5185\u5BB9\u586B\u5145 ----\n\n\tstring WinnerText( ScoreWire[] rows )\n\t{\n\t\tif ( rows is null || rows.Length == 0 ) return \u0022\u0022;\n\n\t\tif ( !MatchState.IsTeam )\n\t\t\treturn $\u0022WINNER \u2014 {rows[0].Name}   ({rows[0].Mass:0})\u0022;\n\n\t\t// \u56E2\u961F\u8D5B\uFF1A\u80DC\u8D1F\u6309\u961F\u4F0D 3 \u4EBA\u603B\u5206\n\t\tvar totals = new Dictionary\u003Cint, float\u003E();\n\t\tforeach ( var r in rows )\n\t\t{\n\t\t\ttotals.TryGetValue( r.Team, out var m );\n\t\t\ttotals[r.Team] = m \u002B r.Mass;\n\t\t}\n\n\t\tint best = -1;\n\t\tfloat bestMass = -1f;\n\t\tforeach ( var kv in totals )\n\t\t{\n\t\t\tif ( kv.Value \u003E bestMass )\n\t\t\t{\n\t\t\t\tbestMass = kv.Value;\n\t\t\t\tbest = kv.Key;\n\t\t\t}\n\t\t}\n\t\treturn $\u0022TEAM {best \u002B 1} WINS   ({bestMass:0})\u0022;\n\t}\n\n\tvoid FillMatchRows()\n\t{\n\t\tvar rows = _standings;\n\t\tint selfRank = -1;\n\t\tfor ( int i = 0; i \u003C rows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tif ( rows[i].SteamId == _mySteamId \u0026\u0026 !rows[i].Bot )\n\t\t\t{\n\t\t\t\tselfRank = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfor ( int i = 0; i \u003C _matchRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar row = _matchRows[i];\n\t\t\tif ( i \u003E= rows.Length )\n\t\t\t{\n\t\t\t\trow.Text = \u0022\u0022;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar r = rows[i];\n\t\t\trow.Text = RowText( $\u0022{i \u002B 1}.\u0022, r.Name, $\u0022{r.Mass:0}\u0022, r.Team );\n\n\t\t\tbool isSelf = r.SteamId == _mySteamId \u0026\u0026 !r.Bot;\n\t\t\trow.Style.FontColor = isSelf ? new Color( 0.21f, 0.94f, 1f ) : new Color( 1f, 1f, 1f, 0.78f );\n\t\t}\n\n\t\t// \u699C\u5916\u81EA\u8EAB\u884C\n\t\tbool showSelf = selfRank \u003E= _matchRows.Length;\n\t\t_selfRow.Style.Display = showSelf ? DisplayMode.Flex : DisplayMode.None;\n\t\tif ( showSelf )\n\t\t{\n\t\t\tvar r = rows[selfRank];\n\t\t\t_selfRow.Text = RowText( $\u0022#{selfRank \u002B 1}.\u0022, r.Name, $\u0022{r.Mass:0}\u0022, r.Team );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u884C\u6587\u672C\uFF08\u56E2\u961F\u8D5B\u5E26 T# \u524D\u7F00\uFF1B\u666E\u901A\u8D5B\u53EA\u6709\u540D\u6B21/\u540D\u5B57/\u5206\u6570\uFF09 \u003C/summary\u003E\n\tstring RowText( string rank, string name, string mass, byte team )\n\t{\n\t\treturn MatchState.IsTeam ? $\u0022{rank} [T{team \u002B 1}] {name}   {mass}\u0022 : $\u0022{rank}  {name}   {mass}\u0022;\n\t}\n\n\t/// \u003Csummary\u003E \u5168\u670D\u699C\u5148\u5360\u4F4D\u0022\u52A0\u8F7D\u4E2D\u0022\uFF0C\u4E91\u7AEF\u56DE\u6765\u540E\u586B\u5145\uFF08\u7A7A\u4E86\u964D\u7EA7\u672C\u5730\u699C\uFF09 \u003C/summary\u003E\n\tvoid FillGlobalPlaceholder()\n\t{\n\t\t_globalRows[0].Text = \u0022LOADING ...\u0022;\n\t\tfor ( int i = 1; i \u003C _globalRows.Length; i\u002B\u002B )\n\t\t\t_globalRows[i].Text = \u0022\u0022;\n\t\t_globalNote.Text = \u0022\u0022;\n\t}\n\n\tvoid ApplyGlobal( List\u003C( string Name, float Mass )\u003E entries )\n\t{\n\t\tif ( !_shown ) return;\n\t\tif ( _globalRows is null || _globalRows[0] is null || !_globalRows[0].IsValid() ) return;\n\n\t\tif ( entries is null )\n\t\t{\n\t\t\t// \u4E91\u7AEF\u4E0D\u53EF\u7528\uFF08\u672A\u53D1\u5E03\u5305\u662F\u5E38\u6001\uFF09\uFF1A\u964D\u7EA7\u672C\u673A\u6301\u4E45\u5316\u699C\n\t\t\tentries = LocalBoard.Top( _globalRows.Length );\n\t\t\t_globalNote.Text = entries is not null ? \u0022OFFLINE BOARD (THIS MACHINE)\u0022 : \u0022NO RECORDS YET\u0022;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t_globalNote.Text = \u0022BEST SINGLE-MATCH SCORE\u0022;\n\t\t}\n\n\t\tfor ( int i = 0; i \u003C _globalRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tif ( entries is null || i \u003E= entries.Count )\n\t\t\t{\n\t\t\t\t_globalRows[i].Text = \u0022\u0022;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t_globalRows[i].Text = $\u0022{i \u002B 1}.  {entries[i].Name}   {entries[i].Mass:0}\u0022;\n\t\t}\n\t}\n\n\t// ---- \u9010\u5E27\uFF1Areveal \u52A8\u753B \u002B \u5149\u6807 \u002B \u7A7A\u683C\u56DE\u83DC\u5355 ----\n\n\tprotected override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\tBuildUi();\n\t\tif ( !_shown ) return;\n\n\t\tif ( Panel.IsValid() ) Panel.Style.Display = DisplayMode.Flex;\n\t\tMouse.Visibility = MouseVisibility.Visible;   // \u7ED3\u7B97\u671F\u6309\u94AE\u8981\u80FD\u70B9\n\n\t\tfloat t = _sinceShow;\n\t\tAnimate( _title, t, 0.05f );\n\t\tAnimate( _winner, t, 0.2f );\n\t\tAnimate( _matchHead, t, MatchRowDelay - 0.2f );\n\n\t\tfor ( int i = 0; i \u003C _matchRows.Length; i\u002B\u002B )\n\t\t\tAnimate( _matchRows[i], t, MatchRowDelay \u002B i * MatchRowStagger );\n\n\t\tAnimate( _selfRow, t, MatchRowDelay \u002B _matchRows.Length * MatchRowStagger );\n\t\tAnimate( _globalHead, t, GlobalDelay - 0.2f );\n\n\t\tfor ( int i = 0; i \u003C _globalRows.Length; i\u002B\u002B )\n\t\t\tAnimate( _globalRows[i], t, GlobalDelay \u002B i * GlobalStagger );\n\n\t\tAnimate( _globalNote, t, GlobalDelay \u002B _globalRows.Length * GlobalStagger );\n\t\tAnimate( _back, t, GlobalDelay \u002B _globalRows.Length * GlobalStagger \u002B 0.3f );\n\n\t\t// \u81EA\u52A8\u8FD4\u56DE\uFF08v0.6.4.2\uFF09\uFF1A\u7ED3\u7B97\u663E\u793A 10 \u79D2\u540E\u56DE\u5927\u5385\uFF08\u591A\u4EBA\uFF09/\u4E3B\u83DC\u5355\uFF08\u5355\u673A\uFF09\uFF0C\u7A7A\u683C\u53EF\u63D0\u524D\n\t\tvar remain = GameConfig.SettlementAutoReturnSeconds - _sinceShow;\n\t\tif ( remain \u003C= 0f )\n\t\t{\n\t\t\tCircleroyaleGame.Current?.AfterSettlement();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( _back.IsValid() )\n\t\t\t_back.Text = Networking.IsActive\n\t\t\t\t? $\u0022RETURN TO LOBBY IN {MathF.Ceiling( remain ):0} \u2014 [SPACE NOW]\u0022\n\t\t\t\t: $\u0022BACK TO MENU IN {MathF.Ceiling( remain ):0} \u2014 [SPACE NOW]\u0022;\n\t}\n\n\t/// \u003Csummary\u003E \u5355\u884C\u6E10\u5165\uFF1A\u51FA\u573A\u524D Opacity 0\uFF1B0.25s \u5185\u900F\u660E\u5EA6 0\u21921\u3001PaddingLeft 36px\u21920\uFF08\u89C6\u89C9\u4E0A\u4ECE\u53F3\u6ED1\u5230\u4F4D\uFF09 \u003C/summary\u003E\n\tvoid Animate( Label row, float now, float revealAt )\n\t{\n\t\tif ( row is null || !row.IsValid() ) return;\n\n\t\tfloat k = Math.Clamp( ( now - revealAt ) / 0.25f, 0f, 1f );\n\t\tif ( k \u003C= 0f )\n\t\t{\n\t\t\trow.Style.Opacity = 0f;\n\t\t\treturn;\n\t\t}\n\n\t\trow.Style.Opacity = k;\n\t\trow.Style.PaddingLeft = ( 1f - k ) * 36f;\n\t}\n}\n"},{"Ident":"slfy.circleroyale","Path":"Game/PowerUpManager.cs","FileName":"PowerUpManager.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\n\n/// \u003Csummary\u003E\n/// \u9053\u5177\u7BA1\u7406\uFF08v0.7.3.0\uFF09\uFF1A**host \u6743\u5A01 \u002B \u9759\u6001 RPC \u540C\u6B65**\uFF0C\u4E0E FoodManager \u540C\u6B3E\u6A21\u5F0F\n/// \uFF08\u7EAF\u6570\u636E\u5B9E\u4F53\u4E0D\u8D70\u7F51\u7EDC\u5BF9\u8C61\u2014\u2014\u8FD0\u884C\u65F6 live create \u4E0D\u590D\u5236\u5230\u5BA2\u6237\u7AEF\uFF0C\u5B9E\u6D4B\uFF09\u3002\n/// \u56FA\u5B9A\u69FD\u4F4D\u5236\uFF1A\u573A\u4E0A\u6052 PowerUpOnField \u4E2A\u69FD\uFF0C\u88AB\u6361\u540E\u540C\u69FD\u5EF6\u65F6\u91CD\u5237\uFF08\u6362\u79CD\u7C7B\u6362\u4F4D\u7F6E\uFF09\uFF0C\n/// \u5168\u91CF\u540C\u6B65\u53EA\u6709\u4E00\u4E2A\u5C0F\u6570\u7EC4\uFF0C\u4E2D\u9014\u52A0\u5165/\u70ED\u91CD\u8F7D\u6062\u590D\u90FD\u4FBF\u5B9C\u3002\n/// \u62FE\u53D6\u4E3B\u7403\u4E0E\u5206\u8EAB\u90FD\u7B97\uFF08v0.7.8.6\uFF0C\u5B58\u4E3B\u4EBA\u80CC\u5305\uFF09\uFF1B\u6548\u679C\u5728\u62FE\u53D6\u77AC\u95F4\u5E94\u7528\u5230 Ball\uFF08buff \u6302\u73A9\u5BB6\uFF0C\u8D28\u91CF\u7F50\u5373\u65F6\u52A0\u8D28\u91CF\uFF09\u3002\n/// Init/Tick \u4EC5\u6743\u5A01\u7AEF\u8C03\u7528\uFF1BApply* \u4EC5\u4F9B\u5BA2\u6237\u7AEF\u5E94\u7528\u8FDC\u7AEF\u4E8B\u4EF6\u3002\n/// \u003C/summary\u003E\npublic sealed class PowerUpManager\n{\n\t/// \u003Csummary\u003E \u9053\u5177\u79CD\u7C7B\uFF08HUD/\u6E32\u67D3/\u6548\u679C switch \u5171\u7528\uFF1BNone=255 \u4F5C Ball.BuffKind \u7684\u0022\u65E0 buff\u0022\u54E8\u5175\uFF09 \u003C/summary\u003E\n\tpublic enum Kind : byte\n\t{\n\t\tSpeed = 0,    // \u26A1 \u52A0\u901F\uFF1A8s \u79FB\u901F \u00D71.4\uFF08\u9EC4\uFF09\n\t\tMagnet = 1,   // \uD83E\uDDF2 \u78C1\u94C1\uFF1A8s \u5438\u98DF\u534A\u5F84\u7FFB\u500D\uFF08\u9752\uFF09\n\t\tMass = 2,     // \uD83D\uDC8A \u8D28\u91CF\u7F50\uFF1A\u002B300 \u5373\u65F6\uFF08\u54C1\u7EA2\uFF09\n\t\tShield = 3,   // \uD83D\uDEE1 \u62A4\u76FE\uFF1A5s \u4E0D\u53EF\u88AB\u5927\u7403\u5403\uFF08\u84DD\uFF09\n\t\tSpike = 4,    // \uD83D\uDCA5 \u5C16\u523A\u5206\u8EAB\uFF1A\u751F\u6210\u8DDF\u968F\u4E3B\u4EBA 20s \u7684\u5C16\u523A\uFF08\u7D2B\u7EFF\uFF09\uFF0C\u649E\u7834\u6BD4\u81EA\u5DF1\u5927\u7684\u7403\u5956 3s \u65E0\u654C\n\t}\n\n\tpublic const byte None = 255;\n\n\t/// \u003Csummary\u003E \u69FD\u4F4D\u6E32\u67D3\u8272\uFF08\u4E0E\u9713\u8679\u8C03\u8272\u677F\u72EC\u7ACB\u2014\u2014\u9053\u5177\u8981\u4E00\u773C\u548C\u98DF\u7269\u533A\u5206\u5F00\uFF09 \u003C/summary\u003E\n\tpublic static Color ColorOf( byte kind )\n\t{\n\t\tswitch ( kind )\n\t\t{\n\t\t\tcase (byte)Kind.Speed: return new Color( 1.00f, 0.85f, 0.25f );\n\t\t\tcase (byte)Kind.Magnet: return new Color( 0.30f, 0.90f, 1.00f );\n\t\t\tcase (byte)Kind.Mass: return new Color( 1.00f, 0.40f, 0.70f );\n\t\t\tcase (byte)Kind.Shield: return new Color( 0.40f, 0.55f, 1.00f );\n\t\t\tcase (byte)Kind.Spike: return new Color( 0.55f, 1.00f, 0.45f );   // \u4EAE\u7EFF\uFF08\u91CE\u523A\u7D2B=\u969C\u788D\uFF0C\u7EFF\u8272=\u53EF\u64CD\u7EB5\u7684\u6B66\u5668\uFF09\n\t\t\tdefault: return new Color( 0.6f, 0.6f, 0.6f );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E HUD/\u56FE\u4F8B\u7528\u7684\u9053\u5177\u540D \u003C/summary\u003E\n\tpublic static string NameOf( byte kind )\n\t{\n\t\tswitch ( kind )\n\t\t{\n\t\t\tcase (byte)Kind.Speed: return \u0022SPEED\u0022;\n\t\t\tcase (byte)Kind.Magnet: return \u0022MAGNET\u0022;\n\t\t\tcase (byte)Kind.Shield: return \u0022SHIELD\u0022;\n\t\t\tcase (byte)Kind.Spike: return \u0022SPIKE\u0022;\n\t\t\tcase (byte)Kind.Mass: return \u0022\u002BMASS\u0022;\n\t\t\tdefault: return \u0022\u0022;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u9053\u5177\u69FD\uFF08\u6E32\u67D3\u4E0E HUD \u8BFB\uFF1Bhost \u6743\u5A01\uFF0C\u5BA2\u6237\u7AEF\u4E3A\u4E8B\u4EF6\u955C\u50CF\uFF09 \u003C/summary\u003E\n\tpublic sealed class Slot\n\t{\n\t\tpublic bool Alive;\n\t\tpublic byte Kind;\n\t\tpublic Vector2 Pos;\n\t\tpublic TimeSince SincePicked;   // \u4EC5 host\uFF1A\u91CD\u5237\u8BA1\u65F6\n\t}\n\n\treadonly List\u003CSlot\u003E _slots = new();\n\n\t/// \u003Csummary\u003E \u5582\u98DF\u5171\u4EAB\u79EF\u5206\u6C60\uFF08\u6309\u69FD\uFF0C\u4EC5 host\uFF09\uFF1A\u8C01\u5410\u4E1D\u90FD\u5F80\u91CC\u5145\uFF0C\u79EF\u5206 \u2265 \u672C\u6B21\u5582\u98DF\u8005\u4F53\u91CD\u00D7PowerUpFeedRatio \u5373\u89E6\u53D1\u7ED9\u5582\u98DF\u8005\u2014\u2014\n\t/// \u5927\u7403\u5582\u5230\u4E00\u534A\u4F1A\u88AB\u5C0F\u7403\u51E0\u53E3\u62A2\u8D70\uFF08\u864E\u53E3\u593A\u98DF\uFF0Cv0.7.4.0 \u7528\u6237\u5B9A\u7A3F\uFF09 \u003C/summary\u003E\n\tfloat[] _feedScores = Array.Empty\u003Cfloat\u003E();\n\n\t/// \u003Csummary\u003E Q \u6280\u80FD\u5145\u80FD\u8BA1\u65F6\uFF08\u6309 SteamId\uFF0C\u4EC5 host\uFF1Bv0.7.8.30 Q \u6539\u968F\u65F6\u95F4\u5145\u80FD\uFF0CE \u4ECD\u62FE\u53D6\uFF09 \u003C/summary\u003E\n\treadonly Dictionary\u003Clong, TimeSince\u003E _qCharge = new();\n\n\t/// \u003Csummary\u003E Q \u69FD\u5F53\u524D\u6301\u6709\u6280\u80FD\u7684\u7403\uFF08\u51B7\u5374\u6682\u505C\u6807\u8BB0\uFF0C\u68C0\u6D4B\u0022\u653E\u8D70\u0022\u7684\u8F6C\u53D8\uFF09 \u003C/summary\u003E\n\treadonly HashSet\u003Clong\u003E _qHeld = new();\n\n\t/// \u003Csummary\u003E \u5168\u90E8\u69FD\u4F4D\uFF08NeonRenderer \u6BCF\u5E27\u8BFB\uFF0C\u5404\u7AEF\u5185\u5BB9\u4E00\u81F4\uFF09 \u003C/summary\u003E\n\tpublic IReadOnlyList\u003CSlot\u003E Slots =\u003E _slots;\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF/\u70ED\u91CD\u8F7D\u540E\u672C\u5730\u5217\u8868\u672A\u5C31\u7EEA\uFF08\u4E0D\u6E32\u67D3\u4E5F\u4E0D\u8BEF\u5224\uFF09 \u003C/summary\u003E\n\tpublic bool Ready =\u003E _slots.Count \u003E 0;\n\n\t/// \u003Csummary\u003E host\uFF1A\u751F\u6210\u5168\u90E8\u69FD\u4F4D\u5E76\u5E7F\u64AD\u5168\u91CF\uFF08\u5F00\u5C40/\u6F14\u793A\u8D5B\u8C03\u7528\uFF09 \u003C/summary\u003E\n\tpublic void Init()\n\t{\n\t\t_slots.Clear();\n\t\tfor ( int i = 0; i \u003C GameConfig.PowerUpOnField; i\u002B\u002B )\n\t\t{\n\t\t\t_slots.Add( new Slot\n\t\t\t{\n\t\t\t\tAlive = true,\n\t\t\t\tKind = RandomKind(),\n\t\t\t\tPos = RandomPos(),\n\t\t\t} );\n\t\t}\n\n\t\tif ( Networking.IsActive ) NetworkManager.PowerUpFull( ToWire() );\n\t\t_feedScores = new float[_slots.Count];\n\t}\n\n\t/// \u003Csummary\u003E \u6E05\u7A7A\uFF08\u56DE\u4E3B\u83DC\u5355\uFF1B\u5BA2\u6237\u7AEF\u7ECF PowerUpFull \u7A7A\u6570\u7EC4\u540C\u6B65\uFF09 \u003C/summary\u003E\n\tpublic void Reset()\n\t{\n\t\t_slots.Clear();\n\t\t_feedScores = Array.Empty\u003Cfloat\u003E();\n\t}\n\n\t/// \u003Csummary\u003E host\uFF1A\u62FE\u53D6\u5224\u5B9A\uFF08\u4E3B\u7403 \u002B \u5206\u8EAB\u5171\u7528\uFF09\u002B \u5230\u671F\u69FD\u4F4D\u91CD\u5237\u3002\u4EC5\u6743\u5A01\u7AEF\u6BCF\u5E27\u8C03\u7528\u3002\n\t/// v0.7.5.0 \u8D77\u62FE\u53D6**\u5B58\u5165\u80CC\u5305**\uFF08bot \u4F8B\u5916\uFF1A\u62FE\u53D6\u5373\u7528\uFF09\u2014\u2014\u80CC\u5305\u6EE1\u5219\u9053\u5177\u7559\u5728\u573A\u4E0A\u4E0D\u88AB\u6361\uFF1B\n\t/// v0.7.8.6 \u8D77\u5206\u8EAB\uFF08SplitPiece\uFF09\u4E5F\u8D70\u540C\u4E00\u5224\u5B9A\uFF0C\u6361\u5230\u5B58\u4E3B\u4EBA\u80CC\u5305 \u003C/summary\u003E\n\tpublic void Tick( List\u003CBall\u003E balls, List\u003CCellPiece\u003E cells = null )\n\t{\n\t\tif ( _slots.Count == 0 ) return;\n\n\t\tforeach ( var ball in balls )\n\t\t{\n\t\t\tif ( !ball.IsValid() || !ball.Alive ) continue;\n\t\t\tPickupPass( ball, ball.WorldPosition, ball.Radius );\n\t\t}\n\n\t\t// \u5206\u8EAB\u62FE\u53D6\uFF08v0.7.8.6\uFF09\uFF1A\u6361\u5230\u5B58\u4E3B\u4EBA\u80CC\u5305\uFF08\u540C\u4E00 Q/E \u80CC\u5305\uFF09\uFF1B\u5B62\u5B50\u4E0D\u6361\uFF08\u4E2D\u6027\u98DF\u7269\u56E2\uFF09\uFF0C\n\t\t// \u5C16\u523A\u5206\u8EAB\u4E0D\u6361\uFF08\u6B66\u5668\u4E0D\u517C\u804C\u53D6\u8D27\uFF09\u3002\u4E3B\u4EBA\u6B7B\u4EA1\u7684\u5206\u8EAB\u4E0B\u4E00\u5E27 TickCells \u624D\u6E05\uFF0C\u8FD9\u91CC\u987A\u5E26\u8FC7\u6EE4\u3002\n\t\t// \u26A0\uFE0F \u5012\u5E8F for\uFF1Abot \u62FF\u7740\uFF0820s \u540E\u8F6C\u6B63\u7684\uFF09\u5C16\u523A\u5206\u8EAB\u8E29\u5230\u5C16\u523A\u9053\u5177 \u2192 \u62FE\u53D6\u5373\u7528 \u2192\n\t\t// SpawnSpikeMinion \u5F80 _cells \u8FFD\u52A0\u2014\u2014foreach \u679A\u4E3E\u4E2D\u6539\u96C6\u5408\u4F1A\u70B8\uFF08v0.7.8.20 \u4FEE\uFF09\n\t\tif ( cells is not null )\n\t\t{\n\t\t\tfor ( int i = cells.Count - 1; i \u003E= 0; i-- )\n\t\t\t{\n\t\t\t\tvar c = cells[i];\n\t\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\t\tif ( !c.OwnerBall.IsValid() || !c.OwnerBall.Alive ) continue;\n\t\t\t\tPickupPass( c.OwnerBall, c.Pos, c.Radius );\n\t\t\t}\n\t\t}\n\n\t\t// \u7A7A\u69FD\u91CD\u5237\n\t\tfor ( int i = 0; i \u003C _slots.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar s = _slots[i];\n\t\t\tif ( s.Alive || s.SincePicked \u003C GameConfig.PowerUpRefillSeconds ) continue;\n\n\t\t\ts.Alive = true;\n\t\t\ts.Kind = RandomKind();\n\t\t\ts.Pos = RandomPos();\n\t\t\tif ( i \u003C _feedScores.Length ) _feedScores[i] = 0f;   // \u91CD\u5237\u79EF\u5206\u6E05\u96F6\uFF08\u57F9\u517B\u8FDB\u5EA6\u4E0D\u4F5C\u6570\uFF09\n\t\t\tif ( Networking.IsActive ) NetworkManager.PowerUpRespawned( i, s.Kind, s.Pos.x, s.Pos.y );\n\t\t}\n\n\t\t// Q \u6280\u80FD\u968F\u65F6\u95F4\u5145\u80FD\uFF08v0.7.8.30 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u771F\u4EBA\u7403 Q \u69FD\u7A7A\u65F6\u6309**\u5F53\u524D\u8D28\u91CF**\u8BA1\u65F6\u5145\u80FD\n\t\t// \uFF0815~60s\uFF0C\u8D8A\u8F7B\u8D8A\u5FEB\uFF09\uFF0C\u5230\u70B9\u81EA\u52A8\u53D1\u4E00\u4E2A\u968F\u673A\u6280\u80FD\u3002bot \u62FE\u53D6\u5373\u7528\u65E0\u80CC\u5305\uFF0C\u4E0D\u53C2\u4E0E\uFF1B\n\t\t// \u6301\u6709\u672A\u653E\u65F6\u51B7\u5374\u6682\u505C\uFF08\u6280\u80FD\u8EBA\u5728\u69FD\u91CC\u7B49\u6309\u952E\uFF09\u3002\u8BA1\u65F6\u6309 SteamId \u8BB0\uFF08\u8DE8\u70ED\u91CD\u8F7D\u7A33\u5B9A\uFF09\n\t\tforeach ( var b in balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.Alive || b.IsBot ) continue;\n\n\t\t\tvar sid = b.OwnerSteamId;\n\t\t\tif ( b.PowerA != None )\n\t\t\t{\n\t\t\t\t_qHeld.Add( sid );   // \u6280\u80FD\u5F85\u653E\uFF1A\u51B7\u5374\u6682\u505C\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( _qHeld.Remove( sid ) )\n\t\t\t{\n\t\t\t\t_qCharge[sid] = 0;   // \u521A\u653E\u8D70/\u590D\u6D3B\u6E05\u7A7A\uFF1A\u51B7\u5374\u4ECE\u73B0\u5728\u8D77\u7B97\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( !_qCharge.TryGetValue( sid, out var ts ) )\n\t\t\t{\n\t\t\t\t_qCharge[sid] = 0;   // \u9996\u89C1\uFF08\u5165\u5C40\uFF09\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( ts \u003C GameConfig.QSkillCooldownFor( b.Mass ) ) continue;\n\n\t\t\t_qCharge[sid] = 0;\n\t\t\tb.PowerA = RandomKind();\n\t\t\tif ( GameSfx.IsMine( b.OwnerSteamId ) )\n\t\t\t{\n\t\t\t\tGameSfx.Pickup();\n\t\t\t\tCircleroyaleGame.Current?.Hud?.AddBanner( $\u0022{NameOf( b.PowerA )} READY (Q)\u0022, ColorOf( b.PowerA ) );\n\t\t\t}\n\t\t\t// \u5BA2\u6237\u7AEF\u73A9\u5BB6\u7684 Q \u5230\u70B9\uFF1A\u6A2A\u5E45\u002B\u97F3\u6548\u8D70\u5E7F\u64AD\uFF08mode2\uFF09\uFF0Chost \u56DE\u58F0\u7531 OnPowerBannerRemote \u7684 IsAuthority \u6321\u6389\uFF08v0.7.8.31\uFF09\n\t\t\tif ( Networking.IsActive ) NetworkManager.PowerBanner( b.PowerA, b.OwnerSteamId, 2 );\n\t\t\tGameLog.Info( $\u0022[game] q skill granted: {NameOf( b.PowerA )} -\u003E {b.PlayerName}\u0022 );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u4E00\u4E2A\u0022\u62FE\u53D6\u4F53\u0022\uFF08\u4E3B\u7403\u6216\u5206\u8EAB\uFF09\u626B\u4E00\u904D\u69FD\u4F4D\u3002bot \u62FE\u53D6\u5373\u7528\uFF1B\u771F\u4EBA\u5B58\u5165 Q/E \u80CC\u5305\n\t/// \uFF08\u6548\u679C\u7B49\u6309\u952E\u91CA\u653E\uFF09\uFF0C\u80CC\u5305\u6EE1\u5219\u9053\u5177\u7559\u5728\u573A\u4E0A\u3002\u5224\u5B9A\u5708\u5C01\u9876\u53CD\u9A6C\u592A\uFF08\u5927\u7403\u8981\u523B\u610F\u78BE\u4E0A\u53BB\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tvoid PickupPass( Ball receiver, Vector3 pos, float radius )\n\t{\n\t\t// \u62FE\u53D6\u5224\u5B9A\u5C01\u9876\uFF08v0.7.4.0 \u53CD\u9A6C\u592A\uFF09\uFF1A\u5927\u7403\u4E0D\u518D\u0022\u8DEF\u8FC7\u987A\u8D70\u0022\u2014\u2014\u5224\u5B9A\u5708\u6309\u5C01\u9876\u534A\u5F84\u7B97\uFF0C\n\t\t// \u5FC5\u987B\u523B\u610F\u78BE\u5230\u9053\u5177\u6B63\u4E0A\u65B9\u624D\u5403\u5F97\u5230\uFF1B\u5C0F\u7403\u53CD\u800C\u8F7B\u677E\n\t\tvar rr = MathF.Min( radius, GameConfig.PowerUpPickRadiusCap ) \u002B GameConfig.PowerUpRadius;\n\n\t\tfor ( int i = 0; i \u003C _slots.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar s = _slots[i];\n\t\t\tif ( !s.Alive ) continue;\n\n\t\t\tvar dx = s.Pos.x - pos.x;\n\t\t\tvar dy = s.Pos.y - pos.y;\n\t\t\tif ( dx * dx \u002B dy * dy \u003E rr * rr ) continue;\n\n\t\t\tif ( receiver.IsBot )\n\t\t\t{\n\t\t\t\tConsume( i, receiver );   // bot \u4E0D\u56E4\uFF1A\u62FE\u53D6\u5373\u89E6\u53D1\u6548\u679C\uFF08banner \u7531 Consume \u5185 IsMine \u6321\u6389\uFF09\n\t\t\t}\n\t\t\telse if ( receiver.StorePower( s.Kind ) \u003E= 0 )\n\t\t\t{\n\t\t\t\t// \u771F\u4EBA\uFF1A\u5B58\u5165 Q/E \u80CC\u5305\u69FD\uFF08\u6548\u679C\u7B49\u73A9\u5BB6\u6309\u952E\u91CA\u653E\uFF09\uFF0C\u9053\u5177\u69FD\u91CD\u5237\n\t\t\t\ts.Alive = false;\n\t\t\t\ts.SincePicked = 0;\n\n\t\t\t\tNeonRenderer.Spark( new Vector3( s.Pos.x, s.Pos.y, 0f ), ColorOf( s.Kind ) );\n\t\t\t\tif ( GameSfx.IsMine( receiver.OwnerSteamId ) )\n\t\t\t\t{\n\t\t\t\t\tGameSfx.EatFood( new Vector3( s.Pos.x, s.Pos.y, 0f ) );\n\t\t\t\t\tGameAchievements.PowerStored();   // \u6210\u5C31\uFF1A\u7D2F\u8BA1\u6361 5 \u4E2A\u5165\u5305\uFF08host \u672C\u673A\u4FA7\uFF09\n\t\t\t\t}\n\t\t\t\tif ( Networking.IsActive ) NetworkManager.PowerUpPicked( i, s.Kind, receiver.OwnerSteamId );\n\n\t\t\t\tShowBanner( receiver, s.Kind, stored: true );\n\t\t\t\tGameLog.Info( $\u0022[game] powerup stored: {NameOf( s.Kind )} -\u003E {receiver.PlayerName}\u0022 );\n\t\t\t}\n\t\t\t// \u80CC\u5305\u6EE1\uFF1A\u4E0D\u6D88\u8017\uFF0C\u9053\u5177\u7559\u5728\u573A\u4E0A\uFF08\u4E0B\u4E00\u9897\u7403/\u4E0B\u4E2A\u5E27\u518D\u5224\uFF09\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u6D88\u8017\u9053\u5177\u5E76\u89E6\u53D1\u6548\u679C\uFF08bot \u62FE\u53D6\u5373\u7528 / \u5582\u98DF\u89E6\u53D1\u5171\u7528\u8DEF\u5F84\uFF1B\u69FD\u91CD\u5237\uFF09 \u003C/summary\u003E\n\tvoid Consume( int index, Ball receiver )\n\t{\n\t\tif ( (uint)index \u003E= (uint)_slots.Count ) return;\n\t\tvar s = _slots[index];\n\t\tif ( !s.Alive ) return;\n\n\t\ts.Alive = false;\n\t\ts.SincePicked = 0;\n\t\tif ( index \u003C _feedScores.Length ) _feedScores[index] = 0f;\n\n\t\tApplyEffect( receiver, s.Kind );\n\t\tNeonRenderer.Spark( new Vector3( s.Pos.x, s.Pos.y, 0f ), ColorOf( s.Kind ) );\n\t\tif ( GameSfx.IsMine( receiver.OwnerSteamId ) )\n\t\t\tGameSfx.EatFood( new Vector3( s.Pos.x, s.Pos.y, 0f ) );\n\n\t\tShowBanner( receiver, s.Kind, stored: false );\n\t\tif ( Networking.IsActive ) NetworkManager.PowerUpPicked( index, s.Kind, receiver.OwnerSteamId );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u4E2A\u4EBA\u9AD8\u5149\u6A2A\u5E45\uFF08v0.7.6.0\uFF09\uFF1Ahost \u672C\u5730\u64AD\u81EA\u5DF1\u7684 \u002B \u5E7F\u64AD\u7ED9\u5176\u4ED6\u7AEF\uFF08\u5BA2\u6237\u7AEF\u6309 IsMine \u518D\u663E\u793A\uFF09\u3002\n\t/// stored=true \u0022XXX STORED\u0022\uFF08\u6361\u5230\u5165\u80CC\u5305\uFF09/ false \u6FC0\u6D3B\u6587\u6848\uFF08\u5582\u517B\u89E6\u53D1/bot \u62FE\u53D6\u5373\u7528\uFF09\u3002\n\t/// v0.7.7.0\uFF1A\u6A2A\u5E45\u914D\u5957\u0022\u53EE\u549A\u0022\u97F3\uFF08\u672C\u673A\u8033\u6735\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tvoid ShowBanner( Ball receiver, byte kind, bool stored )\n\t{\n\t\tvar text = stored ? $\u0022{NameOf( kind )} STORED\u0022 : GameHud.ActivationText( kind );\n\t\tif ( GameSfx.IsMine( receiver.OwnerSteamId ) )\n\t\t{\n\t\t\tCircleroyaleGame.Current?.Hud?.AddBanner( text, ColorOf( kind ) );\n\t\t\tGameSfx.Pickup();\n\t\t}\n\n\t\tif ( Networking.IsActive ) NetworkManager.PowerBanner( kind, receiver.OwnerSteamId, (byte)( stored ? 0 : 1 ) );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// host\uFF1A\u5B62\u5B50\u5582\u9053\u5177\uFF08\u0022\u8840\u91CF\u6C60\u0022\uFF09\u2014\u2014\u5171\u4EAB\u79EF\u5206\u6C60\u6309\u69FD\u7D2F\u8BA1\uFF08\u8C01\u5410\u4E1D\u90FD\u5F80\u91CC\u5145\uFF09\uFF0C\n\t/// \u79EF\u5206 \u2265 \u672C\u6B21\u5582\u98DF\u8005\u5F53\u524D\u4F53\u91CD \u00D7 PowerUpFeedRatio \u5373\u89E6\u53D1\u6548\u679C\u7ED9\u5582\u98DF\u8005\uFF08**\u7ACB\u5373\u751F\u6548\uFF0C\u4E0D\u5165\u80CC\u5305**\uFF09\u3002\n\t/// \u5927\u7403\u8981\u5582\u5230 5%\u00D7\u5927\u4F53\u91CD\u624D\u8F6E\u5230\u81EA\u5DF1\u8FBE\u6807\uFF0C\u5C0F\u7403\u51E0\u53E3\u5C31\u591F\u2014\u2014\u4E2D\u9014\u622A\u80E1\u5373\u0022\u864E\u53E3\u593A\u98DF\u0022\u3002\n\t/// feeder \u65E0\u6548\uFF08\u65AD\u7EBF/\u6B7B\u4EA1\u77AC\u95F4\uFF09\u53EA\u5145\u80FD\u4E0D\u5224\u5B9A\u3002\n\t/// \u003C/summary\u003E\n\tpublic void Feed( Ball feeder, int index, float blobMass )\n\t{\n\t\tif ( (uint)index \u003E= (uint)_slots.Count || !_slots[index].Alive ) return;\n\t\tif ( index \u003E= _feedScores.Length ) Array.Resize( ref _feedScores, _slots.Count );\n\n\t\t_feedScores[index] \u002B= blobMass;\n\n\t\tif ( feeder is null || !feeder.IsValid() || !feeder.Alive ) return;\n\t\tif ( _feedScores[index] \u003C feeder.Mass * GameConfig.PowerUpFeedRatio ) return;\n\n\t\tvar kind = _slots[index].Kind;\n\t\tConsume( index, feeder );\n\t\tGameAchievements.FeedTriggered( feeder.OwnerSteamId );   // \u6210\u5C31\uFF1A\u9996\u6B21\u5582\u98DF\u89E6\u53D1\uFF08host \u672C\u673A\u4FA7\uFF09\n\t\tGameLog.Info( $\u0022[game] powerup fed to trigger: {NameOf( kind )} -\u003E {feeder.PlayerName}\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u9053\u5177\u6548\u679C\uFF08host \u6743\u5A01\uFF1B\u8D28\u91CF\u7F50\u5373\u65F6\u5230\u8D26\uFF0C\u5176\u4F59\u6302 buff\uFF0C\u5C16\u523A\u5206\u8EAB\u5728\u6E38\u620F\u7CFB\u7EDF\u751F\u6210\uFF09\u3002\n\t/// \u62FE\u53D6\u5B58\u80CC\u5305\uFF08v0.7.5.0\uFF09\uFF1A\u771F\u4EBA\u6309\u952E UsePower \u65F6\u624D\u8D70\u5230\u8FD9\u91CC \u003C/summary\u003E\n\tpublic static void ApplyEffect( Ball ball, byte kind )\n\t{\n\t\tswitch ( kind )\n\t\t{\n\t\t\tcase (byte)Kind.Speed:\n\t\t\t\tball.ApplyBuff( kind, GameConfig.PowerUpSpeedSeconds );\n\t\t\t\tbreak;\n\t\t\tcase (byte)Kind.Magnet:\n\t\t\t\tball.ApplyBuff( kind, GameConfig.PowerUpMagnetSeconds );\n\t\t\t\tbreak;\n\t\t\tcase (byte)Kind.Shield:\n\t\t\t\tball.ApplyBuff( kind, GameConfig.PowerUpShieldSeconds );\n\t\t\t\tbreak;\n\t\t\tcase (byte)Kind.Mass:\n\t\t\t\tball.Mass = MathF.Min( GameConfig.MaxMass, ball.Mass \u002B GameConfig.PowerUpMassBonus );\n\t\t\t\tbreak;\n\t\t\tcase (byte)Kind.Spike:\n\t\t\t\tCircleroyaleGame.Current?.SpawnSpikeMinion( ball );\n\t\t\t\tbreak;\n\t\t}\n\n\t\tGameLog.Info( $\u0022[game] powerup picked: {NameOf( kind )} -\u003E {ball.PlayerName}\u0022 );\n\t}\n\n\tbyte RandomKind() =\u003E (byte)Game.Random.Float( 0f, 4.999f );\n\n\tVector2 RandomPos()\n\t{\n\t\tvar half = GameConfig.ArenaHalfSize - GameConfig.PowerUpMargin;\n\t\treturn new Vector2( Game.Random.Float( -half, half ), Game.Random.Float( -half, half ) );\n\t}\n\n\t/// \u003Csummary\u003E \u5168\u91CF\u5FEB\u7167\uFF08\u5F00\u5C40\u8865\u53D1/\u65B0\u8FDE\u63A5\u5B9A\u5411\u7528\uFF1Bhost \u4FA7\u8C03\u7528\uFF09 \u003C/summary\u003E\n\tpublic PowerUpWire[] ToWireForSync() =\u003E ToWire();\n\n\tPowerUpWire[] ToWire()\n\t{\n\t\tvar wires = new PowerUpWire[_slots.Count];\n\t\tfor ( int i = 0; i \u003C _slots.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar s = _slots[i];\n\t\t\twires[i] = new PowerUpWire\n\t\t\t{\n\t\t\t\tKind = s.Alive ? s.Kind : None,\n\t\t\t\tX = s.Pos.x,\n\t\t\t\tY = s.Pos.y,\n\t\t\t};\n\t\t}\n\t\treturn wires;\n\t}\n\n\t// ---- \u5BA2\u6237\u7AEF\u5E94\u7528\uFF08\u8FDC\u7AEF\u4E8B\u4EF6 \u2192 \u672C\u5730\u69FD\u4F4D\uFF09----\n\n\tpublic void ApplyFull( PowerUpWire[] wires )\n\t{\n\t\t_slots.Clear();\n\t\tif ( wires is null ) return;\n\n\t\tforeach ( var w in wires )\n\t\t{\n\t\t\t_slots.Add( new Slot\n\t\t\t{\n\t\t\t\tAlive = w.Kind != None,\n\t\t\t\tKind = w.Kind,\n\t\t\t\tPos = new Vector2( w.X, w.Y ),\n\t\t\t} );\n\t\t}\n\t}\n\n\tpublic void ApplyPicked( int index )\n\t{\n\t\tif ( (uint)index \u003E= (uint)_slots.Count ) return;   // \u70ED\u91CD\u8F7D\u540E\u672A\u540C\u6B65\u5168\u91CF\u524D\uFF0C\u5FFD\u7565\u589E\u91CF\n\t\t_slots[index].Alive = false;\n\t}\n\n\tpublic void ApplyRespawned( int index, byte kind, Vector2 pos )\n\t{\n\t\tif ( (uint)index \u003E= (uint)_slots.Count ) return;\n\n\t\tvar s = _slots[index];\n\t\ts.Alive = true;\n\t\ts.Kind = kind;\n\t\ts.Pos = pos;\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"UI/MainMenu.cs","FileName":"MainMenu.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using Sandbox.UI;\n\n/// \u003Csummary\u003E\n/// \u4E3B\u83DC\u5355\uFF08M4\uFF09\uFF1A\u5EFA\u670D / \u52A0\u5165 / \u4EBA\u673A\u5BF9\u6218 / \u9000\u51FA\u3002\u7EAF C# Panel\uFF08\u7F16\u8F91\u5668\u70ED\u91CD\u8F7D\u53CB\u597D\uFF0C\u540C GameHud \u6A21\u5F0F\uFF09\u3002\n/// \u83DC\u5355\u9636\u6BB5\u4E16\u754C\u0022\u58F3\u0022\u5DF2\u5EFA\u597D\uFF08\u7F51\u683C \u002B \u76F8\u673A\u7167\u5E38\u6E32\u67D3\uFF09\uFF0C\u83DC\u5355\u534A\u900F\u660E\u538B\u6697\u76D6\u5728\u4E0A\u9762\u3002\n/// M5\uFF1A\u6BD4\u8D5B\u53C2\u6570\u8BBE\u7F6E\u79FB\u8FDB\u4E86**\u6E38\u620F\u623F\u95F4**\uFF08LobbyPanel \u4E8C\u7EA7\u754C\u9762\uFF09\u2014\u2014HOST/VS BOTS \u8FDB\u623F\u8C03\u53C2\uFF0C\u70B9 START \u5F00\u5C40\u3002\n/// \u70B9\u51FB \u002B \u952E\u76D8\u5FEB\u6377\u952E\u53CC\u901A\u9053\uFF08\u7F16\u8F91\u5668\u5185\u5D4C\u89C6\u53E3\u53EF\u80FD\u6536\u4E0D\u5230\u9F20\u6807\u70B9\u51FB\uFF0C\u952E\u76D8\u4FDD\u5E95\uFF09\uFF1A\n/// 1=\u5EFA\u670D 2=\u52A0\u5165 3=\u4EBA\u673A 4=\u8BBE\u7F6E\u9875 5(\u6570\u5B57\u952E)=\u9000\u51FA\u3002\n/// \u6837\u5F0F\u5728\u540C\u76EE\u5F55 MainMenu.cs.scss\uFF08\u6309\u0022\u7C7B\u540D.cs.scss\u0022\u81EA\u52A8\u52A0\u8F7D\uFF09\u3002\n/// \u003C/summary\u003E\npublic sealed class MainMenu : PanelComponent\n{\n\tLabel _status;\n\tLabel _level;\n\tbool _built;\n\n\tprotected override void OnStart()\n\t{\n\t\tbase.OnStart();\n\t\tBuildUi();\n\t}\n\n\tvoid BuildUi()\n\t{\n\t\tvar root = Panel;\n\t\tif ( root is null ) return;\n\n\t\t// \u5DF2\u5EFA\u597D\u4E14\u5F15\u7528\u6709\u6548 \u2192 \u4E0D\u91CD\u590D\u5EFA\uFF08\u70ED\u91CD\u8F7D\u534A\u6B8B\u65F6\u6574\u4F53\u6E05\u7A7A\u91CD\u5EFA\uFF09\n\t\tif ( _built \u0026\u0026 _status.IsValid() \u0026\u0026 root.ChildrenCount \u003E 0 ) return;\n\n\t\troot.DeleteChildren( true );\n\n\t\tvar title = new Label() { Classes = \u0022title\u0022 };\n\t\ttitle.Text = \u0022CIRCLEROYALE\u0022;\n\t\troot.AddChild( title );\n\n\t\tvar sub = new Label() { Classes = \u0022subtitle\u0022 };\n\t\tsub.Text = \u0022NEON ARENA \u2014 EAT OR BE EATEN\u0022;\n\t\troot.AddChild( sub );\n\n\t\t// \u8D26\u53F7\u7B49\u7EA7\u89D2\u6807\uFF08v0.7.8.19\uFF09\uFF1A\u4E91\u7AEF XP \u5F02\u6B65\u52A0\u8F7D\uFF0CGameXp.LevelText \u672A\u5C31\u7EEA\u65F6\u7559\u767D\n\t\t_level = new Label() { Classes = \u0022mlevel\u0022 };\n\t\troot.AddChild( _level );\n\n\t\tAddButton( root, \u0022HOST GAME  \u2014  [1]\u0022, \u0022host\u0022 );\n\t\tAddButton( root, \u0022JOIN GAME  \u2014  [2]\u0022, \u0022join\u0022 );\n\t\tAddButton( root, \u0022VS BOTS    \u2014  [3]\u0022, \u0022bots\u0022 );\n\t\tAddButton( root, \u0022SETTINGS  \u2014  [4]\u0022, \u0022settings\u0022 );\n\t\tAddButton( root, \u0022RANKINGS  \u2014  [5]\u0022, \u0022rankings\u0022 );\n\t\tAddButton( root, \u0022QUIT       \u2014  [6]\u0022, \u0022quit\u0022 );\n\n\t\t_status = new Label() { Classes = \u0022status\u0022 };\n\t\troot.AddChild( _status );\n\n\t\tvar ver = new Label() { Classes = \u0022version\u0022 };\n\t\tver.Text = GameConfig.Version;\n\t\troot.AddChild( ver );\n\n\t\tvar hint = new Label() { Classes = \u0022hint\u0022 };\n\t\thint.Text = \u0022WASD MOVE \u2014 SPACE SPLIT \u2014 R FEED\u0022;\n\t\troot.AddChild( hint );\n\n\t\t_built = true;\n\t}\n\n\tvoid AddButton( Panel root, string text, string id )\n\t{\n\t\tvar b = new Label() { Classes = $\u0022btn {id}\u0022 };\n\t\tb.Text = text;\n\t\t// lambda \u5FC5\u987B\u6355\u83B7 this\uFF08\u8C03\u5B9E\u4F8B\u65B9\u6CD5\uFF09\u2014\u2014\u65E0\u6355\u83B7\u7684\u9759\u6001 lambda \u5728\u70ED\u91CD\u8F7D\u540E\u65E0\u6CD5\u91CD\u6620\u5C04\uFF0C\n\t\t// \u70B9\u51FB\u4F1A\u629B NoMatchLambda \u5F02\u5E38\uFF08\u5B9E\u6D4B\uFF1A\u6309\u94AE\u5168\u53D8\u0022\u70B9\u4E86\u6CA1\u53CD\u5E94\u0022\uFF09\n\t\tb.AddEventListener( \u0022onclick\u0022, () =\u003E OnMenuButton( id ) );\n\t\troot.AddChild( b );\n\t}\n\n\t/// \u003Csummary\u003E \u83DC\u5355\u6309\u94AE\u70B9\u51FB\uFF08\u5B9E\u4F8B\u65B9\u6CD5\uFF1A\u70ED\u91CD\u8F7D\u53EF\u91CD\u6620\u5C04\uFF09 \u003C/summary\u003E\n\tvoid OnMenuButton( string id ) =\u003E\n\t\tCircleroyaleGame.Current?.MenuAction( id switch { \u0022host\u0022 =\u003E 0, \u0022join\u0022 =\u003E 1, \u0022bots\u0022 =\u003E 2, \u0022settings\u0022 =\u003E 4, \u0022rankings\u0022 =\u003E 5, _ =\u003E 3 } );\n\n\tprotected override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\t// \u5F3A\u5236\u5149\u6807\u53EF\u89C1\u53EF\u4EA4\u4E92\uFF08\u9762\u677F\u9ED8\u8BA4 PointerEvents=None\uFF0CAuto \u6A21\u5F0F\u4E0B\u6CA1\u4EBA\u0022\u60F3\u8981\u9F20\u6807\u0022\u5149\u6807\u5C31\u6CA1\u4E86\uFF09\uFF1B\n\t\t// Hide() \u65F6\u6062\u590D Auto\u3002\u653E\u5728 OnUpdate \u6BCF\u5E27\u65AD\u8A00\uFF0C\u4EFB\u4F55\u6837\u5F0F\u7EA7\u8054\u610F\u5916\u90FD\u515C\u5F97\u4F4F\n\t\tMouse.Visibility = MouseVisibility.Visible;\n\n\t\tBuildUi();\n\n\t\t// \u952E\u76D8\u4FDD\u5E95\uFF08\u7F16\u8F91\u5668\u5185\u5D4C\u89C6\u53E3\u9F20\u6807\u4E8B\u4EF6\u4E0D\u53EF\u9760\uFF0C\u952E\u76D8\u4E00\u5B9A\u80FD\u8FDB\u6765\uFF09\uFF1A\n\t\t// [4] \u8BBE\u7F6E\u9875\uFF1B\u6570\u5B57 5/6 \u76F4\u8BFB\uFF08Slot5/Slot6 \u672A\u5728 Input \u914D\u7F6E\u91CC\uFF09= \u6392\u884C/\u9000\u51FA\n\t\tif ( Input.Pressed( \u0022Slot1\u0022 ) ) CircleroyaleGame.Current?.MenuAction( 0 );\n\t\tif ( Input.Pressed( \u0022Slot2\u0022 ) ) CircleroyaleGame.Current?.MenuAction( 1 );\n\t\tif ( Input.Pressed( \u0022Slot3\u0022 ) ) CircleroyaleGame.Current?.MenuAction( 2 );\n\t\tif ( Input.Pressed( \u0022Slot4\u0022 ) ) CircleroyaleGame.Current?.MenuAction( 4 );\n\t\tif ( Input.Keyboard.Pressed( \u00225\u0022 ) ) CircleroyaleGame.Current?.MenuAction( 5 );\n\t\tif ( Input.Keyboard.Pressed( \u00226\u0022 ) ) CircleroyaleGame.Current?.MenuAction( 3 );\n\n\t\t// \u7B49\u7EA7\u89D2\u6807\uFF08\u4E91\u7AEF XP \u5F02\u6B65\u52A0\u8F7D\uFF0CGameXp \u5185\u90E8\u8282\u6D41\u81EA\u6108\u91CD\u8BD5\uFF09\n\t\tGameXp.EnsureLocalXp();\n\t\tSetText( _level, GameXp.LevelText() );\n\t}\n\n\tstatic void SetText( Label l, string text )\n\t{\n\t\tif ( l is null || !l.IsValid() || l.Text == text ) return;\n\t\tl.Text = text;\n\t}\n\n\t/// \u003Csummary\u003E \u72B6\u6001\u884C\uFF08\u8FDE\u63A5\u4E2D / \u5931\u8D25\u91CD\u8BD5\u63D0\u793A\uFF09 \u003C/summary\u003E\n\tpublic void SetStatus( string text )\n\t{\n\t\tif ( _status.IsValid() ) _status.Text = text;\n\t}\n\n\t/// \u003Csummary\u003E \u5F00\u5C40\u540E\u9690\u85CF\u83DC\u5355\uFF08Panel \u65E0 Enabled\uFF0C\u76F4\u63A5\u7981\u6389\u6574\u4E2A GameObject\uFF09\uFF0C\u5E76\u628A\u5149\u6807\u8FD8\u7ED9\u6E38\u620F \u003C/summary\u003E\n\tpublic void Hide()\n\t{\n\t\tif ( !GameObject.Enabled ) return;   // \u5DF2\u9690\u85CF\uFF1A\u522B\u52A8\u5149\u6807\uFF08ReconcileUi \u6BCF\u5E27\u8C03\uFF0C\u623F\u95F4\u9875\u8981\u5149\u6807\u53EF\u70B9\uFF09\n\t\tMouse.Visibility = MouseVisibility.Auto;\n\t\tGameObject.Enabled = false;\n\t}\n\n\t/// \u003Csummary\u003E \u4ECE\u623F\u95F4/\u7ED3\u7B97\u56DE\u83DC\u5355\uFF08M5\uFF09\uFF1A\u91CD\u65B0\u663E\u793A\uFF08OnUpdate \u6062\u590D\u5149\u6807\u65AD\u8A00\uFF09 \u003C/summary\u003E\n\tpublic void Show()\n\t{\n\t\tGameObject.Enabled = true;\n\t\tBuildUi();\n\t\tif ( _status.IsValid() ) _status.Text = \u0022\u0022;\n\t}\n}\n"},{"Ident":"slfy.circleroyale","Path":"ui/settingspanel.cs.scss","FileName":"settingspanel.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"SettingsPanel {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\tfont-family: SimHei;\n\tcolor: white;\n\tpointer-events: all;\n\n\t.sp-title {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 88px;\n\t\tfont-size: 48px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 10px;\n\t\ttext-align: center;\n\t\tcolor: #35f0ff;\n\t\ttext-shadow: 0 0 26px rgba( 53, 240, 255, 0.85 );\n\t}\n\n\t.sp-hint {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 178px;\n\t\tfont-size: 13px;\n\t\tletter-spacing: 3px;\n\t\ttext-align: center;\n\t\tcolor: rgba( 255, 255, 255, 0.32 );\n\t}\n\n\t.sp-row {\n\t\tfont-size: 22px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 3px;\n\t\ttext-align: center;\n\t\tcolor: #9ad8ff;\n\t\ttext-shadow: 0 0 10px rgba( 0, 0, 0, 0.8 );\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t}\n\n\t.sp-row:hover {\n\t\tcolor: #ffffff;\n\t}\n\n\t.sp-back {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 936px;\n\t\tfont-size: 15px;\n\t\tletter-spacing: 4px;\n\t\ttext-align: center;\n\t\tcolor: rgba( 255, 255, 255, 0.45 );\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t}\n\n\t.sp-back:hover {\n\t\tcolor: rgba( 255, 255, 255, 0.9 );\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"Game/GameAchievements.cs","FileName":"GameAchievements.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\n\n/// \u003Csummary\u003E\n/// \u6210\u5C31\u5BF9\u63A5\uFF08v0.7.8.14\uFF0C\u5171 25 \u6761\uFF09\uFF1A\u6210\u5C31\u672C\u4F53\u5728 sbox.game \u6E38\u620F\u5305\u9875\u9762 Services \u003E Achievements \u914D\u7F6E\n/// \uFF08ident/\u6807\u9898/\u63CF\u8FF0/\u56FE\u6807/\u5206\u503C/\u89E3\u9501\u6A21\u5F0F\uFF09\uFF0C\u4EE3\u7801\u53EA\u6309 ident \u89E6\u53D1 Manual \u6210\u5C31\u3002\n/// \u5F15\u64CE\u884C\u4E3A\uFF08\u6E90\u7801 AchievementCollection.ManualUnlock\uFF09\uFF1Aident \u672A\u914D\u7F6E/\u5DF2\u89E3\u9501/\u975E Manual \u65F6\n/// \u9759\u9ED8\u8DF3\u8FC7\u2014\u2014\u5148\u5199\u4EE3\u7801\u540E\u914D\u9875\u9762\uFF0C\u914D\u597D\u5373\u751F\u6548\uFF1B\u6BCF\u6B21\u4E8B\u4EF6\u90FD\u8C03 Unlock\uFF0C\u7531\u5F15\u64CE\u53BB\u91CD\u3002\n/// Unlock \u53EA\u5BF9**\u672C\u673A\u73A9\u5BB6**\u751F\u6548\uFF08\u4E13\u7528\u670D\u52A1\u5668\u81EA\u52A8 no-op\uFF09\u2014\u2014\u6240\u6709\u89E6\u53D1\u70B9\u5FC5\u987B\u5148 GameSfx.IsMine\n/// \u5224\u5B9A\u0022\u662F\u4E0D\u662F\u672C\u673A\u8D5A\u5230\u7684\u0022\uFF0Chost \u4E0D\u66FF\u5BA2\u6237\u7AEF\u89E3\u9501\u3002\n/// \u7D2F\u8BA1\u578B\u6210\u5C31\uFF08\u5403\u98DF\u7269\u603B\u6570\u7B49\uFF09\u8D70\u5305\u9875\u9762 Stat \u7ED1\u5B9A\uFF08Stats.Increment \u5373\u53EF\uFF09\uFF0C\u4E0D\u8D70\u8FD9\u91CC\u3002\n/// \u8FDB\u7A0B\u5185\u8BA1\u6570\u5668\uFF08\u51FB\u6740\u6570/\u6361\u5305\u6570\uFF09\u91CD\u542F\u4F1A\u6E05\u96F6\u2014\u2014\u4E00\u6B21\u6027\u6210\u5C31\u89E3\u9501\u7531\u4E91\u7AEF\u53BB\u91CD\uFF0C\u53EA\u5F71\u54CD\u0022\u665A\u51E0\u5C40\u89E3\u9501\u0022\u3002\n/// \u003C/summary\u003E\npublic static class GameAchievements\n{\n\t// ---- \u9996\u6279 5 \u6761\uFF08v0.7.8.13\uFF09----\n\tpublic const string FirstBlood = \u0022first_blood\u0022;     // \u9996\u6B21\u5403\u6389\u4E00\u540D\u73A9\u5BB6\n\tpublic const string Godlike = \u0022godlike\u0022;            // \u5355\u5C40\u4E94\u8FDE\u6740\n\tpublic const string SpikeBlast = \u0022spike_blast\u0022;     // \u5C16\u523A\u5206\u8EAB\u70B8\u6389\u6BD4\u81EA\u5DF1\u5927\u7684\u7403\n\tpublic const string TeamWinner = \u0022team_winner\u0022;     // \u56E2\u961F\u8D5B\u6240\u5728\u961F\u593A\u51A0\n\tpublic const string Champion = \u0022champion\u0022;          // \u4E2A\u4EBA\u7B2C\u4E00\u540D\u7ED3\u7B97\n\n\t// ---- \u7B2C\u4E8C\u6279 20 \u6761\uFF08v0.7.8.14\uFF09----\n\tpublic const string DoubleKill = \u0022double_kill\u0022;     // \u5355\u5C40\u53CC\u8FDE\u6740\n\tpublic const string TripleKill = \u0022triple_kill\u0022;     // \u5355\u5C40\u4E09\u8FDE\u6740\n\tpublic const string Rampage = \u0022rampage\u0022;            // \u5355\u5C40\u56DB\u8FDE\u6740\n\tpublic const string BigPrey = \u0022big_prey\u0022;           // \u5355\u53E3\u541E\u6389\u5927\u7403\n\tpublic const string Revenge = \u0022revenge\u0022;            // \u5403\u6389\u4E0A\u6B21\u5403\u6389\u81EA\u5DF1\u7684\u73A9\u5BB6\n\tpublic const string Hunter10 = \u0022hunter_10\u0022;         // \u7D2F\u8BA1\u5403 10 \u540D\u73A9\u5BB6\n\tpublic const string Hunter50 = \u0022hunter_50\u0022;         // \u7D2F\u8BA1\u5403 50 \u540D\u73A9\u5BB6\n\tpublic const string HalfTon = \u0022half_ton\u0022;           // \u4E00\u6761\u547D\u603B\u8D28\u91CF 500\n\tpublic const string OneTon = \u0022one_ton\u0022;             // \u4E00\u6761\u547D\u603B\u8D28\u91CF 1000\n\tpublic const string TwoTon = \u0022two_ton\u0022;             // \u4E00\u6761\u547D\u603B\u8D28\u91CF 2000\n\tpublic const string FiveTon = \u0022five_ton\u0022;           // \u4E00\u6761\u547D\u603B\u8D28\u91CF 5000\n\tpublic const string FirstTool = \u0022first_tool\u0022;       // \u9996\u6B21\u4F7F\u7528\u9053\u5177\n\tpublic const string FullBag = \u0022full_bag\u0022;           // \u4E24\u683C\u80CC\u5305\u540C\u65F6\u6709\u8D27\n\tpublic const string PowerHoarder = \u0022power_hoarder\u0022; // \u7D2F\u8BA1\u6361 5 \u4E2A\u9053\u5177\u5165\u5305\n\tpublic const string FirstFeed = \u0022first_feed\u0022;       // \u9996\u6B21\u5582\u98DF\u89E6\u53D1\u9053\u5177\n\tpublic const string SpikeOp = \u0022spike_op\u0022;           // \u9996\u6B21\u653E\u51FA\u5C16\u523A\u5206\u8EAB\n\tpublic const string OctoSplit = \u0022octo_split\u0022;       // \u540C\u65F6\u62E5\u6709 8 \u9897\u8EAB\u4F53\n\tpublic const string Untouchable = \u0022untouchable\u0022;    // \u4E00\u6761\u547D\u5B58\u6D3B 5 \u5206\u949F\n\tpublic const string FirstDeath = \u0022first_death\u0022;     // \u7B2C\u4E00\u6B21\u88AB\u5403\n\tpublic const string FirstWin = \u0022first_win\u0022;         // \u9996\u6B21\u83B7\u80DC\n\n\tconst float BigPreyMass = 800f;   // \u5355\u53E3\u541E\u5165\u8D28\u91CF\u95E8\u69DB\uFF08\u8FD4\u8FD8\u524D\u7684\u53E3\u5F84\uFF0C\u5BF9\u5E94 ~1000 \u8D28\u91CF\u7684\u7403\uFF09\n\n\t/// \u003Csummary\u003E \u4E00\u6761\u547D\u5185\u7684\u8D28\u91CF\u91CC\u7A0B\u7891\uFF08\u4E0E MassSteps \u4E00\u4E00\u5BF9\u5E94\u89E3\u9501 HalfTon/OneTon/TwoTon/FiveTon\uFF09 \u003C/summary\u003E\n\tstatic readonly float[] MassSteps = { 500f, 1000f, 2000f, 5000f };\n\n\tstatic int _lifetimeKills;     // \u8FDB\u7A0B\u5185\u7D2F\u8BA1\u51FB\u6740\n\tstatic int _lifetimePickups;   // \u8FDB\u7A0B\u5185\u7D2F\u8BA1\u9053\u5177\u5165\u5305\n\tstatic long _lastKiller;       // \u6700\u8FD1\u5403\u6389\u672C\u673A\u7684\u73A9\u5BB6\uFF08\u590D\u4EC7\u540D\u5355\uFF09\n\n\t/// \u003Csummary\u003E \u672C\u673A\u51FB\u6740\uFF08\u6BCF\u6B21\u5403\u7403\u90FD\u8C03\uFF0C\u5F15\u64CE\u5BF9\u5DF2\u89E3\u9501\u9759\u9ED8\u53BB\u91CD\uFF09\uFF1B\n\t/// combo = \u672C\u5C40\u8FDE\u6740\u6570\uFF08GameSfx 10s \u7A97\u53E3\uFF09\uFF0CmassGained = \u672C\u53E3\u541E\u5165\u8D28\u91CF \u003C/summary\u003E\n\tpublic static void LocalKill( int combo, float massGained, long eatenSteamId )\n\t{\n\t\tGameXp.OnLocalKill();   // \u7ECF\u9A8C\u8BB0\u8D26\uFF1A\u672C\u5C40\u51FB\u6740\uFF08v0.7.8.19\uFF09\n\n\t\tUnlock( FirstBlood );\n\t\tif ( combo \u003E= 2 ) Unlock( DoubleKill );\n\t\tif ( combo \u003E= 3 ) Unlock( TripleKill );\n\t\tif ( combo \u003E= 4 ) Unlock( Rampage );\n\t\tif ( combo \u003E= 5 ) Unlock( Godlike );\n\n\t\tif ( massGained \u003E= BigPreyMass ) Unlock( BigPrey );\n\t\tif ( eatenSteamId != 0 \u0026\u0026 eatenSteamId == _lastKiller ) Unlock( Revenge );\n\n\t\t_lifetimeKills\u002B\u002B;\n\t\tif ( _lifetimeKills \u003E= 10 ) Unlock( Hunter10 );\n\t\tif ( _lifetimeKills \u003E= 50 ) Unlock( Hunter50 );\n\t}\n\n\t/// \u003Csummary\u003E \u672C\u673A\u88AB\u5403\uFF1A\u53C2\u4E0E\u5956 \u002B \u8BB0\u4EC7\uFF08\u4E4B\u540E\u5403\u6389\u4ED6\u89E3\u9501 Revenge\uFF09 \u003C/summary\u003E\n\tpublic static void LocalDeath( long killerSteamId )\n\t{\n\t\t_lastKiller = killerSteamId;\n\t\tUnlock( FirstDeath );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u6BCF\u5E27\u4F53\u68C0\uFF08GameHud.OnUpdate \u8C03\uFF0C\u5168\u7AEF\uFF09\uFF1A\u4E00\u6761\u547D\u8D28\u91CF\u91CC\u7A0B\u7891 / 8 \u9897\u8EAB\u4F53 / \u5B58\u6D3B 5 \u5206\u949F / \u80CC\u5305\u53CC\u6EE1\u3002\n\t/// \u95E8\u69DB\u5224\u5B9A\u6BCF\u5E27\u91CD\u590D\u89E6\u53D1\u7531\u5F15\u64CE\u53BB\u91CD\uFF0C\u65E0\u9700\u672C\u5730\u72B6\u6001\u3002\n\t/// \u003C/summary\u003E\n\tpublic static void MatchTick( CircleroyaleGame game )\n\t{\n\t\tif ( !game.IsMatchStarted || MatchState.MatchOver ) return;\n\n\t\tvar local = game.LocalBall;\n\t\tif ( !local.IsValid() || !local.Alive ) return;\n\n\t\tvar mass = game.TotalMassFor( local );\n\t\tif ( mass \u003E= MassSteps[0] ) Unlock( HalfTon );\n\t\tif ( mass \u003E= MassSteps[1] ) Unlock( OneTon );\n\t\tif ( mass \u003E= MassSteps[2] ) Unlock( TwoTon );\n\t\tif ( mass \u003E= MassSteps[3] ) Unlock( FiveTon );\n\n\t\tint pieces = 0;\n\t\tforeach ( var c in game.Cells )\n\t\t{\n\t\t\tif ( c.PieceKind == CellPiece.Kind.SplitPiece \u0026\u0026 c.OwnerSteamId == local.OwnerSteamId ) pieces\u002B\u002B;\n\t\t}\n\t\tif ( pieces \u003E= 8 ) Unlock( OctoSplit );\n\n\t\tif ( local.LifeSeconds \u003E 300f ) Unlock( Untouchable );\n\n\t\t// \u80CC\u5305\u53CC\u6EE1\uFF08PowerA/B \u662F [Sync]\uFF0C255=\u7A7A\uFF09\u2014\u2014\u540C\u6B65\u5230\u4F4D\u624D\u89E6\u53D1\uFF0C\u65E0\u65F6\u5E8F\u7ADE\u6001\n\t\tif ( local.PowerA != 255 \u0026\u0026 local.PowerB != 255 ) Unlock( FullBag );\n\t}\n\n\t/// \u003Csummary\u003E \u672C\u673A\u6309 Q/E \u91CA\u653E\u9053\u5177\uFF08UsePowerSlot \u6A2A\u5E45\u5904\u8C03\uFF09\uFF1A\u9996\u6B21\u7528\u9053\u5177 / \u9996\u6B21\u653E\u5C16\u523A \u003C/summary\u003E\n\tpublic static void PowerUsed( byte kind )\n\t{\n\t\tUnlock( FirstTool );\n\t\tif ( kind == (byte)PowerUpManager.Kind.Spike ) Unlock( SpikeOp );\n\t}\n\n\t/// \u003Csummary\u003E \u672C\u673A\u6361\u5230\u9053\u5177\u5165\u5305\uFF08host \u672C\u673A\u62FE\u53D6\u4E0E\u5BA2\u6237\u7AEF\u5E7F\u64AD\u843D\u5730\u4E24\u6761\u8DEF\u90FD\u4F1A\u8C03\uFF09\uFF1A\u7D2F\u8BA1 5 \u4E2A \u003C/summary\u003E\n\tpublic static void PowerStored()\n\t{\n\t\tif ( \u002B\u002B_lifetimePickups \u003E= 5 ) Unlock( PowerHoarder );\n\t}\n\n\t/// \u003Csummary\u003E \u5582\u98DF\u89E6\u53D1\u9053\u5177\uFF08host \u7684 Feed \u5224\u5B9A \u002B \u5BA2\u6237\u7AEF PowerBanner \u843D\u5730\uFF0C\u4E24\u8DEF\u90FD\u8FC7 IsMine\uFF09 \u003C/summary\u003E\n\tpublic static void FeedTriggered( long feederSteamId )\n\t{\n\t\tif ( GameSfx.IsMine( feederSteamId ) )\n\t\t\tUnlock( FirstFeed );\n\t}\n\n\t/// \u003Csummary\u003E \u5C16\u523A\u5206\u8EAB\u70B8\u6389\u6BD4\u4E3B\u4EBA\u66F4\u5927\u7684\u7403\uFF08host \u5224\u5B9A\uFF1Bowner=\u5C16\u523A\u4E3B\u4EBA\uFF0C\u5E7F\u64AD\u5230\u5404\u7AEF\u540E\u672C\u673A\u5224\u5B9A\uFF09 \u003C/summary\u003E\n\tpublic static void SpikeMinionBlast( long ownerSteamId )\n\t{\n\t\tif ( GameSfx.IsMine( ownerSteamId ) )\n\t\t\tUnlock( SpikeBlast );\n\t\tif ( Networking.IsActive )\n\t\t\tNetworkManager.SpikeReward( ownerSteamId );\n\t}\n\n\t/// \u003Csummary\u003E \u5C16\u523A\u5956\u52B1\u5E7F\u64AD\u843D\u5730\uFF08\u5BA2\u6237\u7AEF\uFF1Bhost \u5DF2\u5728 SpikeMinionBlast \u5224\u8FC7\uFF0C\u6321\u56DE\u58F0\uFF09 \u003C/summary\u003E\n\tpublic static void OnSpikeRewardRemote( long ownerSteamId )\n\t{\n\t\tif ( NetworkManager.IsAuthority ) return;\n\t\tif ( GameSfx.IsMine( ownerSteamId ) )\n\t\t\tUnlock( SpikeBlast );\n\t}\n\n\t/// \u003Csummary\u003E \u6BD4\u8D5B\u7ED3\u7B97\uFF08\u5168\u7AEF\u5404\u81EA\u5224\u5B9A\uFF09\uFF1A\u56E2\u961F\u8D5B=\u672C\u961F\u603B\u8D28\u91CF\u7B2C\u4E00\uFF08\u5E76\u5217\u4E5F\u7B97\uFF09\uFF1B\u666E\u901A\u8D5B=\u4E2A\u4EBA\u7B2C\u4E00\u540D\u3002\n\t/// \u8FD4\u56DE\u662F\u5426\u83B7\u80DC\uFF08GameXp \u7ED3\u7B97\u7ECF\u9A8C\u7528\uFF09 \u003C/summary\u003E\n\tpublic static bool Settlement( ScoreWire[] standings, long localSteamId )\n\t{\n\t\tif ( standings is null || standings.Length == 0 ) return false;\n\n\t\tbool won = false;\n\n\t\tif ( MatchState.IsTeam )\n\t\t{\n\t\t\tvar local = CircleroyaleGame.Current?.LocalBall;\n\t\t\tif ( local.IsValid() \u0026\u0026 local.TeamIndex \u003E= 0 )\n\t\t\t{\n\t\t\t\tvar totals = new Dictionary\u003Cint, float\u003E();\n\t\t\t\tforeach ( var row in standings )\n\t\t\t\t{\n\t\t\t\t\tvar t = totals.TryGetValue( row.Team, out var m ) ? m : 0f;\n\t\t\t\t\ttotals[row.Team] = t \u002B row.Mass;\n\t\t\t\t}\n\n\t\t\t\tvar mine = totals.GetValueOrDefault( local.TeamIndex );\n\t\t\t\tvar best = -1f;\n\t\t\t\tforeach ( var kv in totals )\n\t\t\t\t{\n\t\t\t\t\tif ( kv.Value \u003E best ) best = kv.Value;\n\t\t\t\t}\n\n\t\t\t\tif ( mine \u003E= best )\n\t\t\t\t{\n\t\t\t\t\tUnlock( TeamWinner );\n\t\t\t\t\twon = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if ( standings[0].SteamId == localSteamId )\n\t\t{\n\t\t\tUnlock( Champion );\n\t\t\twon = true;\n\t\t}\n\n\t\tif ( won ) Unlock( FirstWin );\n\t\treturn won;\n\t}\n\n\tstatic void Unlock( string ident ) =\u003E Sandbox.Services.Achievements.Unlock( ident );\n}"},{"Ident":"slfy.circleroyale","Path":"Game/ScoreUploader.cs","FileName":"ScoreUploader.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\n/// \u003Csummary\u003E\n/// \u6210\u7EE9\u4E0A\u4F20\u4E0E\u5168\u670D\u699C\u8BFB\u53D6\uFF08M5\uFF09\u3002\u4E91\u7AEF\u8D70 Sandbox.Services\uFF1A\n/// \u4E0A\u4F20 = Stats.SetValue(\u0022cr_match_mass\u0022, \u672C\u5C40\u603B\u8D28\u91CF) \u002B Flush\uFF1B\n/// \u8BFB\u53D6 = Leaderboards.GetFromStat \u805A\u5408 Max\uFF08\u5168\u670D\u6700\u9AD8\u5355\u5C40\uFF09\u3002\n/// \u26A0\uFE0F \u672A\u53D1\u5E03\u7684\u672C\u5730\u5305\u540E\u7AEF\u6CA1\u6709\u5305\u8EAB\u4EFD\uFF0CRefresh \u5185\u90E8\u541E 404\uFF08Entries \u4FDD\u6301\u7A7A\uFF09\u2014\u2014\n/// \u5C55\u793A\u5C42\u636E\u6B64\u81EA\u52A8\u964D\u7EA7\u5230 LocalBoard \u7684\u672C\u5730\u6301\u4E45\u5316\u699C\u3002\n/// \u003C/summary\u003E\npublic static class ScoreUploader\n{\n\t/// \u003Csummary\u003E \u6392\u884C\u699C\u7EDF\u8BA1\u540D\uFF08\u805A\u5408 Max = \u5355\u5C40\u6700\u9AD8\u603B\u8D28\u91CF\uFF09 \u003C/summary\u003E\n\tpublic const string StatName = \u0022cr_match_mass\u0022;\n\n\t/// \u003Csummary\u003E \u672C\u673A\u6210\u7EE9\u4E0A\u4E91\uFF08\u6309\u767B\u5F55\u7528\u6237\u8BB0\u8D26\uFF1B\u62FF\u4E0D\u5230\u672C\u673A\u7403\u5C31\u9759\u9ED8\u8DF3\u8FC7\uFF09 \u003C/summary\u003E\n\tpublic static void SubmitOwn( ScoreWire[] standings, CircleroyaleGame game )\n\t{\n\t\ttry\n\t\t{\n\t\t\tvar local = game?.LocalBall;\n\t\t\tif ( !local.IsValid() ) return;\n\n\t\t\tvar mass = game.TotalMassFor( local );\n\t\t\tSandbox.Services.Stats.SetValue( StatName, mass );\n\t\t\tSandbox.Services.Stats.Flush();\n\t\t\tGameLog.Info( $\u0022[score] uploaded {mass:0} ({StatName})\u0022 );\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tGameLog.Info( $\u0022[score] upload failed: {e.Message}\u0022 );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u62C9\u5168\u670D\u699C Top10\uFF08\u5F02\u6B65\uFF1B\u7A7A/\u5931\u8D25\u56DE\u8C03 null\u2014\u2014\u5C55\u793A\u5C42\u964D\u7EA7\u672C\u5730\u699C\uFF09 \u003C/summary\u003E\n\tpublic static async void FetchGlobal( Action\u003CList\u003C( string Name, float Mass )\u003E\u003E done )\n\t{\n\t\ttry\n\t\t{\n\t\t\tvar board = Sandbox.Services.Leaderboards.GetFromStat( StatName );\n\t\t\tboard.SetAggregationMax();\n\t\t\tboard.SetSortDescending();\n\t\t\tboard.FilterByNone();\n\t\t\tboard.MaxEntries = 10;\n\t\t\tawait board.Refresh();\n\n\t\t\tvar list = new List\u003C( string Name, float Mass )\u003E();\n\t\t\tforeach ( var e in board.Entries )\n\t\t\t\tlist.Add( ( e.DisplayName, (float)e.Value ) );\n\n\t\t\tdone( list.Count \u003E 0 ? list : null );\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tGameLog.Info( $\u0022[score] global board fetch failed: {e.Message}\u0022 );\n\t\t\tdone( null );\n\t\t}\n\t}\n}\n"},{"Ident":"slfy.circleroyale","Path":"Player/Ball.cs","FileName":"Ball.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// \u7403\uFF08\u771F\u4EBA/bot \u5171\u7528\uFF09\uFF1A**owner \u672C\u5730\u6A21\u62DF\u79FB\u52A8**\uFF08\u771F\u4EBA=\u952E\u76D8\u8F93\u5165\uFF0Cbot=host \u7684 BotBrain\uFF09\uFF0C\n/// \u6743\u5A01\u6570\u503C\uFF08Mass/IsBot/ColorIndex/PlayerName/OwnerSteamId\uFF09\u7531 host \u7ECF [Sync(FromHost)] \u4E0B\u53D1\uFF0C\n/// \u4F4D\u7F6E\u968F\u7F51\u7EDC transform \u81EA\u52A8\u540C\u6B65\uFF08owner \u2192 \u5176\u4ED6\u7AEF\uFF09\u3002\n/// \u89C6\u89C9\uFF1A\u9713\u8679\u7EBF\u6761\u7531 NeonRenderer \u6279\u91CF\u7ED8\u5236\uFF1B\u5934\u50CF\u4E3A\u672C\u5730 SpriteRenderer\uFF08\u5404\u7AEF\u6309 OwnerSteamId \u81EA\u53D6\uFF09\u3002\n/// \u003C/summary\u003E\npublic sealed class Ball : Component\n{\n\t[Sync( SyncFlags.FromHost )] public float Mass { get; set; } = GameConfig.StartMass;\n\n\t[Sync( SyncFlags.FromHost )] public bool IsBot { get; set; }\n\n\t[Sync( SyncFlags.FromHost )] public int ColorIndex { get; set; }\n\n\t[Sync( SyncFlags.FromHost )] public string PlayerName { get; set; } = \u0022Player\u0022;\n\n\t/// \u003Csummary\u003E \u62E5\u6709\u8005 SteamId\uFF08bot \u4E3A 0\uFF09\uFF1Bhost \u5199\u5165\uFF0C\u5404\u7AEF\u636E\u6B64\u52A0\u8F7D\u5BF9\u5E94\u5934\u50CF \u003C/summary\u003E\n\t[Sync( SyncFlags.FromHost )] public long OwnerSteamId { get; set; }\n\n\t/// \u003Csummary\u003E \u961F\u4F0D\u53F7\uFF08\u56E2\u961F\u8D5B 0..N-1\uFF0C\u666E\u901A\u8D5B -1\uFF09\uFF1Bhost \u5F00\u5C40\u8F6E\u8F6C\u5206\u914D\uFF0C\u91CD\u751F\u4E0D\u53D8 \u003C/summary\u003E\n\t[Sync( SyncFlags.FromHost )] public int TeamIndex { get; set; } = -1;\n\n\t/// \u003Csummary\u003E \u86F0\u4F0F\u5907\u7528\u7403\uFF08v0.7.8.25\uFF09\uFF1A\u623F\u95F4\u9884\u7559/\u672C\u5C40\u591A\u4F59\u7684 bot\u3002Alive=false \u4E0E\u0022\u6B7B\u4EA1\u0022\u65E0\u6CD5\u533A\u5206\uFF0C\n\t/// TickBots \u66FE\u628A 16 \u9897\u86F0\u4F0F\u7403\u5F53\u5C38\u4F53\u539F\u5730\u590D\u6D3B\u6210\u65E0\u961F FFA bot \u704C\u8FDB T1\u2014\u2014\u5FC5\u987B\u6709\u72EC\u7ACB\u6807\u8BB0 \u003C/summary\u003E\n\t[Sync( SyncFlags.FromHost )] public bool Dormant { get; set; }\n\n\t/// \u003Csummary\u003E \u4E3B\u4EBA\u8D26\u53F7\u7B49\u7EA7\uFF08v0.7.8.19\uFF09\uFF1A\u5404\u7AEF\u7B97\u597D\u5199\u8FDB**\u81EA\u5DF1\u7684\u7403**\uFF08\u666E\u901A [Sync]=owner \u53EF\u5199\uFF09\uFF0C\n\t/// \u5168\u573A\u540D\u724C\u53EF\u89C1\uFF1Bbot \u6052 0 \u4E0D\u663E\u793A \u003C/summary\u003E\n\t[Sync] public int AccountLevel { get; set; }\n\n\t// ---- \u9053\u5177 buff\uFF08v0.7.3.0\uFF09\uFF1Ahost \u6743\u5A01\u5199\u5165\uFF0C\u5404\u7AEF\u5C55\u793A/\u6548\u679C\u5171\u7528\u3002\n\t// Kind \u6B8B\u7559\u65E7\u503C\u6CA1\u5173\u7CFB\uFF0CHasBuff \u53EA\u770B _sinceBuff \u003C BuffDuration\uFF1B\n\t// serial \u81EA\u589E\u4FDD\u8BC1\u0022\u540C\u7C7B buff \u518D\u6361\u4E00\u6B21\u0022\u4E5F\u89E6\u53D1 [Change]\uFF08\u5BA2\u6237\u7AEF\u91CD\u7F6E\u672C\u5730\u8BA1\u65F6\uFF09 ----\n\n\t[Sync( SyncFlags.FromHost )] public byte BuffKind { get; set; } = 255;\n\n\t[Sync( SyncFlags.FromHost )] public float BuffDuration { get; set; }\n\n\t[Sync( SyncFlags.FromHost ), Change( nameof( OnBuffSynced ) )] public byte BuffSerial { get; set; }\n\n\tTimeSince _sinceBuff = 999f;\n\n\t// ---- \u9053\u5177\u80CC\u5305\uFF08v0.7.5.0 \u4E3B\u52A8\u4F7F\u7528\uFF09\uFF1A\u62FE\u53D6\u5B58\u5165 Q/E \u4E24\u683C\uFF0C\u6309\u952E\u91CA\u653E\u6548\u679C\u3002\n\t// 255 = \u7A7A\u69FD\uFF1B\u6B7B\u4EA1/\u91CD\u751F\u6E05\u7A7A\uFF08\u673A\u4F1A\u4E0D\u8DE8\u547D\u643A\u5E26\uFF09 ----\n\n\t[Sync( SyncFlags.FromHost )] public byte PowerA { get; set; } = 255;\n\n\t[Sync( SyncFlags.FromHost )] public byte PowerB { get; set; } = 255;\n\n\t/// \u003Csummary\u003E \u5404\u7AEF\u6536\u5230 buff \u53D8\u5316\uFF08\u542B host \u672C\u5730\u5199\u5165\uFF09\u5373\u91CD\u7F6E\u672C\u5730\u8BA1\u65F6\u2014\u2014\u5BA2\u6237\u7AEF\u636E\u6B64\u5012\u7B97\u5269\u4F59\u65F6\u95F4 \u003C/summary\u003E\n\tvoid OnBuffSynced() =\u003E _sinceBuff = 0;\n\n\t/// \u003Csummary\u003E host\uFF1A\u7ED9\u8FD9\u9897\u7403\u4E0A buff\uFF08\u8D28\u91CF\u7F50\u7B49\u5373\u65F6\u6548\u679C\u4E0D\u8D70\u8FD9\u91CC\uFF09 \u003C/summary\u003E\n\tpublic void ApplyBuff( byte kind, float duration )\n\t{\n\t\tBuffKind = kind;\n\t\tBuffDuration = duration;\n\t\tBuffSerial\u002B\u002B;\n\t\t_sinceBuff = 0;\n\t}\n\n\t/// \u003Csummary\u003E buff \u662F\u5426\u751F\u6548\u4E2D\uFF08host \u6743\u5A01\u5224\u5B9A\u4E0E\u5BA2\u6237\u7AEF\u5C55\u793A\u540C\u4E00\u516C\u5F0F\uFF0C\u5BA2\u6237\u7AEF\u81EA\u7136\u8FC7\u671F\u65E0\u9700\u901A\u77E5\uFF09 \u003C/summary\u003E\n\tpublic bool HasBuff( PowerUpManager.Kind kind ) =\u003E\n\t\tBuffKind == (byte)kind \u0026\u0026 _sinceBuff \u003C BuffDuration;\n\n\t/// \u003Csummary\u003E \u5F53\u524D buff \u5269\u4F59\u79D2\u6570\uFF08HUD \u5012\u8BA1\u65F6\u7528\uFF1B\u65E0 buff \u4E3A 0\uFF09 \u003C/summary\u003E\n\tpublic float BuffLeft =\u003E BuffKind == 255 ? 0f : MathF.Max( 0f, BuffDuration - _sinceBuff );\n\n\t/// \u003Csummary\u003E \u80CC\u5305\u7B2C slot \u683C\uFF080=Q \u69FD 1=E \u69FD\uFF09\u7684\u9053\u5177\u79CD\u7C7B\uFF1B255=\u7A7A \u003C/summary\u003E\n\tpublic byte PowerSlot( int slot ) =\u003E slot == 0 ? PowerA : PowerB;\n\n\t/// \u003Csummary\u003E \u62FE\u53D6\u5165\u5305\uFF08v0.7.8.30 \u8D77**\u53EA\u8FDB E \u69FD**\u2014\u2014Q \u6539\u4E3A\u968F\u65F6\u95F4\u5145\u80FD\u7684\u6280\u80FD\u4F4D\uFF0C\u4E0D\u518D\u63A5\u6536\u62FE\u53D6\uFF09\uFF1B\n\t/// E \u6EE1\u8FD4\u56DE -1\uFF08\u9053\u5177\u7559\u5728\u573A\u4E0A\uFF09 \u003C/summary\u003E\n\tpublic int StorePower( byte kind )\n\t{\n\t\tif ( PowerB == 255 ) { PowerB = kind; return 1; }\n\t\treturn -1;\n\t}\n\n\t/// \u003Csummary\u003E host\uFF1A\u4F7F\u7528\u80CC\u5305\u9053\u5177\u2014\u2014\u6E05\u69FD\u5E76\u89E6\u53D1\u6548\u679C\uFF08PowerUpManager.ApplyEffect\uFF09 \u003C/summary\u003E\n\tpublic void UsePower( int slot )\n\t{\n\t\tvar kind = PowerSlot( slot );\n\t\tif ( kind == 255 ) return;\n\n\t\tif ( slot == 0 ) PowerA = 255;\n\t\telse PowerB = 255;\n\n\t\tPowerUpManager.ApplyEffect( this, kind );\n\t}\n\n\t/// \u003Csummary\u003E \u6309\u952E\u91CA\u653E\u80CC\u5305\u69FD\uFF08pressed \u4E3A true \u65F6\u5904\u7406\uFF09\uFF1A\u672C\u5730\u5148\u64AD\u6FC0\u6D3B\u6A2A\u5E45\u002B\u97F3\u6548\u002B\u8F7B\u9707\uFF0C\n\t/// \u518D\u8D70\u6743\u5A01/RPC \u8DEF\u5F84 \u003C/summary\u003E\n\tvoid UsePowerSlot( int slot, bool pressed )\n\t{\n\t\tif ( !pressed ) return;\n\n\t\tvar kind = PowerSlot( slot );\n\t\tif ( kind == 255 ) return;   // \u7A7A\u69FD\uFF1A\u6309\u952E\u65E0\u6548\uFF08\u4E0D\u64AD\u6A2A\u5E45\u4E0D\u53D1\u8BF7\u6C42\uFF09\n\n\t\tCircleroyaleGame.Current?.Hud?.AddBanner( GameHud.ActivationText( kind ), GameHud.KindColor( kind ) );\n\t\tGameSfx.Pickup();\n\t\tCircleroyaleGame.Current?.Camera?.Shake( 5f );   // \u653E\u6280\u80FD\u8F7B\u9707\uFF08\u51FB\u6740\u9707\u66F4\u731B\uFF0C\u968F combo \u52A0\u7801\uFF09\n\t\tGameAchievements.PowerUsed( kind );   // \u6210\u5C31\uFF1A\u9996\u6B21\u7528\u9053\u5177 / \u9996\u6B21\u653E\u5C16\u523A\uFF08v0.7.8.14\uFF09\n\n\t\tif ( NetworkManager.IsAuthority )\n\t\t\tUsePower( slot );\n\t\telse\n\t\t\tRequestUsePower( slot );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u662F\u5426\u5B58\u6D3B\u3002**\u7403\u5BF9\u8C61\u6C38\u4E0D\u9500\u6BC1**\uFF1A\u6B7B\u4EA1 = Alive=false\uFF08\u540C\u6B65\u5B57\u6BB5\uFF0C\u6E32\u67D3/AI/\u5224\u5B9A\u5168\u90E8\u8DF3\u8FC7\uFF09\uFF0C\n\t/// \u91CD\u751F = host \u7F6E\u56DE true\u3002\u7ED5\u5F00\u0022\u8FDB\u884C\u4E2D NetworkSpawn \u4E0D\u590D\u5236\u5230\u5DF2\u8FDE\u63A5\u5BA2\u6237\u7AEF\u0022\u7684\u5F15\u64CE\u884C\u4E3A\uFF08\u5B9E\u6D4B\u5927\u5751\uFF09\u3002\n\t/// \u003C/summary\u003E\n\t[Sync( SyncFlags.FromHost )]\n\tpublic bool Alive { get; set; } = true;\n\n\tpublic float Radius =\u003E GameConfig.StartRadius * MathF.Sqrt( Mass / GameConfig.StartMass );\n\n\t/// \u003Csummary\u003E \u5F53\u524D\u901F\u5EA6\uFF08v0.7.8.32\uFF1Abot \u62E6\u622A\u9884\u5224\u6309\u5B83\u6253\u63D0\u524D\u91CF\uFF1B\u53EA\u8BFB\uFF0CBallInput \u5185\u90E8\u7EF4\u62A4\uFF09 \u003C/summary\u003E\n\tpublic Vector3 Velocity =\u003E _velocity;\n\n\t/// \u003Csummary\u003E \u672C\u6761\u547D\u5DF2\u5B58\u6D3B\u79D2\u6570\uFF08\u6210\u5C31\u0022\u5B58\u6D3B 5 \u5206\u949F\u0022\u7528\uFF1B\u91CD\u751F\u65F6 _sinceSpawn \u5F52\u96F6\uFF09 \u003C/summary\u003E\n\tpublic float LifeSeconds =\u003E _sinceSpawn;\n\n\t/// \u003Csummary\u003E \u672C\u7403\u7684\u9713\u8679\u8272\uFF08\u7531 ColorIndex \u4ECE\u8C03\u8272\u677F\u53D6\uFF09 \u003C/summary\u003E\n\tpublic Color NeonColor =\u003E GameConfig.Palette[Math.Clamp( ColorIndex, 0, GameConfig.Palette.Length - 1 )];\n\n\t/// \u003Csummary\u003E \u79FB\u901F\uFF1A\u8D28\u91CF\u66F2\u7EBF \u00D7 \u52A0\u901F buff\uFF08\u26A1 \u65F6 \u00D71.4\uFF1BSpeed \u5C5E\u6027\u5404\u7AEF\u540C\u7B97\uFF0Cowner \u6A21\u62DF\u4E0E host \u6821\u9A8C\u4E00\u81F4\uFF09 \u003C/summary\u003E\n\tpublic float Speed\n\t{\n\t\tget\n\t\t{\n\t\t\tvar s = MathF.Max( GameConfig.MinSpeed,\n\t\t\t\tGameConfig.BaseSpeed * MathF.Pow( GameConfig.StartMass / Mass, GameConfig.SpeedCurve ) );\n\t\t\treturn HasBuff( PowerUpManager.Kind.Speed ) ? s * GameConfig.PowerUpSpeedBoost : s;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u662F\u5426\u51FA\u751F\u4FDD\u62A4\u4E2D\uFF08\u865A\u73AF\u62A4\u76FE\u8868\u73B0\uFF0C\u53CC\u5411\u514D\u6218\uFF09 \u003C/summary\u003E\n\tpublic bool IsProtected =\u003E _sinceSpawn \u003C GameConfig.SpawnProtectSeconds;\n\n\t/// \u003Csummary\u003E \u7403\u4F53\u80CC\u666F\u586B\u5145\u8272\uFF1A\u771F\u4EBA=\u5934\u50CF\u4E3B\u8272\u53D6\u6837\uFF0Cbot=\u9713\u8679\u914D\u8272 \u003C/summary\u003E\n\tpublic Color BackgroundColor =\u003E _avatarColor ?? NeonColor;\n\n\tVector3 _velocity;\n\tTimeSince _sinceSpawn;\n\tSpriteRenderer _avatar;\n\tColor? _avatarColor;\n\tVector2 _botDir;\n\tVector2 _lastMoveDir = Vector2.Right;   // \u9759\u6B62\u65F6\u5206\u88C2/\u5410\u5B62\u5B50\u7684\u65B9\u5411\u515C\u5E95\n\tVector2 _lastAimDir = Vector2.Zero;     // \u5149\u6807\u7784\u51C6\u65B9\u5411\uFF08\u5206\u88C2/\u5410\u5B62\u5B50\u7528\uFF0Cv0.7.0.0\uFF09\n\n\t/// \u003Csummary\u003E \u5F53\u524D\u7784\u51C6\u65B9\u5411\uFF08\u672C\u673A\u7403\uFF1A\u6E32\u67D3\u5916\u7F18\u7BAD\u5934\u7528\uFF1Bbot \u6052 Zero \u4E0D\u753B\uFF09 \u003C/summary\u003E\n\tpublic Vector2 AimDir =\u003E _lastAimDir;\n\tTimeSince _sinceEject;                  // \u5BA2\u6237\u7AEF\u6309\u4F4F\u8FDE\u5410\u7684\u8282\u6D41\uFF08host \u8FD8\u6709\u540C\u6B3E\u6821\u9A8C\uFF09\n\tTimeSince _sinceAvatarRetry = 1f;   // \u9996\u5E27\u7ACB\u5373\u5C1D\u8BD5\n\tint _avatarRetryCount;\n\tbool _avatarReady;\n\tbool _wasAlive = true;\n\n\t/// \u003Csummary\u003E \u6B7B\u4EA1\u65F6\u523B\uFF08host \u8BB0\u5F55\uFF0C\u7528\u4E8E bot \u539F\u5730\u590D\u6D3B\u7684\u5EF6\u65F6\uFF09 \u003C/summary\u003E\n\tpublic TimeSince SinceDeath { get; private set; }\n\n\t/// \u003Csummary\u003E\n\t/// host \u4FA7\u6807\u8BB0\uFF1A\u8FD9\u9897\u7403\u662F\u4E3A\u8FDC\u7A0B\u8FDE\u63A5\u751F\u6210\u7684\uFF08\u4EC5\u6743\u5A01\u7AEF\u6709\u610F\u4E49\uFF0C\u4E0D\u8054\u7F51\uFF09\u3002\n\t/// ScanBalls/SetLocalBall \u7528\u5B83\u628A\u8FDC\u7A0B\u7403\uFF08\u542B\u7ADE\u6001\u4E0B\u672A\u7F51\u7EDC\u5316\u7684\u5E7D\u7075\u7403\uFF09\u6392\u9664\u51FA\u672C\u673A\u7403\u5019\u9009\uFF0C\n\t/// \u5426\u5219\u540C\u8D26\u53F7\u53CC\u5F00\u65F6\u5E7D\u7075\u7403\u4F1A\u9876\u6389 host \u81EA\u5DF1\u7684\u7403\u3001\u76F8\u673A\u6765\u56DE\u8DF3\uFF08\u5B9E\u6D4B\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tpublic bool IsRemoteOwned { get; set; }\n\n\t/// \u003Csummary\u003E host \u4FA7\u6807\u8BB0\u6B7B\u4EA1\uFF08\u5BF9\u8C61\u4FDD\u7559\uFF0C\u539F\u5730\u590D\u7528\uFF09 \u003C/summary\u003E\n\tpublic void MarkDead()\n\t{\n\t\tAlive = false;\n\t\tSinceDeath = 0;\n\t\t_velocity = Vector3.Zero;\n\t}\n\n\t/// \u003Csummary\u003E \u91CD\u7F6E\u51FA\u751F\u4FDD\u62A4\u8BA1\u65F6\uFF08host \u5728\u73A9\u5BB6\u771F\u6B63\u5165\u5C40\u65F6\u8C03\u7528\u2014\u2014pre-snapshot \u7403\u7684\n\t/// \u4FDD\u62A4\u671F\u5728\u63E1\u624B\u671F\u95F4\u5C31\u7A7A\u8DD1\u5B8C\u4E86\uFF0C\u5B9E\u6D4B\u52A0\u5165 1.6s \u5373\u88AB\u5403\uFF1BIsProtected \u6309\u5404\u7AEF\u672C\u5730\u8BA1\u65F6\u5224\u5B9A\uFF0C\n\t/// host \u91CD\u7F6E\u5373\u6062\u590D\u6743\u5A01\u5224\u5B9A\uFF0C\u5BA2\u6237\u7AEF\u81EA\u5DF1\u7684\u8BA1\u65F6\u968F\u5FEB\u7167\u5E94\u7528\u53E6\u7B97\uFF09 \u003C/summary\u003E\n\tpublic void RefreshSpawnProtection() =\u003E _sinceSpawn = 0;\n\n\t/// \u003Csummary\u003E \u77AC\u79FB\uFF08\u5206\u8EAB\u664B\u5347\u4E3B\u7403\u65F6\u7528\uFF09\uFF1A\u4F4D\u7F6E\u5F52 owner \u6A21\u62DF\uFF0Cowner \u7AEF\u5FC5\u987B\u81EA\u5DF1\u642C\u8FC7\u53BB\uFF1B\n\t/// \u4E0D\u89E6\u53D1\u4FDD\u62A4\u671F/\u91CD\u64AD\u97F3\u6548\u2014\u2014\u8FD9\u662F\u8EAB\u4EFD\u5EF6\u7EED\uFF0C\u4E0D\u662F\u91CD\u751F \u003C/summary\u003E\n\tpublic void TeleportTo( Vector3 pos )\n\t{\n\t\tWorldPosition = new Vector3( pos.x, pos.y, 0f );\n\t\t_velocity = Vector3.Zero;\n\t\t_lastMoveDir = Vector2.Zero;\n\t}\n\n\t/// \u003Csummary\u003E host \u4FA7\u521D\u59CB\u5316\uFF08\u4EC5\u6743\u5A01\u7AEF\u8C03\u7528\uFF1B\u5BA2\u6237\u7AEF\u5B57\u6BB5\u5168\u90E8\u8D70\u7F51\u7EDC\u540C\u6B65\uFF09\u3002\n\t/// teamIndex \u2265 0 \u65F6\u540C\u65F6\u5199\u5165\u961F\u4F0D\u53F7\uFF1B\u56E2\u961F\u8D5B\u989C\u8272\u9501\u961F\u4F0D\u8272\uFF0C\u666E\u901A\u8D5B\u968F\u673A\u9713\u8679\u8272 \u003C/summary\u003E\n\tpublic void Init( bool isBot, int teamIndex = -1 )\n\t{\n\t\tIsBot = isBot;\n\t\tif ( teamIndex \u003E= 0 ) TeamIndex = teamIndex;\n\n\t\tColorIndex = MatchState.IsTeam\n\t\t\t? ( ( TeamIndex % GameConfig.Palette.Length ) \u002B GameConfig.Palette.Length ) % GameConfig.Palette.Length\n\t\t\t: (int)Game.Random.Float( 0f, GameConfig.Palette.Length - 0.001f );\n\n\t\t_sinceSpawn = 0;\n\t\t_velocity = Vector3.Zero;\n\n\t\t// \u9053\u5177 buff \u4E0E\u80CC\u5305\u968F\u91CD\u751F\u6E05\u6389\uFF08\u673A\u4F1A\u4E0D\u8DE8\u547D\u643A\u5E26\uFF09\n\t\tBuffKind = 255;\n\t\tBuffDuration = 0f;\n\t\tPowerA = 255;\n\t\tPowerB = 255;\n\t}\n\n\t/// \u003Csummary\u003E bot \u7531 BotBrain \u6BCF\u5E27\u5199\u5165\u7684\u79FB\u52A8\u65B9\u5411\uFF08\u771F\u4EBA\u8D70 BallInput\uFF0C\u4E0D\u7528\u8FD9\u4E2A\uFF09 \u003C/summary\u003E\n\tpublic void SetBotDirection( Vector2 dir ) =\u003E _botDir = dir;\n\n\tprotected override void OnStart()\n\t{\n\t\tbase.OnStart();\n\n\t\t// \u6240\u6709\u673A\u5668\u7EDF\u4E00\u4ECE\u7F51\u7EDC\u5BF9\u8C61\u7684 OnStart \u6CE8\u518C\uFF08host \u751F\u6210 / \u5BA2\u6237\u7AEF\u968F\u590D\u5236\u51FA\u73B0\uFF09\n\t\tCircleroyaleGame.Current?.RegisterBall( this );\n\t\tif ( !IsProxy \u0026\u0026 !IsBot )\n\t\t\tCircleroyaleGame.Current?.SetLocalBall( this );\n\n\t\t// \u5934\u50CF\u5706\u5FC3\uFF08\u7528\u6237\u51B3\u7B56\uFF1A\u5934\u50CF\u4F5C\u5706\u5FC3\uFF0C\u9713\u8679\u7EBF\u6761\u4F5C\u5916\u4FA7\uFF09\n\t\tvar go = new GameObject( true, \u0022Avatar\u0022 );\n\t\tgo.Parent = GameObject;\n\n\t\t_avatar = go.AddComponent\u003CSpriteRenderer\u003E();\n\t\t_avatar.Lighting = false;\n\t\t_avatar.Shadows = false;\n\n\t\tTryLoadAvatar();\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u52A0\u8F7D\u5934\u50CF\uFF1Abot \u7528\u5360\u4F4D\u56FE\uFF1B\u771F\u4EBA\u6309 OwnerSteamId \u62C9 Steam \u5934\u50CF\n\t/// \uFF08\u4E0B\u8F7D\u6709\u5EF6\u8FDF\uFF0C\u5931\u8D25\u6BCF\u79D2\u91CD\u8BD5\uFF0C15 \u6B21\u540E\u8F6C\u5360\u4F4D\u56FE\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tvoid TryLoadAvatar()\n\t{\n\t\tif ( IsBot )\n\t\t{\n\t\t\t// \u51E0\u4F55\u56FE\u6848\u5934\u50CF\uFF1A\u6309\u540D\u5B57\u002B\u8272\u53F7\u786E\u5B9A\u6027\u751F\u6210\uFF08\u5404\u7AEF\u7B97\u51FA\u540C\u4E00\u5F20\uFF0C\u96F6\u540C\u6B65\uFF09\n\t\t\tif ( string.IsNullOrEmpty( PlayerName ) ) return;   // \u540D\u5B57\u540C\u6B65\u672A\u5230\uFF0C\u7B49\u91CD\u8BD5\n\n\t\t\tTexture tex = null;\n\t\t\ttry\n\t\t\t{\n\t\t\t\ttex = BotAvatar.GetOrCreate( PlayerName, ColorIndex );\n\t\t\t}\n\t\t\tcatch ( Exception e )\n\t\t\t{\n\t\t\t\tLog.Warning( $\u0022bot avatar generate failed for \u0027{PlayerName}\u0027: {e.Message}\u0022 );\n\t\t\t}\n\t\t\tApplyAvatarTexture( tex ?? Texture.Load( GameConfig.AvatarPlaceholderPath, false ) );\n\t\t\t_avatarReady = true;\n\t\t\treturn;\n\t\t}\n\n\t\tif ( OwnerSteamId == 0 ) return;   // \u540C\u6B65\u672A\u5230\uFF0C\u7B49\u4E0B\u4E00\u8F6E\u91CD\u8BD5\n\n\t\tvar avatar = Texture.LoadAvatar( OwnerSteamId, 128 );\n\t\tif ( avatar is null ) return;\n\n\t\tApplyAvatarTexture( avatar );\n\t\t_avatarReady = true;\n\t}\n\n\tvoid ApplyAvatarTexture( Texture tex )\n\t{\n\t\tif ( tex is null )\n\t\t{\n\t\t\tLog.Warning( $\u0022avatar load failed: \u0027{PlayerName}\u0027 bot={IsBot} steam={OwnerSteamId}\u0022 );\n\t\t\t_avatar.Enabled = false;\n\t\t\treturn;\n\t\t}\n\n\t\t_avatar.Sprite = Sprite.FromTexture( tex );   // Texture setter \u5DF2\u5E9F\u5F03\uFF0C\u5FC5\u987B\u8D70 Sprite \u8D44\u6E90\n\t\t_avatarColor = IsBot ? null : SampleAverageColor( tex );\n\t}\n\n\t/// \u003Csummary\u003E \u5934\u50CF\u4E3B\u8272\uFF1A\u5168\u56FE\u4E0D\u900F\u660E\u50CF\u7D20\u7684\u5747\u503C\uFF08\u7528\u4F5C\u7403\u5E95\u586B\u5145\u8272\uFF09 \u003C/summary\u003E\n\tstatic Color? SampleAverageColor( Texture tex )\n\t{\n\t\ttry\n\t\t{\n\t\t\tvar pixels = tex.GetPixels( 0 );\n\t\t\tif ( pixels is null || pixels.Length == 0 ) return null;\n\n\t\t\tfloat r = 0f, g = 0f, b = 0f;\n\t\t\tint n = 0;\n\t\t\tfor ( int i = 0; i \u003C pixels.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar p = pixels[i];\n\t\t\t\tif ( p.a \u003C 32 ) continue;\n\t\t\t\tr \u002B= p.r;\n\t\t\t\tg \u002B= p.g;\n\t\t\t\tb \u002B= p.b;\n\t\t\t\tn\u002B\u002B;\n\t\t\t}\n\t\t\tif ( n == 0 ) return null;\n\n\t\t\treturn new Color( r / n / 255f, g / n / 255f, b / n / 255f );\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\t// \u5934\u50CF\u76F4\u5F84 = \u534A\u5F84\uFF08\u5360\u73A9\u5BB6\u5F62\u72B6\u76F4\u5F84\u7684 50%\uFF0C\u7528\u6237\u6307\u5B9A\uFF09\uFF0C\u5C45\u4E2D\uFF1B\n\t\t// \u6B7B\u4EA1\u65F6\u4E00\u5E76\u9690\u85CF\u2014\u2014\u6B64\u524D\u53EA\u85CF\u4E86\u9713\u8679\u73AF\uFF0C\u5934\u50CF\u65B9\u5757\u7559\u5728\u539F\u5730\u50CF\u0022\u5C38\u4F53\u0022\uFF08\u5B9E\u6D4B\uFF09\n\t\tif ( _avatar.IsValid() )\n\t\t{\n\t\t\tvar d = Radius;\n\t\t\t_avatar.Size = new Vector2( d, d );\n\t\t\t_avatar.Enabled = Alive;\n\t\t}\n\n\t\t// \u5934\u50CF\u91CD\u8BD5\uFF1A\u6BCF\u79D2\u4E00\u6B21\uFF0C15 \u6B21\u672A\u62FF\u5230\u8F6C\u5360\u4F4D\u56FE\uFF08bot \u4E5F\u8D70\uFF1A\u540D\u5B57/\u8272\u53F7\u540C\u6B65\u53EF\u80FD\u665A\u4E8E OnStart\uFF09\n\t\tif ( !_avatarReady \u0026\u0026 _sinceAvatarRetry \u003E 1f )\n\t\t{\n\t\t\t_sinceAvatarRetry = 0;\n\t\t\tif ( _avatarRetryCount\u002B\u002B \u003E= 15 )\n\t\t\t{\n\t\t\t\tApplyAvatarTexture( Texture.Load( GameConfig.AvatarPlaceholderPath, false ) );\n\t\t\t\t_avatarReady = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tTryLoadAvatar();\n\t\t\t}\n\t\t}\n\n\t\t// ---- \u5206\u88C2/\u5410\u5B62\u5B50\u8F93\u5165\uFF08M4.11\uFF1A\u6BCF\u5E27\u8DEF\u5F84\uFF0C\u4EC5\u672C\u673A\u771F\u4EBA\u3001\u6D3B\u7403\uFF09----\n\t\t// \u6743\u5A01\u7AEF\u76F4\u63A5\u6267\u884C\uFF1B\u5BA2\u6237\u7AEF\u53D1\u5B9E\u4F8B RPC\uFF08[Rpc.Host]\uFF0Chost \u4FA7\u6821\u9A8C\u540E\u6267\u884C\uFF0C\u5206\u8EAB\u662F host \u6743\u5A01\u6570\u636E\uFF09\u3002\n\t\t// \u97F3\u6548\u5728\u6309\u952E\u77AC\u95F4\u5373\u64AD\uFF08\u672C\u5730\u5373\u65F6\u53CD\u9988\uFF1Bhost \u6821\u9A8C\u4E0D\u8FC7\u9876\u591A\u7A7A\u54CD\u4E00\u6B21\uFF09\n\t\tif ( IsProxy || IsRemoteOwned || IsBot || !Alive ) return;\n\t\tif ( MatchState.MatchOver ) return;   // \u6BD4\u8D5B\u7ED3\u675F\uFF1A\u5168\u573A\u51BB\u7ED3\uFF0C\u8F93\u5165\u4E0D\u54CD\u5E94\n\n\t\t// \u505C\u5728\u7B49\u5F85\u9875\uFF08P \u9000\u5C40/\u6389\u7EBF\u6062\u590D\uFF09\u4E14\u4E0D\u5728\u5BF9\u5C40\u65F6\u505C\u6B62\u64CD\u63A7\uFF1A\u7403\u7559\u5728\u539F\u5730\uFF0C\u9632\u9694\u5C4F\u76F2\u5F00\u3002\n\t\t// \u26A0\uFE0F \u5FC5\u987B\u5E26 !IsMatchStarted\uFF1A\u53EA\u8981\u8FD8\u5728\u5BF9\u5C40\uFF08_gameStarted\uFF09\u5C31\u7EDD\u4E0D\u7981\u8F93\u5165\u2014\u2014\n\t\t// _lobbyWaiting \u82E5\u56E0\u5F02\u5E38\u6B8B\u7559\u4E3A true\uFF0C\u4F1A\u628A\u5BF9\u5C40\u4E2D\u7684\u79FB\u52A8/\u5206\u88C2/\u5410\u5B62\u5B50/\u7784\u51C6\u5168\u9501\u6B7B\n\t\t// \uFF08\u5B9E\u6D4B\uFF1A\u4E0D\u80FD\u5206\u8EAB/\u5410\u4E1D\u3001\u7784\u51C6\u7BAD\u5934\u51BB\u5728\u65E7\u65B9\u5411\uFF09\n\t\t{\n\t\t\tvar g = CircleroyaleGame.Current;\n\t\t\tif ( g is not null \u0026\u0026 g.IsClientWaiting \u0026\u0026 !g.IsMatchStarted )\n\t\t\t{\n\t\t\t\t_lastMoveDir = Vector2.Zero;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tvar inputDir = BallInput.GetMoveDir( Scene.Camera );\n\t\tif ( inputDir.Length \u003E 0.001f ) _lastMoveDir = inputDir;\n\n\t\t// \u7784\u51C6\uFF08v0.7.0.0 \u7528\u6237\u9700\u6C42\uFF09\uFF1A_lastAimDir \u4F9B\u6E32\u67D3\u7784\u51C6\u7EBF\u7528\uFF1B\u52A8\u4F5C\u65B9\u5411\u7531 host \u6309\u8EAB\u4F53\u9010\u4E2A\u7B97\n\t\tvar aimDir = BallInput.GetAimDir( Scene.Camera, WorldPosition );\n\t\tif ( aimDir.Length \u003E 0.001f ) _lastAimDir = aimDir;\n\n\t\t// \u9F20\u6807\u952E\u5E76\u884C\uFF08v0.7.4.1 \u7528\u6237\u9700\u6C42\uFF09\uFF1A\u53F3\u952E\u5206\u88C2\u3001\u5DE6\u952E\u5410\u4E1D\uFF08\u0022Attack1\u0022/\u0022Attack2\u0022 \u4E3A\u5F15\u64CE\u5185\u5EFA\u6309\u94AE\uFF0C\n\t\t// \u5B98\u65B9\u9879\u76EE\u540C\u6B3E\u7528\u6CD5\uFF09\uFF1B\u7A7A\u683C/R \u4FDD\u7559\uFF0C\u4E24\u5957\u952E\u4F4D\u5171\u5B58\u3002\n\t\t// v0.7.8.7 \u624B\u611F\uFF1A\u4F20**\u5149\u6807\u4E16\u754C\u5750\u6807**\u800C\u975E\u65B9\u5411\u2014\u2014host \u5BF9\u6BCF\u4E2A\u8EAB\u4F53\u5355\u72EC\u7B97\u6307\u5411\uFF08\u5206\u8EAB\u4E5F\u671D\u5149\u6807\uFF09\n\t\tvar aimPoint = BallInput.GetAimPoint( Scene.Camera );\n\n\t\tif ( GameConfig.EnableSplit \u0026\u0026 ( Input.Pressed( \u0022Jump\u0022 ) || Input.Pressed( \u0022Attack2\u0022 ) ) )\n\t\t{\n\t\t\tif ( Mass \u003E= GameConfig.SplitMinMass ) GameSfx.Split( WorldPosition );\n\n\t\t\tif ( NetworkManager.IsAuthority )\n\t\t\t\tCircleroyaleGame.Current?.DoSplit( this, aimPoint );\n\t\t\telse\n\t\t\t\tRequestSplit( aimPoint );\n\t\t}\n\n\t\tif ( GameConfig.EnableEject\n\t\t\t\u0026\u0026 ( Input.Down( \u0022Reload\u0022 ) || Input.Down( \u0022Attack1\u0022 ) )\n\t\t\t\u0026\u0026 _sinceEject \u003E GameConfig.EjectCooldownSeconds )\n\t\t{\n\t\t\t_sinceEject = 0;\n\t\t\tif ( Mass \u003E= 2f ) GameSfx.Eject( WorldPosition );   // \u81F3\u5C11\u80FD\u5410\u6700\u5C0F\u4E00\u9897\uFF08\u4E0B\u9650 1 \u002B \u6D88\u8017 1\uFF09\n\n\t\t\tif ( NetworkManager.IsAuthority )\n\t\t\t\tCircleroyaleGame.Current?.DoEject( this, aimPoint );\n\t\t\telse\n\t\t\t\tRequestEject( aimPoint );\n\t\t}\n\n\t\t// \u9053\u5177\u4E3B\u52A8\u4F7F\u7528\uFF08v0.7.5.0\uFF09\uFF1AQ/E \u91CA\u653E\u80CC\u5305\u91CC\u7684\u9053\u5177\u2014\u2014\u6743\u5A01\u7AEF\u76F4\u63A5\u89E6\u53D1\uFF0C\u5BA2\u6237\u7AEF\u53D1 RPC\u3002\n\t\t// banner \u6309\u952E\u5373\u64AD\uFF08\u672C\u5730\u5373\u65F6\u53CD\u9988\uFF0C\u4E0D\u7B49 host \u5F80\u8FD4\uFF09\uFF1B\u69FD\u4F4D\u7A7A\u5219\u4EC0\u4E48\u90FD\u4E0D\u53D1\u751F\n\t\tUsePowerSlot( 0, Input.Keyboard.Pressed( \u0022Q\u0022 ) );\n\t\tUsePowerSlot( 1, Input.Keyboard.Pressed( \u0022E\u0022 ) );\n\t}\n\n\tprotected override void OnFixedUpdate()\n\t{\n\t\tif ( IsProxy ) return;   // \u53EA\u6709\u62E5\u6709\u8005\uFF08\u771F\u4EBA\uFF09\u6216 host\uFF08bot\uFF09\u6A21\u62DF\n\t\tif ( IsRemoteOwned ) return;   // host \u4FA7\u8FDC\u7A0B\u7403\u7531\u5176\u4E3B\u4EBA\u6A21\u62DF\uFF1B\u672A\u7F51\u7EDC\u5316\u7684\u5E7D\u7075\u7403\u7EDD\u4E0D\u672C\u5730\u6A21\u62DF\n\n\t\t// \u6B7B\u4EA1\u72B6\u6001\uFF1A\u505C\u4F4F\u4E0D\u52A8\uFF08\u5BF9\u8C61\u4FDD\u7559\uFF09\uFF1BAlive \u7FFB\u56DE true \u65F6\u672C\u5730\u91CD\u751F\uFF08\u968F\u673A\u6362\u4F4D\u002B\u4FDD\u62A4\u671F\uFF09\n\t\tif ( !Alive )\n\t\t{\n\t\t\t_velocity = Vector3.Zero;\n\t\t\t_wasAlive = false;\n\t\t\treturn;\n\t\t}\n\t\tif ( !_wasAlive )\n\t\t{\n\t\t\t_wasAlive = true;\n\t\t\t_sinceSpawn = 0;          // \u590D\u6D3B\u4FDD\u62A4\u671F\uFF08\u672C\u5730\u8868\u73B0\uFF1B\u6743\u5A01\u4FDD\u62A4\u671F\u5728 host \u7684 Init \u91CC\uFF09\n\t\t\t_velocity = Vector3.Zero;\n\t\t\tWorldPosition = RandomSpawnPos();   // owner \u81EA\u5DF1\u968F\u673A\u6362\u4F4D\uFF08\u4F4D\u7F6E\u5F52 owner \u6A21\u62DF\uFF09\n\t\t\tif ( GameSfx.IsMine( OwnerSteamId ) )   // \u53EA\u64AD\u672C\u673A\u7403\uFF1A\u5F00\u5C40 32 \u4E2A bot \u540C\u5E27\u751F\u6210\u66FE\u53E0 33 \u5C42\u97F3\uFF08v0.7.8.10 \u4FEE\uFF09\n\t\t\t\tGameSfx.Respawn( WorldPosition );\n\t\t\tGameSfx.ResetEatCount();   // \u4E0B\u4E00\u6761\u547D\u5403\u98DF\u7269\u97F3\u4ECE 0 \u91CD\u65B0\u8BA1\n\t\t\tGameSfx.ResetCombo();      // \u8FDE\u51FB\u6E05\u96F6\uFF08\u65B0\u7684\u4E00\u6761\u547D\u91CD\u65B0\u8D77\u7B97\uFF09\n\t\t\tGameLog.Info( $\u0022[ball] local respawn \u0027{PlayerName}\u0027\u0022 );\n\t\t}\n\n\t\t// \u6BD4\u8D5B\u7ED3\u675F\uFF1A\u5168\u573A\u51BB\u7ED3\uFF08\u7ED3\u7B97\u9762\u677F\u63A5\u7BA1\uFF0C\u8C01\u4E5F\u4E0D\u8BB8\u518D\u52A8\uFF09\n\t\tif ( MatchState.MatchOver )\n\t\t{\n\t\t\t_velocity = Vector3.Zero;\n\t\t\treturn;\n\t\t}\n\n\t\t// \u771F\u4EBA = \u672C\u5730\u8F93\u5165\uFF08owner \u6A21\u62DF\uFF09\uFF1Bbot = BotBrain \u5199\u5165\u7684\u65B9\u5411\u3002\n\t\t// \u5206\u88C2/\u5410\u5B62\u5B50\u7684\u8F93\u5165\u5224\u5B9A\u5DF2\u79FB\u5230 OnUpdate\uFF08\u6BCF\u5E27\u8DEF\u5F84\uFF09\u2014\u2014Input \u6309\u952E\u8FB9\u6CBF\u6309\u6E32\u67D3\u5E27\u5237\u65B0\uFF0C\n\t\t// \u56FA\u5B9A\u6B65\u8FDB\u7684 FixedUpdate \u53EF\u80FD\u9519\u8FC7 Pressed \u8FB9\u6CBF\uFF08\u6309\u4E86\u6CA1\u53CD\u5E94\u7684\u9690\u60A3\uFF09\n\t\tvar dir = IsBot ? _botDir : BallInput.GetMoveDir( Scene.Camera );\n\t\tif ( dir.Length \u003E 0.001f ) _lastMoveDir = dir;\n\n\t\t// \u901F\u5EA6\u4E0E\u8F6C\u5411\u90FD\u968F\u8D28\u91CF\u8870\u51CF\uFF08\u627918\u2460\uFF09\uFF1A\u4F53\u79EF\u8D8A\u5927\u6781\u901F\u8D8A\u4F4E\u3001\u60EF\u6027\u8D8A\u5F3A\u8F6C\u5411\u8D8A\u7B28\u62D9\uFF0C\n\t\t// \u5927\u7403\u5FC5\u987B\u63D0\u524D\u9884\u5224\u8D70\u4F4D\u3002\u54CD\u5E94\u65F6\u95F4\u5E38\u6570 = BaseTurnSpeed \u00D7 (StartMass/Mass)^TurnCurve\n\t\tvar turn = MathF.Max( GameConfig.MinTurnSpeed,\n\t\t\tGameConfig.BaseTurnSpeed * MathF.Pow( GameConfig.StartMass / Mass, GameConfig.TurnCurve ) );\n\t\tvar t = 1f - MathF.Exp( -turn * Time.Delta );\n\t\t_velocity = Vector3.Lerp( _velocity, dir * Speed, t );\n\n\t\tvar pos = WorldPosition \u002B _velocity * Time.Delta;\n\n\t\t// \u573A\u5730\u8FB9\u754C\u94B3\u5236\uFF08\u534A\u5F84\u5F02\u5E38\u6DA8\u7834\u573A\u5730\u65F6 half \u515C\u5E95\u4E3A 0\uFF0C\u7EDD\u4E0D\u8BA9 Clamp \u7684 min\u003Emax \u629B\u5F02\u5E38\uFF09\n\t\tvar half = MathF.Max( 0f, GameConfig.ArenaHalfSize - Radius );\n\t\tpos.x = Math.Clamp( pos.x, -half, half );\n\t\tpos.y = Math.Clamp( pos.y, -half, half );\n\t\tpos.z = 0f;\n\n\t\tWorldPosition = pos;\n\n\t\t// \u7403\u4F53\u63A8\u6324\uFF08v0.7.8.24 \u7403\u7403\u5927\u4F5C\u6218\u624B\u611F\uFF09\uFF1A\u5403\u4E0D\u52A8\u5BF9\u65B9=\u5B9E\u5FC3\u2014\u2014\u63A8\u51FA\u4E0E\u654C\u65B9\u7684\u91CD\u53E0\n\t\tApplyPush( half );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u7403\u4F53\u63A8\u6324\uFF08v0.7.8.24 \u7403\u7403\u5927\u4F5C\u6218\u624B\u611F\uFF09\uFF1A**\u5404\u81EA\u628A\u81EA\u5DF1\u63A8\u51FA\u91CD\u53E0**\u2014\u2014\u5BF9\u65B9 bot/host \u7403\u8D70\u540C\u4EE3\u7801\u8DEF\u5F84\u3001\n\t/// \u771F\u4EBA\u7403\u7531\u5176\u4E3B\u4EBA\u63A8\uFF0C\u53CC\u65B9\u6309\u8D28\u91CF\u6743\u91CD\u8BA9\u4F4D\uFF0C\u4E00\u5E27\u5185\u6536\u655B\u5230\u5206\u79BB\uFF0C\u4E0D\u9700\u8981\u4F4D\u7F6E\u6743\u5A01\u4EF2\u88C1\u3002\n\t/// \u961F\u53CB\u4E0E\u81EA\u5DF1\u7684\u8EAB\u4F53\u4E0D\u63A8\uFF08BoB \u60EF\u4F8B\u53EF\u91CD\u53E0\uFF09\uFF1B\u80FD\u5403/\u88AB\u5403\u65F6\u4E0D\u63A8\uFF08\u5403\u5F52 host \u6743\u5A01\uFF0C\u5403\u4E86\u5C31\u4E0D\u5B58\u5728\u91CD\u53E0\uFF09\uFF1B\n\t/// \u5C16\u523A\u5206\u8EAB\u662F\u6B66\u5668\u4E0D\u63A8\uFF08\u78B0\u5230\u8D70\u6B66\u5668\u5224\u5B9A\uFF09\uFF1B\u5B62\u5B50\u4E0D\u63A8\u3002\u4F4D\u7F6E\u662F owner \u6743\u5A01\uFF0C\u63A8\u5B8C\u81EA\u52A8\u540C\u6B65\n\t/// \u003C/summary\u003E\n\tvoid ApplyPush( float half )\n\t{\n\t\tvar game = CircleroyaleGame.Current;\n\t\tif ( game is null ) return;\n\n\t\tforeach ( var b in game.Balls )\n\t\t{\n\t\t\tif ( b is null || !b.IsValid() || !b.Alive || b == this ) continue;\n\t\t\tif ( b.OwnerSteamId == OwnerSteamId ) continue;\n\t\t\tif ( game.SameTeamSteam( OwnerSteamId, b.OwnerSteamId ) ) continue;\n\n\t\t\tvar hi = MathF.Max( Mass, b.Mass );\n\t\t\tvar lo = MathF.Min( Mass, b.Mass );\n\t\t\tif ( hi \u003E= lo * GameConfig.EatRatio ) continue;   // \u80FD\u5403\u5C31\u5403\uFF08host \u6743\u5A01\uFF09\uFF0C\u4E0D\u63A8\n\n\t\t\tvar dx = WorldPosition.x - b.WorldPosition.x;\n\t\t\tvar dy = WorldPosition.y - b.WorldPosition.y;\n\t\t\tvar rr = Radius \u002B b.Radius;\n\t\t\tvar d2 = dx * dx \u002B dy * dy;\n\t\t\tif ( d2 \u003E= rr * rr ) continue;\n\t\t\tvar d = MathF.Sqrt( MathF.Max( d2, 0.0001f ) );\n\t\t\tvar nx = d \u003E 0.001f ? dx / d : 1f;\n\t\t\tvar ny = d \u003E 0.001f ? dy / d : 0f;\n\t\t\t// \u5B8C\u5168\u91CD\u5408\u515C\u5E95\uFF1A\u63A8\u6EE1 rr \u7684\u4E00\u534A\uFF1B\u6B63\u5E38\u60C5\u51B5\u63A8\u0022\u91CD\u53E0 \u00D7 \u5BF9\u65B9\u8D28\u91CF\u5360\u6BD4\u0022\uFF08\u8D8A\u91CD\u8BA9\u5F97\u8D8A\u5C11\uFF09\n\t\t\tvar push = d \u003E 0.001f ? MathF.Min( rr - d, GameConfig.PushMaxStep ) * ( b.Mass / ( Mass \u002B b.Mass ) ) : rr * 0.5f;\n\n\t\t\tWorldPosition = new Vector3(\n\t\t\t\tMath.Clamp( WorldPosition.x \u002B nx * push, -half, half ),\n\t\t\t\tMath.Clamp( WorldPosition.y \u002B ny * push, -half, half ), 0f );\n\t\t}\n\n\t\t// \u654C\u65B9\u5206\u8EAB=\u5B9E\u5FC3\u969C\u788D\uFF08\u81EA\u5DF1\u4E0D\u52A8\uFF0C\u6211\u8BA9\u8DEF\u5168\u989D\u63A8\uFF1B\u80FD\u5403\u65F6\u4E0D\u63A8\u2014\u2014host \u4F1A\u5403\u6389\u5B83\uFF09\n\t\tforeach ( var c in game.Cells )\n\t\t{\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\t\t\tif ( c.OwnerSteamId == OwnerSteamId ) continue;\n\t\t\tif ( game.SameTeamSteam( OwnerSteamId, c.OwnerSteamId ) ) continue;\n\t\t\tif ( Mass \u003E= c.Mass * GameConfig.EatRatio ) continue;\n\n\t\t\tvar dx = WorldPosition.x - c.Pos.x;\n\t\t\tvar dy = WorldPosition.y - c.Pos.y;\n\t\t\tvar rr = Radius \u002B c.Radius;\n\t\t\tvar d2 = dx * dx \u002B dy * dy;\n\t\t\tif ( d2 \u003E= rr * rr ) continue;\n\t\t\tvar d = MathF.Sqrt( MathF.Max( d2, 0.0001f ) );\n\t\t\tvar nx = d \u003E 0.001f ? dx / d : 1f;\n\t\t\tvar ny = d \u003E 0.001f ? dy / d : 0f;\n\t\t\tvar push = d \u003E 0.001f ? MathF.Min( rr - d, GameConfig.PushMaxStep ) : rr * 0.5f;\n\n\t\t\tWorldPosition = new Vector3(\n\t\t\t\tMath.Clamp( WorldPosition.x \u002B nx * push, -half, half ),\n\t\t\t\tMath.Clamp( WorldPosition.y \u002B ny * push, -half, half ), 0f );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u573A\u5730\u5185\u51FA\u751F\u70B9\uFF08owner \u590D\u6D3B\u65F6\u672C\u5730\u9009\u4F4D\uFF09\uFF1A\u8D70\u6E38\u620F\u7CFB\u7EDF\u7684\u5B89\u5168\u91C7\u6837\uFF0C\u65E0\u7CFB\u7EDF\u65F6\u7EAF\u968F\u673A\u515C\u5E95 \u003C/summary\u003E\n\tstatic Vector3 RandomSpawnPos()\n\t{\n\t\tvar game = CircleroyaleGame.Current;\n\t\tif ( game is not null ) return game.RandomSpawnPos();\n\n\t\tvar half = GameConfig.ArenaHalfSize - 128f;\n\t\treturn new Vector3( Game.Random.Float( -half, half ), Game.Random.Float( -half, half ), 0f );\n\t}\n\n\t// ---- \u5206\u88C2/\u5410\u5B62\u5B50 RPC\uFF08M3\uFF09\uFF1A\u5BA2\u6237\u7AEF \u2192 host\uFF0Chost \u6821\u9A8C\u5F52\u5C5E\u540E\u6267\u884C\u3002\n\t// v0.7.8.7\uFF1A\u53C2\u6570\u8BED\u4E49\u6539\u4E3A**\u5149\u6807\u4E16\u754C\u5750\u6807**\uFF08aimPoint\uFF09\uFF0Chost \u9010\u8EAB\u4F53\u7B97\u6307\u5411 ----\n\n\t[Rpc.Host]\n\tpublic void RequestSplit( Vector2 aimPoint )\n\t{\n\t\tvar caller = Rpc.Caller;\n\t\tif ( caller is not null \u0026\u0026 caller.SteamId.Value != OwnerSteamId ) return;   // \u53EA\u80FD\u64CD\u4F5C\u81EA\u5DF1\u7684\u7403\n\n\t\tCircleroyaleGame.Current?.DoSplit( this, aimPoint );\n\t}\n\n\t[Rpc.Host]\n\tpublic void RequestEject( Vector2 aimPoint )\n\t{\n\t\tvar caller = Rpc.Caller;\n\t\tif ( caller is not null \u0026\u0026 caller.SteamId.Value != OwnerSteamId ) return;\n\n\t\tCircleroyaleGame.Current?.DoEject( this, aimPoint );\n\t}\n\n\t[Rpc.Host]\n\tpublic void RequestUsePower( int slot )\n\t{\n\t\tvar caller = Rpc.Caller;\n\t\tif ( caller is not null \u0026\u0026 caller.SteamId.Value != OwnerSteamId ) return;   // \u53EA\u80FD\u64CD\u4F5C\u81EA\u5DF1\u7684\u7403\n\n\t\tUsePower( slot );\n\t}\n\n\tprotected override void OnDestroy()\n\t{\n\t\tbase.OnDestroy();\n\t\tCircleroyaleGame.Current?.RemoveBall( this );\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"Player/CellPiece.cs","FileName":"CellPiece.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// \u5206\u88C2\u7EC6\u80DE / \u5410\u51FA\u7684\u5B62\u5B50\uFF08**\u7EAF\u6570\u636E\u5B9E\u4F53\uFF0C\u4E0D\u662F GameObject**\uFF09\u3002\n/// \u4E3A\u4EC0\u4E48\u4E0D\u8D70\u7F51\u7EDC\u5BF9\u8C61\uFF1A\u8FD0\u884C\u65F6 live create \u5E7F\u64AD\u5230\u5DF2\u8FDE\u63A5\u5BA2\u6237\u7AEF\u7684\u53EF\u9760\u6027\u672A\u9A8C\u8BC1\uFF08M2 \u9057\u7559\uFF09\uFF0C\n/// \u800C\u98DF\u7269\u540C\u6B3E\u0022\u9759\u6001 RPC \u5FEB\u7167\u0022\u662F\u672C\u9879\u76EE\u5B9E\u6D4B 100% \u53EF\u9760\u7684\u552F\u4E00\u540C\u6B65\u901A\u9053\u3002\n/// host \u6743\u5A01\u6A21\u62DF\uFF08\u51B2\u91CF\u8870\u51CF \u002B \u8DDF\u968F\u4E3B\u4EBA\u8F6C\u5411\uFF09\uFF0C\u72B6\u6001\u7ECF CellsState \u9759\u6001 RPC \u6309 CellSyncInterval\n/// \u5E7F\u64AD\uFF1B\u5404\u7AEF\u672C\u5730\u955C\u50CF\u5217\u8868\u53EA\u8D1F\u8D23\u6E32\u67D3\u4E0E\u5E73\u6ED1\uFF08DrawPos \u8FFD Pos\uFF0C\u5BA2\u6237\u7AEF\u628A 15Hz \u5FEB\u7167\u63D2\u987A\uFF09\u3002\n/// \u003C/summary\u003E\npublic sealed class CellPiece\n{\n\tpublic enum Kind : byte\n\t{\n\t\tSplitPiece = 0,   // \u5206\u88C2\u51FA\u7684\u7EC6\u80DE\uFF08\u8DDF\u968F\u4E3B\u4EBA\u65B9\u5411\u79FB\u52A8\uFF0C\u51B7\u5374\u540E\u5E76\u56DE\uFF09\n\t\tEjectedMass = 1,  // \u5410\u51FA\u7684\u5B62\u5B50\uFF08\u4E2D\u6027\u98DF\u7269\u56E2\uFF0C\u8C01\u90FD\u80FD\u5403\uFF09\n\t\tSpikeMinion = 2,  // \u5C16\u523A\u5206\u8EAB\uFF08v0.7.4.0 \u9053\u5177\u751F\u6210\uFF09\uFF1A\u8DDF\u968F\u4E3B\u4EBA 20s\uFF0C\u5230\u671F\u53D8\u56DE\u666E\u901A\u5206\u8EAB\uFF1B\u654C\u65B9\u6309\u5C16\u523A\u89C4\u5219\u5224\u5B9A\n\t}\n\n\tpublic int Id;\n\n\tpublic Kind PieceKind;\n\n\t/// \u003Csummary\u003E \u4E3B\u4EBA SteamId\uFF08bot \u4E3A 0\uFF1Bbot \u4E0D\u5206\u88C2\uFF0C\u6B64\u5B57\u6BB5\u4EC5\u771F\u4EBA\u6709\u503C\uFF09 \u003C/summary\u003E\n\tpublic long OwnerSteamId;\n\n\tpublic int ColorIndex;\n\n\tpublic float Mass;\n\n\t/// \u003Csummary\u003E \u6743\u5A01\u4F4D\u7F6E\uFF08host \u6BCF\u5E27\u63A8\u8FDB\uFF1B\u5BA2\u6237\u7AEF = \u6700\u8FD1\u4E00\u6B21\u5FEB\u7167\u503C\uFF0C15Hz \u8DF3\u53D8\uFF09 \u003C/summary\u003E\n\tpublic Vector3 Pos;\n\n\t/// \u003Csummary\u003E \u6E32\u67D3\u4F4D\u7F6E\uFF1A\u5411 Pos \u6307\u6570\u5E73\u6ED1\uFF08host \u9010\u5E27\u6743\u5A01\u76F4\u63A5\u8D34\u9F50\uFF1B\u5BA2\u6237\u7AEF\u63D2\u503C\uFF09 \u003C/summary\u003E\n\tpublic Vector3 DrawPos;\n\n\t/// \u003Csummary\u003E \u5F39\u5C04\u51B2\u91CF\uFF08\u5206\u88C2/\u5410\u51FA\u77AC\u95F4\uFF09\uFF0C\u968F\u65F6\u95F4\u6307\u6570\u8870\u51CF \u003C/summary\u003E\n\tpublic Vector3 Impulse;\n\n\t/// \u003Csummary\u003E \u8DDF\u968F\u4E3B\u4EBA\u79FB\u52A8\u65B9\u5411\u7684\u901F\u5EA6\u5206\u91CF\uFF08\u4EC5 host \u6A21\u62DF\uFF09 \u003C/summary\u003E\n\tpublic Vector3 SteerVel;\n\n\t/// \u003Csummary\u003E \u51FA\u751F\u65F6\u523B\uFF1A\u5408\u5E76\u51B7\u5374\uFF08\u5206\u8EAB\uFF09\u4E0E\u53EF\u98DF\u5EF6\u8FDF\uFF08\u5B62\u5B50\uFF09\u90FD\u770B\u5B83 \u003C/summary\u003E\n\tpublic TimeSince SinceSpawn;\n\n\t/// \u003Csummary\u003E \u4EC5 host\uFF1A\u4E3B\u4EBA\u7403\u5F15\u7528\uFF08\u5206\u8EAB\u6E05\u7406\u3001\u8F6C\u5411\u65B9\u5411\u6765\u6E90\uFF09\uFF1B\u5B62\u5B50\u6052\u4E3A null\uFF08\u4E2D\u6027\u5B58\u6D3B\uFF09 \u003C/summary\u003E\n\tpublic Ball OwnerBall;\n\n\t/// \u003Csummary\u003E \u534A\u5F84\u6CBF\u7528\u7403\u7684\u8D28\u91CF\u5B9A\u5F8B\uFF1Ar = StartRadius \u00D7 \u221A(mass/StartMass) \u003C/summary\u003E\n\tpublic float Radius =\u003E GameConfig.StartRadius * MathF.Sqrt( MathF.Max( 1f, Mass ) / GameConfig.StartMass );\n\n\t/// \u003Csummary\u003E \u79FB\u52A8\u901F\u5EA6\u6CBF\u7528\u7403\u7684\u901F\u5EA6\u66F2\u7EBF\uFF1A\u8D8A\u5927\u8D8A\u6162 \u003C/summary\u003E\n\tpublic float Speed =\u003E MathF.Max( GameConfig.MinSpeed,\n\t\tGameConfig.BaseSpeed * MathF.Pow( GameConfig.StartMass / MathF.Max( 1f, Mass ), GameConfig.SpeedCurve ) );\n\n\t/// \u003Csummary\u003E \u5408\u5E76\u51B7\u5374\u65F6\u957F\uFF08\u79D2\uFF09\uFF1A\u4E3B\u52A8\u5206\u88C2\u6309\u8D28\u91CF\u66F2\u7EBF\uFF08MergeCooldownFor\uFF09\uFF1B\u649E\u523A\u788E\u7247\u77ED\u51B7\u5374\uFF08SpikePieceMergeCooldown\uFF09 \u003C/summary\u003E\n\tpublic float MergeCooldown = GameConfig.MergeCooldownBase;\n\n\t/// \u003Csummary\u003E \u5408\u5E76\u51B7\u5374\u662F\u5426\u5DF2\u8FC7\uFF08\u4EC5\u5206\u8EAB\u6709\u610F\u4E49\uFF09 \u003C/summary\u003E\n\tpublic bool IsMergeReady =\u003E SinceSpawn \u003E MergeCooldown;\n}\n\n/// \u003Csummary\u003E\n/// \u523A\u7206\u5F39\u5E55\uFF08v0.7.8.24\uFF09\uFF1A\u5582\u523A\u5582\u6EE1\u540E\u4ECE\u523A\u5C04\u51FA\u7684\u5C16\u523A\u5F39\u3002host \u6743\u5A01\u6A21\u62DF\uFF08\u542B\u547D\u4E2D\u5224\u5B9A\uFF09\uFF0C\n/// \u5BA2\u6237\u7AEF\u4E3A\u7EAF\u89C6\u89C9\u5F39\u9053\u955C\u50CF\uFF08SpineDir \u516C\u5F0F\u53CC\u7AEF\u4E00\u81F4\uFF0C\u89C6\u89C9\u5F39\u9053=\u6743\u5A01\u5F39\u9053\uFF09\u3002\n/// \u003C/summary\u003E\npublic sealed class SpikeSpine\n{\n\tpublic Vector2 Pos;\n\n\tpublic Vector2 Vel;\n\n\t/// \u003Csummary\u003E \u51FA\u751F\u65F6\u523B\uFF08SpineLife \u5230\u671F\u6D88\u6563\uFF09 \u003C/summary\u003E\n\tpublic TimeSince SinceSpawn;\n}\n\n/// \u003Csummary\u003E CellsState RPC \u7684\u7EBF\u683C\u5F0F\uFF08\u5168 blittable \u5B57\u6BB5\uFF0C\u540C FoodData \u6A21\u5F0F\uFF09 \u003C/summary\u003E\npublic struct CellWire\n{\n\tpublic int Id;\n\tpublic byte Kind;        // CellPiece.Kind\n\tpublic byte ColorIndex;\n\tpublic long Owner;       // OwnerSteamId\n\tpublic float X;\n\tpublic float Y;\n\tpublic float Mass;\n}\n"},{"Ident":"slfy.circleroyale","Path":"ui/lobbypanel.cs.scss","FileName":"lobbypanel.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"LobbyPanel {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\tfont-family: SimHei;\n\tcolor: white;\n\tpointer-events: all;\n\n\t.lp-title {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 88px;\n\t\tfont-size: 48px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 10px;\n\t\ttext-align: center;\n\t\tcolor: #35f0ff;\n\t\ttext-shadow: 0 0 26px rgba( 53, 240, 255, 0.85 );\n\t}\n\n\t.lp-status {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 168px;\n\t\tfont-size: 15px;\n\t\tletter-spacing: 3px;\n\t\ttext-align: center;\n\t\tcolor: #ffb63b;\n\t}\n\n\t.lp-head {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 246px;\n\t\tfont-size: 12px;\n\t\tletter-spacing: 4px;\n\t\ttext-align: center;\n\t\tcolor: rgba( 255, 255, 255, 0.32 );\n\t}\n\n\t.lp-head.players {\n\t\ttop: 350px;\n\t}\n\n\t.lp-setting {\n\t\tposition: absolute;\n\t\ttop: 282px;\n\t\twidth: 33.33%;\n\t\tfont-size: 17px;\n\t\tletter-spacing: 2px;\n\t\ttext-align: center;\n\t\tcolor: #9ad8ff;\n\t\ttext-shadow: 0 0 10px rgba( 0, 0, 0, 0.6 );\n\t\tcursor: pointer;\n\t\tpointer-events: all;   // \u9762\u677F\u9ED8\u8BA4 None\uFF1B\u5BA2\u6237\u7AEF\u70B9\u51FB\u7531 CycleSetting \u7684 _isHost \u5B88\u536B\u6321\u6389\n\t}\n\n\t.lp-setting:hover {\n\t\tcolor: #35f0ff;\n\t}\n\n\t.lp-setting.set-mode { left: 0; }\n\t.lp-setting.set-time { left: 33.33%; }\n\t.lp-setting.set-players { left: 66.66%; }\n\n\t// \u8BBE\u7F6E\u53EF\u70B9\u63D0\u793A\uFF08v0.7.8.0\uFF0C\u4EC5 host\uFF09\uFF1A\u6A21\u5F0F\u884C\u547C\u5438\u91D1\u5149\u4E0B\u65B9\u7684\u5F15\u5BFC\u5C0F\u5B57\n\t.lp-setting-hint {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 318px;\n\t\tfont-size: 12px;\n\t\tletter-spacing: 4px;\n\t\ttext-align: center;\n\t\tcolor: rgba( 255, 199, 80, 0.55 );\n\t\ttext-shadow: 0 0 10px rgba( 0, 0, 0, 0.8 );\n\t}\n\n\t// \u9053\u5177\u56FE\u4F8B\uFF08v0.7.8.4 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u79FB\u5230\u5DE6\u4E0B\u89D2\uFF08\u73A9\u5BB6\u5217\u8868\u5DE6\u5217\u7684\u4E0B\u65B9\u7A7A\u767D\uFF0C\u7EA2\u7BAD\u5934\u4F4D\u7F6E\uFF09\n\t.lp-legend-head {\n\t\tposition: absolute;\n\t\tleft: 64px;\n\t\ttop: 660px;\n\t\twidth: 440px;\n\t\tfont-size: 12px;\n\t\tletter-spacing: 4px;\n\t\ttext-align: left;\n\t\tcolor: rgba( 255, 255, 255, 0.32 );\n\t}\n\n\t.lp-legend-name {\n\t\tposition: absolute;\n\t\tleft: 64px;\n\t\twidth: 200px;\n\t\tfont-size: 15px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 2px;\n\t\ttext-align: left;\n\t\ttext-shadow: 0 0 8px rgba( 0, 0, 0, 0.9 );\n\t}\n\n\t.lp-legend-desc {\n\t\tposition: absolute;\n\t\tleft: 268px;\n\t\twidth: 290px;\n\t\tfont-size: 13px;\n\t\tletter-spacing: 1px;\n\t\ttext-align: left;\n\t\tcolor: rgba( 255, 255, 255, 0.62 );\n\t\ttext-shadow: 0 0 8px rgba( 0, 0, 0, 0.9 );\n\t}\n\n\t.lp-legend-note {\n\t\tposition: absolute;\n\t\tleft: 64px;\n\t\ttop: 862px;\n\t\twidth: 490px;\n\t\tfont-size: 11px;\n\t\tletter-spacing: 2px;\n\t\ttext-align: left;\n\t\tcolor: rgba( 255, 255, 255, 0.32 );\n\t\ttext-shadow: 0 0 8px rgba( 0, 0, 0, 0.9 );\n\t}\n\n\t.lp-player {\n\t\tfont-size: 16px;\n\t\tletter-spacing: 1px;\n\t\ttext-align: left;\n\t\ttext-shadow: 0 0 8px rgba( 0, 0, 0, 0.9 );\n\t}\n\n\t// BOTS \u63D0\u793A / LEAVE\uFF08v0.7.8.5 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u79FB\u5165\u53F3\u4E0B\u84DD\u6846\u533A\u57DF\uFF0C\u4E0E START \u7AD6\u6392\u53F3\u5BF9\u9F50\n\t.lp-note {\n\t\tposition: absolute;\n\t\tright: 64px;\n\t\ttop: 690px;\n\t\twidth: 520px;\n\t\tfont-size: 12px;\n\t\tletter-spacing: 3px;\n\t\ttext-align: right;\n\t\tcolor: rgba( 255, 255, 255, 0.3 );\n\t}\n\n\t.lp-countdown {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 548px;\n\t\tfont-family: Inter;\n\t\tfont-size: 40px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 8px;\n\t\ttext-align: center;\n\t\tcolor: #ffcf5c;\n\t\ttext-shadow: 0 0 24px rgba( 255, 207, 92, 0.8 );\n\t}\n\n\t// START / JOIN GAME\uFF08v0.7.8.5 \u7528\u6237\u5B9A\u7A3F\uFF09\uFF1A\u53F3\u4E0B\u84DD\u6846\u533A\u57DF\u4E2D\u90E8\n\t.lp-start {\n\t\tposition: absolute;\n\t\tright: 64px;\n\t\ttop: 780px;\n\t\tfont-size: 26px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 6px;\n\t\ttext-align: right;\n\t\tcolor: #35f0ff;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\ttext-shadow: 0 0 18px rgba( 53, 240, 255, 0.7 );\n\t}\n\n\t.lp-start:hover {\n\t\tcolor: #ffffff;\n\t\ttext-shadow: 0 0 26px rgba( 53, 240, 255, 1 );\n\t}\n\n\t.lp-join {\n\t\tposition: absolute;\n\t\tright: 64px;\n\t\ttop: 780px;\n\t\tfont-size: 26px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 6px;\n\t\ttext-align: right;\n\t\tcolor: #ffcf5c;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\ttext-shadow: 0 0 18px rgba( 255, 207, 92, 0.7 );\n\t}\n\n\t.lp-join:hover {\n\t\tcolor: #ffffff;\n\t\ttext-shadow: 0 0 26px rgba( 255, 207, 92, 1 );\n\t}\n\n\t.lp-leave {\n\t\tposition: absolute;\n\t\tright: 64px;\n\t\ttop: 880px;\n\t\tfont-size: 15px;\n\t\tletter-spacing: 4px;\n\t\ttext-align: right;\n\t\tcolor: rgba( 255, 255, 255, 0.45 );\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t}\n\n\t.lp-leave:hover {\n\t\tcolor: rgba( 255, 255, 255, 0.9 );\n\t}\n}\n"},{"Ident":"slfy.circleroyale","Path":"ui/mainmenu.cs.scss","FileName":"mainmenu.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"MainMenu {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\tfont-family: Inter;\n\tcolor: white;\n\tpointer-events: all;   // \u26A0\uFE0F \u53EA\u8BA4 auto/none/all\uFF1A\u5199 normal \u975E\u6CD5\u88AB\u5FFD\u7565\uFF08\u9762\u677F\u9ED8\u8BA4 None=\u9F20\u6807\u5168\u6B7B\uFF09\n\n\t.title {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 170px;\n\t\tfont-size: 84px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 12px;\n\t\tcolor: #35f0ff;\n\t\ttext-shadow: 0 0 34px rgba( 53, 240, 255, 0.85 );\n\t\ttext-align: center;\n\t}\n\n\t.subtitle {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 286px;\n\t\tfont-size: 15px;\n\t\tletter-spacing: 6px;\n\t\tcolor: rgba( 255, 255, 255, 0.4 );\n\t\ttext-align: center;\n\t}\n\n\t// \u8D26\u53F7\u7B49\u7EA7\u89D2\u6807\uFF08v0.7.8.19\uFF09\uFF1A\u4E91\u7AEF XP \u52A0\u8F7D\u5B8C\u6210\u624D\u663E\u793A\n\t.mlevel {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 326px;\n\t\tfont-size: 15px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 3px;\n\t\tcolor: #ffcf5c;\n\t\ttext-align: center;\n\t\ttext-shadow: 0 0 14px rgba( 255, 207, 92, 0.45 );\n\t}\n\n\t.btn {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tfont-size: 26px;\n\t\tfont-weight: 600;\n\t\tletter-spacing: 4px;\n\t\tcolor: rgba( 255, 255, 255, 0.85 );\n\t\ttext-align: center;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\ttext-shadow: 0 0 12px rgba( 53, 240, 255, 0.3 );\n\t}\n\n\t.btn.host { top: 380px; }\n\t.btn.join { top: 442px; }\n\t.btn.bots { top: 504px; }\n\t.btn.settings { top: 566px; }\n\t.btn.rankings { top: 628px; }\n\t.btn.quit { top: 690px; color: rgba( 255, 255, 255, 0.4 ); }\n\n\t.btn:hover {\n\t\tcolor: #35f0ff;\n\t\ttext-shadow: 0 0 20px rgba( 53, 240, 255, 0.9 );\n\t}\n\n\t.status {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 720px;\n\t\tfont-size: 14px;\n\t\tletter-spacing: 2px;\n\t\tcolor: #ffb63b;\n\t\ttext-align: center;\n\t}\n\n\t// \u6BD4\u8D5B\u53C2\u6570\u8BBE\u7F6E\u5DF2\u79FB\u5165\u6E38\u620F\u623F\u95F4\uFF08LobbyPanel\uFF0CM5 \u4E8C\u7EA7\u754C\u9762\uFF09\n\n\t.version {\n\t\tposition: absolute;\n\t\tright: 28px;\n\t\ttop: 8px;\n\t\tfont-size: 11px;\n\t\tletter-spacing: 2px;\n\t\tcolor: rgba( 255, 255, 255, 0.35 );\n\t}\n\n\t.hint {\n\t\tposition: absolute;\n\t\tleft: 30px;\n\t\tbottom: 18px;\n\t\tfont-size: 14px;\n\t\tletter-spacing: 2px;\n\t\tcolor: rgba( 255, 255, 255, 0.42 );\n\t}\n}\n"},{"Ident":"slfy.circleroyale","Path":"ui/rankingspanel.cs.scss","FileName":"rankingspanel.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"RankingsPanel {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\tfont-family: SimHei;\n\tcolor: white;\n\tpointer-events: all;\n\n\t.rk-title {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 88px;\n\t\tfont-size: 48px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 10px;\n\t\ttext-align: center;\n\t\tcolor: #ffcf5c;\n\t\ttext-shadow: 0 0 26px rgba( 255, 207, 92, 0.8 );\n\t}\n\n\t.rk-status {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 170px;\n\t\tfont-size: 14px;\n\t\tletter-spacing: 3px;\n\t\ttext-align: center;\n\t\tcolor: rgba( 255, 255, 255, 0.4 );\n\t}\n\n\t.rk-head {\n\t\tposition: absolute;\n\t\tleft: 460px;\n\t\twidth: 1000px;\n\t\ttop: 252px;\n\t\tfont-size: 12px;\n\t\tletter-spacing: 2px;\n\t\ttext-align: left;\n\t\tcolor: rgba( 255, 255, 255, 0.3 );\n\t}\n\n\t.rk-row {\n\t\tfont-size: 19px;\n\t\tletter-spacing: 1px;\n\t\ttext-align: left;\n\t\ttext-shadow: 0 0 8px rgba( 0, 0, 0, 0.9 );\n\t}\n\n\t.rk-self {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 830px;\n\t\tfont-size: 17px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 3px;\n\t\ttext-align: center;\n\t\tcolor: #35f0ff;\n\t\ttext-shadow: 0 0 14px rgba( 53, 240, 255, 0.6 );\n\t}\n\n\t.rk-back {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 936px;\n\t\tfont-size: 15px;\n\t\tletter-spacing: 4px;\n\t\ttext-align: center;\n\t\tcolor: rgba( 255, 255, 255, 0.45 );\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t}\n\n\t.rk-back:hover {\n\t\tcolor: rgba( 255, 255, 255, 0.9 );\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"Game/FoodManager.cs","FileName":"FoodManager.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// \u98DF\u7269\u7BA1\u7406\uFF08**\u6BCF\u53F0\u673A\u5668\u4E00\u4EFD\u672C\u5730\u5B9E\u4F8B\uFF0C\u4E0D\u8D70\u5BF9\u8C61\u590D\u5236**\u2014\u2014NetList \u6302\u5BF9\u8C61\u65E0\u6CD5\u590D\u5236\u5230\u5BA2\u6237\u7AEF\uFF0C\u5B9E\u6D4B\uFF09\uFF1A\n/// host \u6743\u5A01\u751F\u6210/\u5403/\u91CD\u751F\uFF1B\u72B6\u6001\u53D8\u5316\u7ECF NetworkManager \u7684\u9759\u6001 RPC \u5E7F\u64AD\uFF0C\u5404\u7AEF\u5E94\u7528\u5230\u672C\u5730\u5217\u8868\u3002\n/// Init/TryEat/Tick \u4EC5\u6743\u5A01\u7AEF\u8C03\u7528\uFF1BApply* \u4EC5\u4F9B\u5BA2\u6237\u7AEF\u5E94\u7528\u8FDC\u7AEF\u4E8B\u4EF6\u3002\n/// \u7A7A\u95F4\u54C8\u5E0C\uFF08v0.6.3.0\uFF09\uFF1A3 \u500D\u573A\u5730 4140 \u9897\u98DF\u7269\u540E\uFF0C\u5403\u5224\u5B9A O(\u7403\u6570\u00D7\u98DF\u7269)\u224820 \u4E07\u6B21/\u5E27\u6210\u4E3A\u74F6\u9888\u2014\u2014\n/// 512 \u5355\u4F4D\u6876\u67E5\u8BE2\u628A\u6BCF\u7403\u68C0\u67E5\u91CF\u4ECE 4140 \u964D\u5230\u51E0\u5341\u3002\n/// \u003C/summary\u003E\npublic sealed class FoodManager\n{\n\tFoodData[] _foods;\n\tTimeSince[] _eatenAt;                           // \u4EC5 host\uFF08\u91CD\u751F\u8BA1\u65F6\uFF09\n\treadonly List\u003Cint\u003E _pendingRespawn = new();     // \u4EC5 host\n\n\t// ---- \u7A7A\u95F4\u54C8\u5E0C\uFF08\u4EC5 host \u5403\u5224\u5B9A\u7528\uFF1B\u5BA2\u6237\u7AEF\u65E0\u5403\u5224\u5B9A\u4E0D\u5EFA\u6876\uFF09----\n\tconst float BucketSize = 512f;\n\tDictionary\u003Cint, List\u003Cint\u003E\u003E _buckets;\n\tint _cols;\n\treadonly List\u003Cint\u003E _nearResults = new();\n\n\t/// \u003Csummary\u003E \u5B58\u6D3B\u98DF\u7269\u6570 \u003C/summary\u003E\n\tpublic int AliveCount { get; private set; }\n\n\t/// \u003Csummary\u003E \u5168\u91CF\u98DF\u7269\uFF08NeonRenderer \u6BCF\u5E27\u8BFB\uFF1B\u5404\u7AEF\u5185\u5BB9\u4E00\u81F4\uFF09 \u003C/summary\u003E\n\tpublic FoodData[] Foods =\u003E _foods;\n\n\tint BucketKey( Vector2 p )\n\t{\n\t\tvar half = GameConfig.ArenaHalfSize;\n\t\tint bx = Math.Clamp( (int) ( ( p.x \u002B half ) / BucketSize ), 0, _cols - 1 );\n\t\tint by = Math.Clamp( (int) ( ( p.y \u002B half ) / BucketSize ), 0, _cols - 1 );\n\t\treturn by * _cols \u002B bx;\n\t}\n\n\tvoid BucketAdd( int index ) =\u003E BucketAddAt( index, _foods[index].Pos );\n\n\tvoid BucketAddAt( int index, Vector2 p )\n\t{\n\t\tint key = BucketKey( p );\n\t\tif ( !_buckets.TryGetValue( key, out var list ) )\n\t\t{\n\t\t\tlist = new List\u003Cint\u003E( 16 );\n\t\t\t_buckets[key] = list;\n\t\t}\n\t\tlist.Add( index );\n\t}\n\n\tvoid BucketMove( int index, Vector2 from )\n\t{\n\t\tint oldKey = BucketKey( from );\n\t\tif ( _buckets.TryGetValue( oldKey, out var list ) )\n\t\t\tlist.Remove( index );\n\n\t\tBucketAdd( index );\n\t}\n\n\t/// \u003Csummary\u003E \u5706\u5F62\u8303\u56F4\u5185\u7684\u5B58\u6D3B\u98DF\u7269\u4E0B\u6807\uFF08\u8FD1\u4F3C\uFF1A\u6309\u6876\u7C97\u7B5B\uFF0C\u8C03\u7528\u65B9\u9700\u7CBE\u786E\u590D\u6838\u8DDD\u79BB\uFF09\u3002\n\t/// \u8FD4\u56DE\u5185\u90E8\u590D\u7528\u5217\u8868\uFF0C\u4E0B\u4E00\u6B21\u8C03\u7528\u5373\u5931\u6548\u3002\u4EC5 host\uFF08\u5403\u5224\u5B9A\u6743\u5A01\u7AEF\uFF09\u3002 \u003C/summary\u003E\n\tpublic List\u003Cint\u003E Nearby( Vector2 pos, float radius )\n\t{\n\t\t_nearResults.Clear();\n\t\tif ( _buckets is null || _foods is null ) return _nearResults;\n\n\t\tint minBx = Math.Clamp( (int) ( ( pos.x - radius \u002B GameConfig.ArenaHalfSize ) / BucketSize ), 0, _cols - 1 );\n\t\tint maxBx = Math.Clamp( (int) ( ( pos.x \u002B radius \u002B GameConfig.ArenaHalfSize ) / BucketSize ), 0, _cols - 1 );\n\t\tint minBy = Math.Clamp( (int) ( ( pos.y - radius \u002B GameConfig.ArenaHalfSize ) / BucketSize ), 0, _cols - 1 );\n\t\tint maxBy = Math.Clamp( (int) ( ( pos.y \u002B radius \u002B GameConfig.ArenaHalfSize ) / BucketSize ), 0, _cols - 1 );\n\n\t\tfor ( int by = minBy; by \u003C= maxBy; by\u002B\u002B )\n\t\t{\n\t\t\tfor ( int bx = minBx; bx \u003C= maxBx; bx\u002B\u002B )\n\t\t\t{\n\t\t\t\tif ( !_buckets.TryGetValue( by * _cols \u002B bx, out var list ) ) continue;\n\n\t\t\t\tforeach ( var idx in list )\n\t\t\t\t{\n\t\t\t\t\tif ( _foods[idx].Alive ) _nearResults.Add( idx );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn _nearResults;\n\t}\n\n\t/// \u003Csummary\u003E host \u6295\u98DF\uFF08\u6743\u5A01\u7AEF\uFF1B\u968F\u540E\u7ECF FoodFull RPC \u53D1\u7ED9\u65B0\u8FDE\u63A5\uFF09 \u003C/summary\u003E\n\tpublic void Init()\n\t{\n\t\t_foods = new FoodData[GameConfig.FoodCount];\n\t\t_eatenAt = new TimeSince[GameConfig.FoodCount];\n\t\t_pendingRespawn.Clear();\n\t\t_buckets = new Dictionary\u003Cint, List\u003Cint\u003E\u003E( 1024 );\n\t\t_cols = Math.Max( 1, (int) ( GameConfig.ArenaHalfSize * 2f / BucketSize ) \u002B 1 );\n\n\t\tfor ( int i = 0; i \u003C _foods.Length; i\u002B\u002B )\n\t\t{\n\t\t\t_foods[i] = MakeFood();\n\t\t\tBucketAdd( i );\n\t\t}\n\t\tAliveCount = _foods.Length;\n\t}\n\n\t/// \u003Csummary\u003E \u6BCF\u5E27\uFF1A\u628A\u5230\u671F\u7684\u91CD\u751F\u4F4D\u91CD\u65B0\u6295\u98DF\u5E76\u5E7F\u64AD\uFF08\u4EC5\u6743\u5A01\u7AEF\uFF09 \u003C/summary\u003E\n\tpublic void Tick()\n\t{\n\t\tfor ( int i = _pendingRespawn.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tint idx = _pendingRespawn[i];\n\t\t\tif ( _eatenAt[idx] \u003C GameConfig.FoodRespawnSeconds ) continue;\n\n\t\t\tvar oldPos = _foods[idx].Pos;\n\t\t\t_foods[idx] = MakeFood();\n\t\t\t_pendingRespawn.RemoveAt( i );\n\t\t\tAliveCount\u002B\u002B;\n\t\t\tBucketMove( idx, oldPos );\n\t\t\tif ( Networking.IsActive ) NetworkManager.FoodRespawned( idx, _foods[idx] );   // \u65E0\u4F1A\u8BDD\uFF08\u83DC\u5355\u6F14\u793A\u8D5B\uFF09\u4E0D\u5E7F\u64AD\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u5C1D\u8BD5\u5403\u6389\u6307\u5B9A\u98DF\u7269\uFF1B\u6210\u529F\u8FD4\u56DE true\uFF08\u8C03\u7528\u65B9\u636E\u6B64\u52A0\u8D28\u91CF\uFF09\u3002\u4EC5\u6743\u5A01\u7AEF\uFF0C\u5185\u90E8\u5E7F\u64AD\u3002\n\t/// eaterSteamId \u968F\u5E7F\u64AD\u4E0B\u53D1\uFF0C\u5BA2\u6237\u7AEF\u636E\u6B64\u5224\u65AD\u662F\u4E0D\u662F\u81EA\u5DF1\u7684\u5634\uFF08\u64AD\u5403\u97F3\uFF09 \u003C/summary\u003E\n\tpublic bool TryEat( int index, long eaterSteamId = 0 )\n\t{\n\t\tif ( (uint)index \u003E= (uint)_foods.Length || !_foods[index].Alive ) return false;\n\n\t\t_foods[index].Alive = false;\n\t\t_eatenAt[index] = 0;\n\t\t_pendingRespawn.Add( index );\n\t\tAliveCount--;\n\t\tif ( Networking.IsActive ) NetworkManager.FoodEaten( index, eaterSteamId );   // \u65E0\u4F1A\u8BDD\u4E0D\u5E7F\u64AD\n\t\treturn true;\n\t}\n\n\tFoodData MakeFood()\n\t{\n\t\tvar half = GameConfig.ArenaHalfSize - GameConfig.FoodMargin;\n\t\treturn new FoodData\n\t\t{\n\t\t\tPos = new Vector2( Game.Random.Float( -half, half ), Game.Random.Float( -half, half ) ),\n\t\t\tColorIndex = (byte)Game.Random.Float( 0f, GameConfig.Palette.Length - 0.001f ),\n\t\t\tAlive = true,\n\t\t\tType = 0,\n\t\t};\n\t}\n\n\t// ---- \u5BA2\u6237\u7AEF\u5E94\u7528\uFF08\u8FDC\u7AEF\u4E8B\u4EF6 \u2192 \u672C\u5730\u5217\u8868\uFF09----\n\n\tpublic void ApplyFull( FoodData[] foods )\n\t{\n\t\t_foods = foods;\n\t\tAliveCount = 0;\n\t\tforeach ( var f in _foods )\n\t\t{\n\t\t\tif ( f.Alive ) AliveCount\u002B\u002B;\n\t\t}\n\t}\n\n\tpublic void ApplyEaten( int index )\n\t{\n\t\tif ( _foods is null ) return;   // \u70ED\u91CD\u8F7D\u540E\u65B0\u5B9E\u4F8B\u672A\u540C\u6B65\u5168\u91CF\u524D\uFF0C\u5FFD\u7565\u589E\u91CF\n\t\tif ( (uint)index \u003E= (uint)_foods.Length || !_foods[index].Alive ) return;\n\n\t\tvar f = _foods[index];\n\t\tf.Alive = false;\n\t\t_foods[index] = f;\n\t\tAliveCount--;\n\t}\n\n\tpublic void ApplyRespawned( int index, FoodData food )\n\t{\n\t\tif ( _foods is null ) return;\n\t\tif ( (uint)index \u003E= (uint)_foods.Length ) return;\n\n\t\t_foods[index] = food;\n\t\tAliveCount\u002B\u002B;\n\t}\n}"},{"Ident":"slfy.circleroyale","Path":"UI/LobbyPanel.cs","FileName":"LobbyPanel.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\nusing Sandbox.UI;\n\n/// \u003Csummary\u003E\n/// \u6E38\u620F\u623F\u95F4/\u5927\u5385\uFF08M5 \u4E8C\u7EA7\u754C\u9762\uFF09\uFF1AHOST / VS BOTS \u70B9\u5F00\u540E\u8FDB\u5165\u2014\u2014host \u53EF\u8C03\u6BD4\u8D5B\u53C2\u6570\u3001\u770B\u73A9\u5BB6\u5217\u8868\u3001\n/// \u70B9 START \u5F00\u5C40\uFF1B\u5BA2\u6237\u7AEF JOIN \u8FDE\u4E0A\u540E\u8FDB\u540C\u4E00\u754C\u9762\uFF0C\u53C2\u6570\u4E0E\u73A9\u5BB6\u5217\u8868\u7531 host \u7ECF LobbyState RPC \u63A8\u9001\uFF0C\n/// \u663E\u793A\u0022\u7B49\u5F85\u623F\u4E3B\u5F00\u59CB\u0022\uFF0C\u623F\u4E3B\u5F00\u5C40\u540E\u7ECF MatchStarted RPC \u81EA\u52A8\u8FDB\u6E38\u620F\u3002\n/// \u4E3B\u83DC\u5355\u7684\u8BBE\u7F6E\u533A\u5DF2\u79FB\u5230\u8FD9\u91CC\uFF08\u623F\u95F4\u624D\u662F\u8BBE\u7F6E\u53C2\u6570\u7684\u5730\u65B9\uFF09\u3002\n/// \u7EAF C# Panel \u002B \u540C\u76EE\u5F55 LobbyPanel.cs.scss\u3002\n/// \u003C/summary\u003E\npublic sealed class LobbyPanel : PanelComponent\n{\n\tLabel _status;\n\tLabel[] _settings;       // 0=\u6A21\u5F0F 1=\u65F6\u957F 2=\u4EBA\u6570\uFF08host \u53EF\u70B9\u51FB\u5FAA\u73AF\uFF0Cclient \u53EA\u8BFB\u5C55\u793A\uFF09\n\tLabel _settingHint;      // \u0022CLICK A SETTING TO CYCLE IT\u0022\uFF08\u4EC5 host\uFF0Cv0.7.8.0\uFF09\n\tLabel _legendHead;       // \u9053\u5177\u56FE\u4F8B\u6807\u9898\uFF08v0.7.8.2\uFF09\n\tLabel[] _legendNames;    // \u56FE\u4F8B\u884C\uFF1A\u8272\u70B9\u002B\u9053\u5177\u540D\uFF08\u79CD\u7C7B\u8272\uFF09\n\tLabel[] _legendDescs;    //   \u4E00\u53E5\u8BDD\u8BF4\u660E\uFF08\u7070\u767D\uFF09\n\tLabel _legendNote;       // \u83B7\u53D6\u65B9\u5F0F\u63D0\u793A\n\tLabel[] _playerRows;     // \u73A9\u5BB6\u5217\u8868\uFF1A2 \u5217 \u00D7 12 \u884C\n\tLabel _botsNote;\n\tLabel _start;\n\tLabel _leave;\n\tLabel _join;             // \u5BA2\u6237\u7AEF\u0022JOIN GAME\u0022\u6309\u94AE\uFF1A\u5BF9\u5C40\u8FDB\u884C\u4E2D\u65F6\u663E\u793A\uFF0C\u70B9\u51FB\u540E 3-2-1 \u8FDB\u5165\uFF08v0.6.4.1\uFF09\n\tLabel _countdown;        // \u52A0\u5165\u5012\u8BA1\u65F6\uFF08\u5BA2\u6237\u7AEF\u6536\u5230 host \u5F00\u5C40\u6307\u4EE4\u540E 3-2-1\uFF0Cv0.6.4.0\uFF09\n\tbool _matchRunning;      // \u5BA2\u6237\u7AEF\u89C6\u89D2\uFF1Ahost \u7684\u5BF9\u5C40\u8FDB\u884C\u4E2D\uFF08JOIN GAME \u6309\u94AE\u4EAE\u8D77\uFF09\n\tbool _modeGlowActive;    // \u6A21\u5F0F\u884C\u547C\u5438\u8272\u6FC0\u6D3B\u4E2D\uFF08\u5207\u56DE\u5BA2\u6237\u7AEF\u8EAB\u4EFD\u65F6\u6062\u590D scss \u57FA\u8272\u4E00\u6B21\uFF09\n\n\tbool _built;\n\tbool _retryOpen;         // Panel \u672A\u5C31\u7EEA\u65F6\u7684\u5F00\u95E8\u91CD\u8BD5\uFF08EnsureWorld \u4F1A\u5148\u4E8E OnStart \u8C03\u7528 ShowClient/ShowHost\uFF09\n\tbool _isHost;\n\tbool _shown;\n\tbool _connectionLost;    // \u6389\u7EBF\u91CD\u8FDE\u4E2D\uFF1A\u72B6\u6001\u884C\u9501\u5B9A\u63D0\u793A\uFF08\u5426\u5219\u6BCF\u5E27 RefreshStatus \u4F1A\u8986\u76D6\u6389\uFF09\n\tTimeSince _sinceListRefresh;\n\n\t// \u5BA2\u6237\u7AEF\uFF1A\u623F\u4E3B\u63A8\u9001\u7684\u623F\u95F4\u4FE1\u606F\uFF08LobbyState RPC\uFF09\n\tbyte _clientMode;\n\tint _clientDuration;\n\tint _clientPlayers;\n\tList\u003Cstring\u003E _clientNames = new List\u003Cstring\u003E();\n\n\tconst int RowsPerCol = 12;\n\n\tprotected override void OnStart()\n\t{\n\t\tbase.OnStart();\n\t\tBuildUi();\n\t\tHide();\n\t}\n\n\tvoid BuildUi()\n\t{\n\t\tvar root = Panel;\n\t\tif ( root is null ) return;\n\t\tif ( _built \u0026\u0026 _status.IsValid() \u0026\u0026 root.ChildrenCount \u003E 0 ) return;\n\n\t\troot.DeleteChildren( true );\n\n\t\troot.Style.Position = PositionMode.Absolute;\n\t\troot.Style.Left = 0f;\n\t\troot.Style.Top = 0f;\n\t\troot.Style.Right = 0f;\n\t\troot.Style.Bottom = 0f;\n\t\troot.Style.BackgroundColor = new Color( 0.008f, 0.012f, 0.05f, 0.55f );   // \u534A\u900F\u660E\uFF1A\u7F51\u683C/\u80CC\u666F\u5BF9\u6218\u900F\u51FA\u6765\uFF08\u7528\u6237\u53CD\u9988\u0022\u9ED1\u5E55\u0022\uFF09\n\t\troot.Style.PointerEvents = PointerEvents.All;\n\n\t\tvar title = new Label() { Classes = \u0022lp-title\u0022 };\n\t\ttitle.Text = \u0022GAME LOBBY\u0022;\n\t\troot.AddChild( title );\n\n\t\t_status = new Label() { Classes = \u0022lp-status\u0022 };\n\t\troot.AddChild( _status );\n\n\t\tvar settingsHead = new Label() { Classes = \u0022lp-head\u0022 };\n\t\tsettingsHead.Text = \u0022MATCH SETTINGS\u0022;\n\t\troot.AddChild( settingsHead );\n\n\t\t_settings = new Label[3];\n\t\tstring[] ids = { \u0022set-mode\u0022, \u0022set-time\u0022, \u0022set-players\u0022 };\n\t\tfor ( int i = 0; i \u003C _settings.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar l = new Label() { Classes = $\u0022lp-setting {ids[i]}\u0022 };\n\t\t\tint index = i;\n\t\t\tl.AddEventListener( \u0022onclick\u0022, () =\u003E CycleSetting( index ) );\n\t\t\troot.AddChild( l );\n\t\t\t_settings[i] = l;\n\t\t}\n\n\t\t// \u8BBE\u7F6E\u53EF\u70B9\u63D0\u793A\uFF08v0.7.8.0\uFF09\uFF1A\u4EC5 host \u53EF\u89C1\u2014\u2014\u914D\u5408\u6A21\u5F0F\u884C\u547C\u5438\u91D1\u5149\uFF0C\u8BA9\u73A9\u5BB6\u77E5\u9053\u80FD\u6539\u6A21\u5F0F\n\t\t_settingHint = new Label() { Classes = \u0022lp-setting-hint\u0022 };\n\t\t_settingHint.Text = \u0022CLICK A SETTING TO CYCLE IT\u0022;\n\t\troot.AddChild( _settingHint );\n\n\t\t// \u9053\u5177\u56FE\u4F8B\uFF08v0.7.8.2\uFF09\uFF1A\u73A9\u5BB6\u5217\u8868\u5DE6\u4FA7\u7A7A\u5217\uFF0C\u8272\u70B9\u002B\u540D\u79F0\u002B\u4E00\u53E5\u8BDD\u8BF4\u660E\u2014\u2014\u5F00\u5C40\u524D\u5B66\u4E60\u9053\u5177\n\t\t_legendHead = new Label() { Classes = \u0022lp-legend-head\u0022 };\n\t\t_legendHead.Text = \u0022POWERUPS\u0022;\n\t\troot.AddChild( _legendHead );\n\n\t\tbyte[] legendKinds = { (byte)PowerUpManager.Kind.Speed, (byte)PowerUpManager.Kind.Magnet,\n\t\t\t(byte)PowerUpManager.Kind.Shield, (byte)PowerUpManager.Kind.Spike, (byte)PowerUpManager.Kind.Mass };\n\t\tstring[] legendDescs =\n\t\t{\n\t\t\t\u00228s move boost\u0022,\n\t\t\t\u00228s double food range\u0022,\n\t\t\t\u00225s can\u0027t be eaten (not spikes)\u0022,\n\t\t\t\u0022burst bigger balls = 3s invincible\u0022,\n\t\t\t\u0022instant \u002B300 mass\u0022,\n\t\t};\n\n\t\t_legendNames = new Label[legendKinds.Length];\n\t\t_legendDescs = new Label[legendKinds.Length];\n\t\tfor ( int i = 0; i \u003C legendKinds.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar kind = legendKinds[i];\n\t\t\tvar rowTop = 690f \u002B i * 34f;   // \u5DE6\u4E0B\u89D2\u56FE\u4F8B\uFF08v0.7.8.4 \u7528\u6237\u5B9A\u7A3F\uFF1A\u7EA2\u7BAD\u5934\u6307\u5411\u7684\u4F4D\u7F6E\uFF09\n\n\t\t\tvar name = new Label() { Classes = \u0022lp-legend-name\u0022 };\n\t\t\tname.Text = $\u0022\u25A0 {PowerUpManager.NameOf( kind )}\u0022;\n\t\t\tname.Style.FontColor = PowerUpManager.ColorOf( kind );\n\t\t\tname.Style.Top = rowTop;\n\t\t\troot.AddChild( name );\n\t\t\t_legendNames[i] = name;\n\n\t\t\tvar desc = new Label() { Classes = \u0022lp-legend-desc\u0022 };\n\t\t\tdesc.Text = legendDescs[i];\n\t\t\tdesc.Style.Top = rowTop \u002B 2f;\n\t\t\troot.AddChild( desc );\n\t\t\t_legendDescs[i] = desc;\n\t\t}\n\n\t\t_legendNote = new Label() { Classes = \u0022lp-legend-note\u0022 };\n\t\t_legendNote.Text = \u0022PICK UP OR SPIT AT IT \u2014 USE WITH Q / E\u0022;\n\t\troot.AddChild( _legendNote );\n\n\t\tvar playersHead = new Label() { Classes = \u0022lp-head players\u0022 };\n\t\tplayersHead.Text = \u0022PLAYERS\u0022;\n\t\troot.AddChild( playersHead );\n\n\t\t// \u73A9\u5BB6\u5217\u8868 2 \u5217 \u00D7 12 \u884C\uFF08\u5DE6\u5217 0..11\uFF0C\u53F3\u5217 12..23\uFF09\n\t\t_playerRows = new Label[RowsPerCol * 2];\n\t\tfor ( int i = 0; i \u003C _playerRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar row = new Label() { Classes = \u0022lp-player\u0022 };\n\t\t\trow.Style.Position = PositionMode.Absolute;\n\t\t\trow.Style.Left = i \u003C RowsPerCol ? 560f : 1010f;\n\t\t\trow.Style.Width = 440f;\n\t\t\trow.Style.Top = 384f \u002B ( i % RowsPerCol ) * 30f;\n\t\t\troot.AddChild( row );\n\t\t\t_playerRows[i] = row;\n\t\t}\n\n\t\t_botsNote = new Label() { Classes = \u0022lp-note\u0022 };\n\t\t_botsNote.Text = \u0022BOTS FILL REMAINING SLOTS AT START\u0022;\n\t\troot.AddChild( _botsNote );\n\n\t\t// \u52A0\u5165\u5012\u8BA1\u65F6\uFF08\u5BA2\u6237\u7AEF\uFF09\uFF1A\u6536\u5230 host \u5F00\u5C40\u6307\u4EE4\u540E\u663E\u793A 3-2-1\n\t\t_countdown = new Label() { Classes = \u0022lp-countdown\u0022 };\n\t\t_countdown.Style.Display = DisplayMode.None;\n\t\troot.AddChild( _countdown );\n\n\t\t// JOIN GAME\uFF08\u5BA2\u6237\u7AEF\uFF09\uFF1A\u5BF9\u5C40\u8FDB\u884C\u4E2D\u65F6\u70B9\u6B64\u8FDB\u5165\uFF083-2-1 \u5012\u8BA1\u65F6\uFF09\n\t\t_join = new Label() { Classes = \u0022lp-join\u0022 };\n\t\t_join.Text = \u0022JOIN GAME\u0022;\n\t\t_join.AddEventListener( \u0022onclick\u0022, () =\u003E OnJoinClicked() );\n\t\troot.AddChild( _join );\n\n\t\t_start = new Label() { Classes = \u0022lp-start\u0022 };\n\t\t_start.Text = \u0022START GAME\u0022;\n\t\t_start.AddEventListener( \u0022onclick\u0022, () =\u003E OnStartClicked() );\n\t\troot.AddChild( _start );\n\n\t\t_leave = new Label() { Classes = \u0022lp-leave\u0022 };\n\t\t_leave.Text = \u0022LEAVE\u0022;\n\t\t_leave.AddEventListener( \u0022onclick\u0022, () =\u003E OnLeaveClicked() );\n\t\troot.AddChild( _leave );\n\n\t\t_built = true;\n\t}\n\n\t/// \u003Csummary\u003E host \u8FDB\u5165\u623F\u95F4\uFF08HOST / VS BOTS \u5F00\u623F\u540E\u8C03\u7528\uFF09 \u003C/summary\u003E\n\tpublic void ShowHost()\n\t{\n\t\t_isHost = true;\n\t\tOpen();\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u8FDE\u5165\u540E\u8FDB\u5165\u623F\u95F4\uFF08\u7B49\u5F85\u623F\u4E3B\u5F00\u5C40\uFF1B\u5217\u8868\u7B49 LobbyState \u63A8\u9001\uFF09 \u003C/summary\u003E\n\tpublic void ShowClient()\n\t{\n\t\t_isHost = false;\n\t\tOpen();\n\t}\n\n\tvoid Open()\n\t{\n\t\tBuildUi();\n\t\tif ( !_built )\n\t\t{\n\t\t\t_retryOpen = true;   // Panel \u672A\u5C31\u7EEA\uFF08EnsureWorld \u7ACB\u5373\u8C03 ShowClient/ShowHost \u65F6\uFF09\uFF0COnUpdate \u91CD\u8BD5\n\t\t\treturn;\n\t\t}\n\n\t\t_retryOpen = false;\n\t\tPanel.Style.Display = DisplayMode.Flex;\n\t\tif ( _shown )\n\t\t{\n\t\t\t// \u5DF2\u663E\u793A\uFF1A\u4E0D\u91CD\u7F6E\u72B6\u6001\uFF08ReconcileUi \u6BCF\u5E27\u8C03\uFF0CJOIN \u6309\u94AE/\u5012\u8BA1\u65F6\u4E0D\u80FD\u88AB\u6E05\u6389\uFF09\u3002\n\t\t\t// \u4F46\u72B6\u6001\u884C\u8981\u6062\u590D\u2014\u2014\u6389\u7EBF\u91CD\u8FDE\u6210\u529F\u56DE\u5230\u623F\u95F4\u65F6\uFF0C\u0022CONNECTION LOST\u0022 \u4E0D\u80FD\u4E00\u76F4\u6302\u7740\n\t\t\tif ( !_isHost ) RefreshStatus();\n\t\t\treturn;\n\t\t}\n\t\t_shown = true;\n\t\t_matchRunning = false;   // \u6BCF\u6B21\u8FDB\u623F\u95F4\u91CD\u7F6E\uFF08MatchStarted \u5230\u8FBE\u540E\u518D\u4EAE JOIN \u6309\u94AE\uFF09\n\t\tHideJoinCountdown();\n\t\t_sinceListRefresh = 1f;   // \u9996\u5E27\u7ACB\u5373\u5237\n\t\tRefreshAll();\n\t}\n\n\t/// \u003Csummary\u003E \u6536\u8D77\uFF08\u5F00\u5C40 / \u56DE\u83DC\u5355\uFF09 \u003C/summary\u003E\n\tpublic void Hide()\n\t{\n\t\t_shown = false;\n\t\t_retryOpen = false;\n\t\tif ( Panel.IsValid() ) Panel.Style.Display = DisplayMode.None;\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u5E94\u7528\u623F\u4E3B\u63A8\u9001\u7684\u623F\u95F4\u4FE1\u606F\uFF08CircleroyaleGame.OnLobbyStateRemote \u8F6C\u53D1\uFF09 \u003C/summary\u003E\n\tpublic void UpdateFromHost( byte mode, int duration, int players, string[] names )\n\t{\n\t\t_connectionLost = false;   // host \u6709\u6D88\u606F = \u8FDE\u63A5\u6D3B\u7740\uFF0C\u89E3\u9664\u6389\u7EBF\u63D0\u793A\n\t\t_clientMode = mode;\n\t\t_clientDuration = duration;\n\t\t_clientPlayers = players;\n\t\t_clientNames = names is not null ? new List\u003Cstring\u003E( names ) : new List\u003Cstring\u003E();\n\n\t\tif ( _shown \u0026\u0026 !_isHost )\n\t\t{\n\t\t\tRefreshSettings();\n\t\t\tRefreshPlayers();\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u663E\u793A\u52A0\u5165\u5012\u8BA1\u65F6\uFF08CircleroyaleGame \u6BCF\u5E27\u8C03\u7528\uFF09 \u003C/summary\u003E\n\tpublic void ShowJoinCountdown( float secondsLeft )\n\t{\n\t\tif ( _countdown is null || !_countdown.IsValid() ) return;\n\n\t\t_countdown.Style.Display = DisplayMode.Flex;\n\t\t_countdown.Text = $\u0022JOINING MATCH IN {MathF.Ceiling( MathF.Max( 0f, secondsLeft ) ):0}\u0022;\n\n\t\t// \u5012\u8BA1\u65F6\u671F\u95F4\u6536\u8D77 JOIN \u6309\u94AE\n\t\tif ( _join.IsValid() ) _join.Style.Display = DisplayMode.None;\n\t}\n\n\t/// \u003Csummary\u003E \u6536\u8D77\u52A0\u5165\u5012\u8BA1\u65F6 \u003C/summary\u003E\n\tpublic void HideJoinCountdown()\n\t{\n\t\tif ( _countdown.IsValid() ) _countdown.Style.Display = DisplayMode.None;\n\t}\n\n\t/// \u003Csummary\u003E \u5BA2\u6237\u7AEF\u89C6\u89D2\uFF1Ahost \u7684\u5BF9\u5C40\u8FDB\u884C\u4E2D\uFF08\u623F\u95F4\u9875\u4EAE\u8D77 JOIN GAME \u6309\u94AE\uFF09 \u003C/summary\u003E\n\tpublic void SetMatchRunning()\n\t{\n\t\t_matchRunning = true;\n\t\tRefreshButtons();\n\n\t\tSetText( _status, \u0022MATCH IN PROGRESS \u2014 JOIN NOW\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E \u6587\u672C\u9632\u6296\uFF08v0.7.1.2\uFF09\uFF1A\u503C\u6CA1\u53D8\u5C31\u4E0D\u8D4B\u2014\u2014Label.Text \u6BCF\u5E27\u91CD\u8BBE\uFF08\u54EA\u6015\u540C\u503C\uFF09\u4F1A\u89E6\u53D1\u5E03\u5C40\n\t/// \u91CD\u7B97\uFF1BReconcileUi \u6BCF\u5E27\u8C03 ShowClient\u2192RefreshStatus \u628A\u72B6\u6001\u884C\u53CD\u590D\u91CD\u5237 = UI \u4E00\u95EA\u4E00\u95EA\uFF08\u5B9E\u6D4B\uFF09 \u003C/summary\u003E\n\tstatic void SetText( Label l, string text )\n\t{\n\t\tif ( !l.IsValid() || l.Text == text ) return;\n\t\tl.Text = text;\n\t}\n\n\t/// \u003Csummary\u003E \u6389\u7EBF\u81EA\u52A8\u91CD\u8FDE\u4E2D\uFF08CircleroyaleGame.OnDisconnectRecoveryStarted\uFF09\uFF1A\u72B6\u6001\u884C\u63D0\u793A \u002B \u6536\u6389 JOIN \u6309\u94AE \u003C/summary\u003E\n\tpublic void SetConnectionLost()\n\t{\n\t\t_connectionLost = true;\n\t\tSetText( _status, \u0022CONNECTION LOST \u2014 RECONNECTING ...\u0022 );\n\t\tif ( _join.IsValid() ) _join.Style.Display = DisplayMode.None;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\tBuildUi();\n\n\t\t// EnsureWorld \u5148\u4E8E OnStart \u8C03\u8FC7 ShowClient/ShowHost \u4E14 Panel \u672A\u5C31\u7EEA \u2192 \u6B64\u523B\u8865\u5F00\n\t\tif ( _retryOpen \u0026\u0026 !_shown )\n\t\t{\n\t\t\tOpen();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !_shown ) return;\n\n\t\tif ( Panel.IsValid() ) Panel.Style.Display = DisplayMode.Flex;\n\t\tMouse.Visibility = MouseVisibility.Visible;   // \u6309\u94AE/\u8BBE\u7F6E\u9879\u8981\u80FD\u70B9\n\n\t\t// host\uFF1A\u73A9\u5BB6\u8FDB\u51FA\u3001\u8BBE\u7F6E\u53D8\u5316\u672C\u5730\u5C31\u80FD\u770B\u5230\uFF0C0.5s \u8F6E\u5237\uFF1Bclient \u5217\u8868\u9760 LobbyState \u63A8\u9001\n\t\tif ( _isHost \u0026\u0026 _sinceListRefresh \u003E 0.5f )\n\t\t{\n\t\t\t_sinceListRefresh = 0;\n\t\t\tRefreshPlayers();\n\t\t\tRefreshStatus();\n\t\t}\n\n\t\t// \u6A21\u5F0F\u884C\u547C\u5438\u91D1\u5149\uFF08v0.7.8.0\uFF0C\u4EC5 host\uFF09\uFF1A\u8BBE\u7F6E\u884C\u662F\u53EF\u70B9\u7684\uFF0C\u7528\u5149\u6548\u5F15\u5BFC\u73A9\u5BB6\u53BB\u6539\u6A21\u5F0F\u3002\n\t\t// \u989C\u8272\u52A8\u753B\u6BCF\u5E27\u5728\u53D8\uFF08Sin \u8FDE\u7EED\uFF09\uFF0C\u4E0D\u78B0 Text \u4E0D\u89E6\u53D1\u5E03\u5C40\u91CD\u7B97\uFF1B\u5BA2\u6237\u7AEF\u6062\u590D\u9759\u6001\u57FA\u8272\n\t\tif ( _settings is not null \u0026\u0026 _settings[0].IsValid() )\n\t\t{\n\t\t\tif ( _isHost )\n\t\t\t{\n\t\t\t\tvar glow = 0.72f \u002B 0.28f * MathF.Sin( Time.Now * 3.2f );\n\t\t\t\t_settings[0].Style.FontColor = new Color( 1f * glow, 0.76f * glow, 0.30f * glow );\n\t\t\t\t_modeGlowActive = true;\n\t\t\t}\n\t\t\telse if ( _modeGlowActive )\n\t\t\t{\n\t\t\t\t_settings[0].Style.FontColor = new Color( 0.604f, 0.847f, 1f );   // scss .lp-setting \u57FA\u8272\n\t\t\t\t_modeGlowActive = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid RefreshAll()\n\t{\n\t\tRefreshSettings();\n\t\tRefreshPlayers();\n\t\tRefreshStatus();\n\t\tRefreshButtons();\n\t}\n\n\tvoid RefreshStatus()\n\t{\n\t\tif ( _isHost )\n\t\t{\n\t\t\tvar names = CircleroyaleGame.Current?.LobbyPlayerNames();\n\t\t\tint n = names?.Count ?? 1;\n\t\t\tSetText( _status, $\u0022ROOM OPEN \u2014 {n} PLAYER{( n \u003E 1 ? \u0022S\u0022 : \u0022\u0022 )} IN LOBBY\u0022 );\n\t\t}\n\t\telse if ( _connectionLost )\n\t\t{\n\t\t\tSetText( _status, \u0022CONNECTION LOST \u2014 RECONNECTING ...\u0022 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSetText( _status, _matchRunning ? \u0022MATCH IN PROGRESS \u2014 JOIN NOW\u0022 : \u0022WAITING FOR HOST TO START ...\u0022 );\n\t\t}\n\t}\n\n\tvoid RefreshSettings()\n\t{\n\t\tif ( _settings is null || _settings[0] is null || !_settings[0].IsValid() ) return;\n\n\t\tbyte mode;\n\t\tfloat duration;\n\t\tint players;\n\n\t\tif ( _isHost )\n\t\t{\n\t\t\tmode = (byte)MatchState.PendingMode;\n\t\t\tduration = MatchState.PendingDuration;\n\t\t\tplayers = MatchState.PendingPlayerTarget;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmode = _clientMode;\n\t\t\tduration = _clientDuration;\n\t\t\tplayers = _clientPlayers;\n\t\t}\n\n\t\tSetText( _settings[0], (MatchState.Mode)mode switch\n\t\t{\n\t\t\tMatchState.Mode.Ffa =\u003E \u0022MODE \u2014 FREE FOR ALL\u0022,\n\t\t\tMatchState.Mode.Team2 =\u003E \u0022MODE \u2014 TEAM (2 / TEAM)\u0022,\n\t\t\tMatchState.Mode.Team3 =\u003E \u0022MODE \u2014 TEAM (3 / TEAM)\u0022,\n\t\t\t_ =\u003E \u0022MODE \u2014 TEAM (4 / TEAM)\u0022,\n\t\t} );\n\n\t\tSetText( _settings[1], duration \u003C 60f\n\t\t\t? $\u0022TIME \u2014 {duration:0} SEC\u0022\n\t\t\t: $\u0022TIME \u2014 {duration / 60f:0} MIN\u0022 );\n\n\t\tSetText( _settings[2], $\u0022PLAYERS \u2014 {players}\u0022 );\n\t}\n\n\tvoid RefreshPlayers()\n\t{\n\t\tif ( _playerRows is null || _playerRows[0] is null || !_playerRows[0].IsValid() ) return;\n\n\t\tList\u003Cstring\u003E names;\n\t\tif ( _isHost )\n\t\t{\n\t\t\tnames = CircleroyaleGame.Current?.LobbyPlayerNames() ?? new List\u003Cstring\u003E();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnames = _clientNames;\n\t\t}\n\n\t\tif ( names.Count == 0 ) names = new List\u003Cstring\u003E { \u0022...\u0022 };\n\n\t\tfor ( int i = 0; i \u003C _playerRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar row = _playerRows[i];\n\t\t\tif ( !row.IsValid() ) continue;\n\n\t\t\tif ( i \u003E= names.Count )\n\t\t\t{\n\t\t\t\tSetText( row, \u0022\u0022 );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar tag = i == 0 ? \u0022  (HOST)\u0022 : \u0022\u0022;\n\t\t\tSetText( row, $\u0022{i \u002B 1}.  {names[i]}{tag}\u0022 );\n\t\t\trow.Style.FontColor = i == 0 ? new Color( 0.21f, 0.94f, 1f ) : new Color( 1f, 1f, 1f, 0.75f );\n\t\t}\n\t}\n\n\tvoid RefreshButtons()\n\t{\n\t\t// host \u6709 START\uFF1B\u5BA2\u6237\u7AEF\u6709 JOIN GAME\uFF08\u5BF9\u5C40\u8FDB\u884C\u4E2D\u624D\u4EAE\uFF09\u3002LEAVE \u4E24\u8FB9\u90FD\u6709\n\t\tif ( _start.IsValid() )\n\t\t\t_start.Style.Display = _isHost ? DisplayMode.Flex : DisplayMode.None;\n\n\t\tif ( _join.IsValid() )\n\t\t\t_join.Style.Display = !_isHost \u0026\u0026 _matchRunning ? DisplayMode.Flex : DisplayMode.None;\n\n\t\t// \u8BBE\u7F6E\u53EF\u70B9\u63D0\u793A\uFF08v0.7.8.0\uFF09\uFF1A\u53EA\u6709 host \u80FD\u6539\u8BBE\u7F6E\uFF0C\u63D0\u793A\u4E5F\u53EA\u7ED9 host\n\t\tif ( _settingHint.IsValid() )\n\t\t\t_settingHint.Style.Display = _isHost ? DisplayMode.Flex : DisplayMode.None;\n\t}\n\n\t/// \u003Csummary\u003E JOIN GAME \u70B9\u51FB\uFF08\u5B9E\u4F8B\u65B9\u6CD5\uFF1A\u70ED\u91CD\u8F7D\u53EF\u91CD\u6620\u5C04\uFF09 \u003C/summary\u003E\n\tvoid OnJoinClicked() =\u003E CircleroyaleGame.Current?.ClientJoinMatch();\n\n\t/// \u003Csummary\u003E START \u70B9\u51FB \u003C/summary\u003E\n\tvoid OnStartClicked() =\u003E CircleroyaleGame.Current?.StartMatchFromLobby();\n\n\t/// \u003Csummary\u003E LEAVE \u70B9\u51FB \u003C/summary\u003E\n\tvoid OnLeaveClicked() =\u003E CircleroyaleGame.Current?.ResetToMenu();\n\n\t// ---- host \u8C03\u53C2\uFF08\u4E0E\u4E3B\u83DC\u5355\u65F6\u4EE3\u7684\u5FAA\u73AF\u903B\u8F91\u4E00\u81F4\uFF09----\n\n\tvoid CycleSetting( int index )\n\t{\n\t\tif ( !_isHost ) return;\n\n\t\tswitch ( index )\n\t\t{\n\t\t\tcase 0:\n\t\t\t\t// \u6A21\u5F0F 4 \u6863\u5FAA\u73AF\uFF08v0.6.5.0\uFF09\uFF1A\u666E\u901A\u8D5B \u2192 2 \u4EBA\u961F \u2192 3 \u4EBA\u961F \u2192 4 \u4EBA\u961F\n\t\t\t\tMatchState.PendingMode = (MatchState.Mode) ( ( (int)MatchState.PendingMode \u002B 1 ) % 4 );\n\t\t\t\tbreak;\n\n\t\t\tcase 1:\n\t\t\t\tMatchState.PendingDuration = NextChoice( GameConfig.MatchDurationChoices, MatchState.PendingDuration );\n\t\t\t\tbreak;\n\n\t\t\tcase 2:\n\t\t\t\tMatchState.PendingPlayerTarget = NextIntChoice( GameConfig.PlayerTargetChoices, MatchState.PendingPlayerTarget );\n\t\t\t\tbreak;\n\t\t}\n\n\t\tRefreshSettings();\n\t\tCircleroyaleGame.Current?.MarkLobbyDirty();   // \u8BA9\u7B49\u5F85\u4E2D\u7684\u5BA2\u6237\u7AEF\u770B\u5230\u65B0\u53C2\u6570\n\t}\n\n\tstatic float NextChoice( float[] choices, float current )\n\t{\n\t\tfor ( int i = 0; i \u003C choices.Length; i\u002B\u002B )\n\t\t{\n\t\t\tif ( MathF.Abs( choices[i] - current ) \u003C 0.5f )\n\t\t\t\treturn choices[( i \u002B 1 ) % choices.Length];\n\t\t}\n\t\treturn choices[0];\n\t}\n\n\tstatic int NextIntChoice( int[] choices, int current )\n\t{\n\t\tfor ( int i = 0; i \u003C choices.Length; i\u002B\u002B )\n\t\t{\n\t\t\tif ( choices[i] == current )\n\t\t\t\treturn choices[( i \u002B 1 ) % choices.Length];\n\t\t}\n\t\treturn choices[0];\n\t}\n}\n"},{"Ident":"slfy.circleroyale","Path":"Game/GameMusic.cs","FileName":"GameMusic.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// \u80CC\u666F\u97F3\u4E50\uFF08M4\uFF09\uFF1A\u4E3B\u83DC\u5355 = chiptune1\uFF0C\u6218\u6597 = battle.music\u3002\u8D70 .sound SoundEvent\uFF08UI \u6807\u5FD7 = 2D \u5E73\u9762\u58F0\uFF0C\n/// \u4E0E 6 \u4E2A\u97F3\u6548\u540C\u4E00\u6761\u5DF2\u9A8C\u8BC1\u94FE\u8DEF\uFF1BResourceLibrary.Get\u0026lt;SoundFile\u0026gt; \u76F4\u63A5\u53D6\u5BFC\u5165 mp3 \u53D6\u4E0D\u5230\uFF0C\u5B9E\u6D4B\uFF09\u3002\n/// **\u5FAA\u73AF = \u64AD\u5B8C\u81EA\u52A8\u91CD\u64AD**\uFF08SoundHandle \u6CA1\u6709\u5FAA\u73AF\u6807\u5FD7\uFF0CTick \u91CC\u68C0\u6D4B\u66F2\u7EC8\u91CD\u5F00\uFF09\u3002\u5207\u66F2\uFF1A\u65E7\u66F2 1s \u6DE1\u51FA\u3001\u65B0\u66F2 1s \u6DE1\u5165\u3002\n/// \u003C/summary\u003E\npublic static class GameMusic\n{\n\tconst string MenuTrack = \u0022sounds/music/chiptune1.sound\u0022;\n\tconst string BattleTrack = \u0022sounds/music/battle.music.sound\u0022;\n\n\tstatic SoundHandle _handle;\n\tstatic string _track;\n\tstatic bool _wasPlaying;   // \u4E0A\u4E00\u5E27 handle \u662F\u5426\u6709\u6548\u4E14\u5728\u64AD\uFF08\u66F2\u7EC8\u91CD\u64AD\u7684\u5224\u636E\uFF0C\u89C1 Tick\uFF09\n\tstatic Sandbox.Audio.Mixer _musicMixer;   // Music \u6DF7\u97F3\u5668\u7F13\u5B58\uFF08\u627E\u4E0D\u5230\u5C31\u7559\u5728\u9ED8\u8BA4 Game\uFF0C\u4E0D\u5F71\u54CD\u51FA\u58F0\uFF09\n\n\t/// \u003Csummary\u003E \u97F3\u4E50\u97F3\u91CF 0-1\uFF08\u63A7\u5236\u53F0\u5B9E\u65F6\u53EF\u8C03\uFF09\u3002\u8D70 Music \u6DF7\u97F3\u5668\uFF0C\u522B\u548C UI \u97F3\u6548\u4E00\u9505\u7096 \u003C/summary\u003E\n\t[ConVar( \u0022cr_music_volume\u0022, Help = \u0022Music volume 0..1 (default 0.22)\u0022 )]\n\tpublic static float Volume { get; set; } = 0.22f;\n\n\t/// \u003Csummary\u003E \u5207\u4E3B\u83DC\u5355\u66F2\uFF08\u5DF2\u5728\u653E\u5C31\u65E0\u4E8B\u53D1\u751F\uFF09 \u003C/summary\u003E\n\tpublic static void PlayMenu() =\u003E Play( MenuTrack );\n\n\t/// \u003Csummary\u003E \u5207\u6218\u6597\u66F2\uFF08\u5F00\u5C40\u65F6\u8C03\uFF09 \u003C/summary\u003E\n\tpublic static void PlayBattle() =\u003E Play( BattleTrack );\n\n\t/// \u003Csummary\u003E \u6BCF\u5E27\u8C03\uFF08CircleroyaleGame.Tick\uFF0C\u83DC\u5355\u9636\u6BB5\u4E5F\u8981\u8DD1\uFF09\uFF1A\u97F3\u91CF\u8DDF\u968F convar \u002B \u66F2\u7EC8\u81EA\u52A8\u91CD\u64AD \u003C/summary\u003E\n\tpublic static void Tick()\n\t{\n\t\tif ( _track is null ) return;\n\n\t\t// \u66F2\u7EC8\u91CD\u64AD\uFF1A\u5F15\u64CE\u5728\u58F0\u97F3\u64AD\u5B8C\u540E\u4F1A Dispose \u6389 handle\uFF08SoundHandle.PreTick: Finished\u2192Dispose\uFF09\uFF0C\n\t\t// \u6240\u4EE5 IsValid \u4F1A\u53D8 false\u2014\u2014**\u4E0D\u80FD**\u7528 \u0060if (!_handle.IsValid()) return;\u0060 \u65E9\u9000\uFF0C\u5426\u5219\u6C38\u8FDC\u5230\u4E0D\u4E86\u91CD\u64AD\u3002\n\t\t// \u53EA\u5728\u0022\u4E0A\u4E00\u5E27\u8FD8\u5728\u64AD\u3001\u73B0\u5728\u5931\u6548/Finished/IsStopped\u0022\u65F6\u91CD\u5F00\uFF0C\u907F\u514D\u8D44\u6E90\u7F3A\u5931\u65F6\u6BCF\u5E27\u7A7A\u8F6C\u91CD\u8BD5\u3002\n\t\tif ( _wasPlaying \u0026\u0026 ( !_handle.IsValid() || _handle.Finished || _handle.IsStopped ) )\n\t\t{\n\t\t\tvar path = _track;\n\t\t\t_handle = default;\n\t\t\t_track = null;\n\t\t\t_wasPlaying = false;\n\t\t\tPlay( path );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !_handle.IsValid() ) return;\n\n\t\t_handle.Volume = Volume;\n\t\t_wasPlaying = _handle.IsPlaying;\n\t}\n\n\tstatic void Play( string path )\n\t{\n\t\tif ( _track == path \u0026\u0026 _handle.IsValid() \u0026\u0026 _handle.IsPlaying ) return;   // \u5DF2\u5728\u653E\u8FD9\u9996\n\n\t\ttry\n\t\t{\n\t\t\tif ( _handle.IsValid() ) _handle.Stop( 1f );   // \u65E7\u66F2\u6DE1\u51FA\u8BA9\u4F4D\n\n\t\t\t_handle = Sound.Play( path );   // \u66F2\u5B50 .sound \u5E26 UI \u6807\u5FD7 = 2D \u5E73\u9762\u58F0\n\t\t\t_track = path;\n\n\t\t\tif ( !_handle.IsValid() )\n\t\t\t{\n\t\t\t\tLog.Warning( $\u0022[music] Sound.Play returned invalid handle: {path} (asset not imported?)\u0022 );\n\t\t\t\t_track = null;   // \u522B\u8BA9 Tick \u6BCF\u5E27\u91CD\u8BD5\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t_wasPlaying = true;\n\n\t\t\t// \u8DEF\u7531\u5230\u5185\u7F6E Music \u6DF7\u97F3\u5668\uFF08Master\u2192Music/Game/UI/Voice\uFF0C\u5F15\u64CE ResetToDefault \u5EFA\uFF09\n\t\t\t_musicMixer ??= Sandbox.Audio.Mixer.FindMixerByName( \u0022Music\u0022 );\n\t\t\tif ( _musicMixer is not null )\n\t\t\t{\n\t\t\t\t// \u7528\u6237\u8981\u6C42\uFF1AMusic \u6DF7\u97F3\u5668\u97F3\u91CF\u51CF\u534A\uFF08\u5F15\u64CE\u9ED8\u8BA4 1.0 \u2192 0.5\uFF1B\u7EDD\u5BF9\u503C\u8D4B\u503C\uFF0C\u70ED\u91CD\u8F7D\u4E0D\u53E0\u52A0\uFF09\n\t\t\t\t_musicMixer.Volume = 0.5f;\n\t\t\t\t_handle.TargetMixer = _musicMixer;\n\t\t\t}\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\u0022[music] play \u0027{path}\u0027 failed: {e.Message}\u0022 );\n\t\t\t_track = null;\n\t\t}\n\t}\n}\n"},{"Ident":"slfy.circleroyale","Path":"Game/PowerUpWire.cs","FileName":"PowerUpWire.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"/// \u003Csummary\u003E\n/// \u9053\u5177\u5168\u91CF\u540C\u6B65\u5355\u5143\uFF08v0.7.3.0\uFF09\uFF1A\u69FD\u4F4D\u5E8F\u53F7 = \u6570\u7EC4\u4E0B\u6807\uFF0CKind=None(255) \u8868\u793A\u7A7A\u69FD\u3002\n/// \u9876\u5C42 struct\u2014\u2014RPC \u53C2\u6570\u5E8F\u5217\u5316\u5BF9\u9876\u5C42\u7C7B\u578B\u6700\u7A33\uFF08FoodData \u540C\u6B3E\u5148\u4F8B\uFF09\u3002\n/// \u003C/summary\u003E\npublic struct PowerUpWire\n{\n\tpublic byte Kind;\n\tpublic float X;\n\tpublic float Y;\n}"},{"Ident":"slfy.circleroyale","Path":"UI/GameHud.cs","FileName":"GameHud.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":368627,"Code":"using System;\nusing System.Collections.Generic;\nusing Sandbox.UI;\n\n/// \u003Csummary\u003E\n/// \u6E38\u620F HUD\uFF08\u7EAF C# Panel\uFF0C\u7F16\u8F91\u5668\u70ED\u91CD\u8F7D\u53CB\u597D\uFF09\uFF1A\u8D28\u91CF\u5206\u6570\u3001\u6392\u884C\u699C\u3001\u6B7B\u4EA1\u91CD\u751F\u3001\u64CD\u4F5C\u63D0\u793A\u3002\n/// \u6570\u636E\u6E90 CircleroyaleGame.Current\uFF1B\u6837\u5F0F\u5728\u540C\u76EE\u5F55 GameHud.cs.scss\uFF08\u6309\u0022\u7C7B\u540D.cs.scss\u0022\u81EA\u52A8\u52A0\u8F7D\uFF09\u3002\n/// \u6CE8\u610F\uFF1A\u9762\u677F\u663E\u9690\u7528 Style.Display\uFF08Panel \u65E0 Enabled \u5C5E\u6027\uFF09\uFF0C\u6839\u9762\u677F pointer-events \u4FDD\u6301 none\n/// \uFF08\u91CD\u751F\u8D70\u952E\u76D8 Space\uFF0C\u4E0D\u9700\u8981\u70B9\u51FB\uFF09\u3002\n/// \u003C/summary\u003E\npublic sealed class GameHud : PanelComponent\n{\n\tLabel _mass;\n\tLabel _hint;\n\tLabel _buff;                            // \u672C\u5730\u7403\u9053\u5177 buff \u72B6\u6001\uFF08\u26A1/\uD83E\uDDF2/\uD83D\uDEE1 \u5269\u4F59\u79D2\uFF0Cv0.7.3.0\uFF09\n\tLabel _tips;                            // \u73A9\u6CD5\u63D0\u793A\u8F6E\u64AD\u6761\uFF08\u5DE6\u4E0B\u89D2\uFF0Cv0.7.5.0 \u82F1\u6587\uFF09\n\tLabel _slotQ;                           // \u80CC\u5305\u69FD\u663E\u793A\uFF08\u53F3\u4E0B\u89D2\uFF0Cv0.7.5.0 \u4E3B\u52A8\u4F7F\u7528\uFF09\uFF1A[Q] \u9053\u5177\u540D\n\tLabel _slotE;                           //   [E] \u9053\u5177\u540D\n\tbyte _slotQShown = 255;                 // \u4E0A\u6B21\u6E32\u67D3\u7684\u69FD\u5185\u5BB9\uFF08\u989C\u8272\u53EA\u5728\u79CD\u7C7B\u5207\u6362\u65F6\u8D4B\u503C\uFF09\n\tbyte _slotEShown = 255;\n\tLabel _version;\n\tLabel[] _rows;\n\tLabel _selfRow;\n\tLabel _deathTitle;\n\tLabel _deathSub;\n\tLabel _matchInfo;                       // \u9876\u90E8\u4E2D\u592E\uFF1A\u6A21\u5F0F \u002B \u5012\u8BA1\u65F6\uFF08M5\uFF09\n\tLabel[] _teamRows;                      // \u961F\u4F0D\u603B\u5206\u6761\uFF08\u56E2\u961F\u8D5B\uFF0CM5\uFF09\n\tLabel[] _mateRows;                      // \u672C\u961F\u6210\u5458\u79EF\u5206\uFF08\u6392\u884C\u699C\u4E0B\u65B9\uFF0C\u56E2\u961F\u8D5B\uFF0Cv0.6.5.0\uFF09\uFF1A0=\u961F\u5934 1..4=\u6210\u5458\n\tDictionary\u003CGuid, TagSlot\u003E _tagMap = new();\n\tDictionary\u003Cint, TagSlot\u003E _pieceTagMap = new();   // \u5206\u8EAB\u540D\u724C\uFF1AKey = \u5206\u8EAB Id\uFF08\u5F52\u5C5E\u540D\u5B57\u7ECF\u4E3B\u4EBA\u7403\u89E3\u6790\uFF09\n\tList\u003CTagSlot\u003E _tagPool;\n\treadonly List\u003CKillFeedEntry\u003E _killFeed = new();\n\treadonly List\u003CBanner\u003E _banners = new();          // \u4E2A\u4EBA\u9AD8\u5149\u6A2A\u5E45\uFF08\u4E2D\u4E0B\u827A\u672F\u5B57\uFF0Cv0.7.6.0\uFF09\n\tbool _built;\n\n\t/// \u003Csummary\u003E \u4E2A\u4EBA\u9AD8\u5149\u6A2A\u5E45\u6761\u76EE\uFF08\u51FB\u6740/\u6361\u9053\u5177/\u653E\u6280\u80FD\uFF09\uFF1A2.1s \u751F\u547D\u5468\u671F\uFF0C0.3s \u5F39\u5165 \u002B 1.2s \u505C\u7559 \u002B 0.6s \u6DE1\u51FA \u003C/summary\u003E\n\tsealed class Banner\n\t{\n\t\tpublic Label Label;\n\t\tpublic TimeSince Since;\n\t\tpublic float ShownBottom = float.MinValue;\n\t}\n\n\t/// \u003Csummary\u003E \u9053\u5177\u4F7F\u7528/\u6FC0\u6D3B\u7684\u827A\u672F\u5B57\u6587\u6848\uFF08\u6309\u952E\u91CA\u653E\u4E0E\u5582\u517B\u89E6\u53D1\u5171\u7528\uFF09 \u003C/summary\u003E\n\tpublic static string ActivationText( byte kind )\n\t{\n\t\tswitch ( (PowerUpManager.Kind)kind )\n\t\t{\n\t\t\tcase PowerUpManager.Kind.Speed: return \u0022SPEED BOOST!\u0022;\n\t\t\tcase PowerUpManager.Kind.Magnet: return \u0022MAGNET ON!\u0022;\n\t\t\tcase PowerUpManager.Kind.Shield: return \u0022SHIELD UP!\u0022;\n\t\t\tcase PowerUpManager.Kind.Spike: return \u0022SPIKE MINION DEPLOYED!\u0022;\n\t\t\tcase PowerUpManager.Kind.Mass: return \u0022\u002B300 MASS!\u0022;\n\t\t\tdefault: return \u0022\u0022;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u9053\u5177\u79CD\u7C7B\u8272\uFF08\u6A2A\u5E45\u914D\u8272\u5165\u53E3\uFF09 \u003C/summary\u003E\n\tpublic static Color KindColor( byte kind ) =\u003E PowerUpManager.ColorOf( kind );\n\n\t/// \u003Csummary\u003E \u51FB\u6740\u9AD8\u5149\u7684\u91D1\u8272\uFF08combo \u4E5F\u5728\u8FD9\u4E00\u65CF\u4E0A\u653E\u5927\uFF09 \u003C/summary\u003E\n\tstatic readonly Color KillGold = new( 1f, 0.81f, 0.36f );\n\n\t/// \u003Csummary\u003E\n\t/// \u51FB\u6740\u6A2A\u5E45\uFF08v0.7.7.0 \u8FDE\u64AD\u7248\uFF09\uFF1A\u7B2C 1 \u6740\u5E38\u89C4\u884C\uFF0C10 \u79D2\u7A97\u53E3\u5185\u8FDE\u6740\u5347\u7EA7\u4E3A\u8FDE\u6740\u5927\u5B57\n\t/// \uFF08DOUBLE KILL! \u2192 TRIPLE KILL! \u2192 RAMPAGE! \u2192 GODLIKE!\uFF0C\u5B57\u53F7\u66F4\u5927\u91D1\u8272\u66F4\u4EAE\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tpublic void AddKillBanner( int combo, string eatenName, float massGained )\n\t{\n\t\tstring text;\n\t\tstring classes;\n\n\t\tswitch ( combo )\n\t\t{\n\t\t\tcase 2: text = $\u0022DOUBLE KILL!  \u002B{massGained:0}\u0022; break;\n\t\t\tcase 3: text = $\u0022TRIPLE KILL!  \u002B{massGained:0}\u0022; break;\n\t\t\tcase 4: text = $\u0022RAMPAGE!  \u002B{massGained:0}\u0022; break;\n\t\t\tcase \u003E= 5: text = $\u0022GODLIKE!  \u002B{massGained:0}\u0022; break;\n\t\t\tdefault: text = $\u0022YOU ATE {eatenName}  \u002B{massGained:0}\u0022; break;\n\t\t}\n\n\t\tclasses = combo \u003E= 2 ? \u0022banner combo\u0022 : \u0022banner\u0022;\n\n\t\tvar l = new Label() { Classes = classes };\n\t\tl.Text = text;\n\t\tl.Style.FontColor = KillGold;\n\t\tPanel?.AddChild( l );\n\t\t_banners.Add( new Banner { Label = l, Since = 0 } );\n\t}\n\n\t// ---- \u73A9\u6CD5\u63D0\u793A\u8F6E\u64AD\uFF08v0.7.5.0 \u82F1\u6587\u6587\u6848\uFF09\uFF1A\u6BCF 8s \u6362\u4E00\u6761\uFF08\u542B 0.5s \u6E10\u5165\uFF09\uFF0C\u968F\u4E3B\u52A8\u4F7F\u7528/\u5582\u517B\u673A\u5236\u66F4\u65B0 ----\n\n\tstatic readonly string[] Tips =\n\t{\n\t\t\u0022SPACE / RMB: split ALL cells in half, toward your cursor\u0022,\n\t\t\u0022R / LMB: eject mass \u2014 feed big teammates or spikes\u0022,\n\t\t\u0022Pickups store into E \u2014 press E to use; Q recharges over time (small = faster)\u0022,\n\t\t\u0022Spit mass AT a pickup to feed it \u2014 trigger at 5% of your weight\u0022,\n\t\t\u0022Small balls feed cheap, big balls feed dear \u2014 steal half-fed pickups\u0022,\n\t\t\u0022Spike minion: ram it into a bigger ball \u2014 burst = 3s invincible\u0022,\n\t\t\u0022Spike minion reverts to a normal cell after 20s (mass refunded)\u0022,\n\t\t\u0022Shield blocks being eaten, not spikes \u2014 don\u0027t hug spikes with it\u0022,\n\t\t\u0022Magnet: 8s of double food pickup radius\u0022,\n\t\t\u0022Big balls must drive OVER pickups to grab them \u2014 smalls grab easy\u0022,\n\t\t\u0022Your split cells pick up powerups into the same Q / E bag\u0022,\n\t\t\u0022Off-screen teammates show an edge arrow \u2014 follow it to regroup\u0022,\n\t\t\u0022Main ball eaten? Biggest cell takes over \u2014 lose all cells and you\u0027re out\u0022,\n\t\t\u0022P: bail out of the match back to the lobby\u0022,\n\t\t\u0022Bigger = clumsier turning \u2014 plan your moves ahead\u0022,\n\t\t\u0022Team mode: teammates can\u0027t eat each other \u2014 stick together\u0022,\n\t\t\u0022Feed a purple spike with R mass \u2014 it grows, then bursts spikes\u0022,\n\t\t\u0022Can\u0027t eat them? You\u0027re solid \u2014 body-block and squeeze\u0022,\n\t\t\u0022Big balls slowly shrink \u2014 keep eating or fade away\u0022,\n\t};\n\n\tint _tipIndex = -1;           // \u9996\u6B21\u8F6E\u6362\u843D\u5230 0\n\tTimeSince _sinceTip = 999f;   // \u9996\u5E27\u7ACB\u5373\u6E10\u5165\u7B2C\u4E00\u6761\n\n\t/// \u003Csummary\u003E \u6587\u672C\u9632\u6296\uFF08v0.7.1.2\uFF09\uFF1A\u503C\u6CA1\u53D8\u5C31\u4E0D\u8D4B\u2014\u2014Label.Text \u91CD\u8BBE\u4F1A\u89E6\u53D1\u5E03\u5C40\u91CD\u7B97\uFF0C\n\t/// \u6BCF\u5E27\u91CD\u8BBE\uFF08\u54EA\u6015\u540C\u503C\uFF09\u4F1A\u8BA9 HUD \u95EA\uFF1B\u6392\u884C\u699C/\u8BA1\u65F6\u5668/\u540D\u724C/\u961F\u53CB\u533A\u5168\u662F\u9010\u5E27\u8DEF\u5F84 \u003C/summary\u003E\n\tstatic void SetText( Label l, string text )\n\t{\n\t\tif ( l is null || !l.IsValid() || l.Text == text ) return;\n\t\tl.Text = text;\n\t}\n\n\t/// \u003Csummary\u003E \u540D\u724C\u69FD\uFF1ALabel \u6309\u7403\u8EAB\u4EFD\uFF08Component.Id\uFF09\u7A33\u5B9A\u7ED1\u5B9A\uFF0C\u9632 bot \u6B7B\u4EA1/\u91CD\u751F\u65F6\u540D\u5B57\u6362\u4F4D\u8DF3\u95EA \u003C/summary\u003E\n\tsealed class TagSlot\n\t{\n\t\tpublic Label Label;\n\t\tpublic bool Used;\n\t\tpublic bool Shown;\n\t}\n\n\t/// \u003Csummary\u003E \u51FB\u6740\u64AD\u62A5\u6761\u76EE\uFF08M5\u0022\u8C01\u5403\u4E86\u8C01\u0022\uFF09\uFF1A4.5s \u540E\u79FB\u9664\uFF0C3.5s \u8D77\u6DE1\u51FA \u003C/summary\u003E\n\tsealed class KillFeedEntry\n\t{\n\t\tpublic Label Label;\n\t\tpublic TimeSince Since;\n\t}\n\n\tprotected override void OnStart()\n\t{\n\t\tbase.OnStart();\n\t\tBuildUi();\n\t}\n\n\tvoid BuildUi()\n\t{\n\t\tvar root = Panel;\n\t\tif ( root is null ) return;\n\n\t\t// \u5DF2\u5EFA\u597D\u4E14\u5F15\u7528\u6709\u6548 \u2192 \u4E0D\u91CD\u590D\u5EFA\u3002\u70ED\u91CD\u8F7D\u53EF\u80FD\u534A\u6B8B\uFF08\u5F15\u7528\u5931\u6548\uFF09\uFF0C\u6574\u4F53\u6E05\u7A7A\u91CD\u5EFA\n\t\tif ( _built \u0026\u0026 _mass.IsValid() \u0026\u0026 _hint.IsValid() \u0026\u0026 root.ChildrenCount \u003E 0 ) return;\n\n\t\troot.DeleteChildren( true );\n\n\t\troot.Style.Position = PositionMode.Absolute;\n\t\troot.Style.Left = 0f;\n\t\troot.Style.Top = 0f;\n\t\troot.Style.Right = 0f;\n\t\troot.Style.Bottom = 0f;\n\t\troot.Style.PointerEvents = PointerEvents.None;\n\n\t\t_mass = new Label() { Classes = \u0022mass\u0022 };\n\t\troot.AddChild( _mass );\n\n\t\t// \u7248\u672C\u53F7\uFF08\u53F3\u4E0A\u89D2\u3001\u6392\u884C\u699C\u4E0A\u65B9\uFF09\uFF1A\u53CC\u5F00\u65F6\u8089\u773C\u6838\u5BF9\u4E24\u7AEF\u6784\u5EFA\u4E00\u81F4\u6027\n\t\t_version = new Label() { Classes = \u0022version\u0022 };\n\t\t_version.Text = GameConfig.Version;\n\t\troot.AddChild( _version );\n\n\t\t_hint = new Label() { Classes = \u0022hint\u0022 };\n\t\t_hint.Text = GameConfig.EnableSplit\n\t\t\t? \u0022WASD MOVE \u2014 SPACE/RMB SPLIT \u2014 R/LMB FEED \u2014 Q/E USE ITEM\u0022\n\t\t\t: \u0022WASD / GAMEPAD \u2014 MOVE\u0022;\n\t\troot.AddChild( _hint );\n\n\t\t// \u9053\u5177 buff \u72B6\u6001\uFF08v0.7.3.0\uFF09\uFF1A\u64CD\u4F5C\u63D0\u793A\u4E0A\u65B9\uFF0C\u6587\u5B57\u002B\u5012\u8BA1\u65F6\uFF0C\u79CD\u7C7B\u914D\u8272\n\t\t_buff = new Label() { Classes = \u0022buff\u0022 };\n\t\troot.AddChild( _buff );\n\n\t\t// \u73A9\u6CD5\u63D0\u793A\u8F6E\u64AD\uFF08v0.7.4.2\uFF09\uFF1Abuff \u4E0B\u65B9\u3001\u64CD\u4F5C\u63D0\u793A\u4E0A\u65B9\uFF0C\u6BCF 8s \u6362\u4E00\u6761\n\t\t_tips = new Label() { Classes = \u0022tips\u0022 };\n\t\troot.AddChild( _tips );\n\n\t\t// \u80CC\u5305\u69FD\u663E\u793A\uFF08v0.7.5.0 \u4E3B\u52A8\u4F7F\u7528\uFF09\uFF1A\u53F3\u4E0B\u89D2\u4E24\u683C\uFF0C[Q]/[E] \u002B \u9053\u5177\u540D\uFF08\u79CD\u7C7B\u914D\u8272\uFF09\n\t\t_slotQ = new Label() { Classes = \u0022slotq\u0022 };\n\t\t_slotE = new Label() { Classes = \u0022slote\u0022 };\n\t\troot.AddChild( _slotQ );\n\t\troot.AddChild( _slotE );\n\n\t\t// \u6392\u884C\u699C\uFF1A\u53F3\u4E0A\u89D2\u56FA\u5B9A\u884C\u6570\uFF0C\u5750\u6807\u5728\u4EE3\u7801\u91CC\u6309\u884C\u53F7\u6392\uFF08\u907F\u5F00 flex \u9677\u9631\uFF09\n\t\t_rows = new Label[GameConfig.LeaderboardRows];\n\t\tfor ( int i = 0; i \u003C _rows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar row = new Label() { Classes = \u0022row\u0022 };\n\t\t\trow.Style.Position = PositionMode.Absolute;\n\t\t\trow.Style.Right = 28f;\n\t\t\trow.Style.Top = 24f \u002B i * 24f;\n\t\t\troot.AddChild( row );\n\t\t\t_rows[i] = row;\n\t\t}\n\n\t\t// \u81EA\u8EAB\u6392\u540D\u5728\u699C\u5916\u65F6\u663E\u793A\u5728\u6392\u884C\u699C\u6B63\u4E0B\u65B9\n\t\t_selfRow = new Label() { Classes = \u0022row self\u0022 };\n\t\t_selfRow.Style.Position = PositionMode.Absolute;\n\t\t_selfRow.Style.Right = 28f;\n\t\t_selfRow.Style.Top = 24f \u002B GameConfig.LeaderboardRows * 24f;\n\t\troot.AddChild( _selfRow );\n\n\t\t// \u672C\u961F\u6210\u5458\u79EF\u5206\uFF08\u56E2\u961F\u8D5B\uFF0Cv0.6.5.0\uFF09\uFF1A\u6392\u884C\u699C/\u81EA\u8EAB\u884C\u4E0B\u65B9\u2014\u20141 \u884C\u961F\u5934 \u002B 4 \u884C\u6210\u5458\uFF084 \u4EBA\u961F\u5C01\u9876\uFF09\n\t\t_mateRows = new Label[5];\n\t\tfor ( int i = 0; i \u003C _mateRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar m = new Label() { Classes = \u0022row\u0022 };\n\t\t\tm.Style.Position = PositionMode.Absolute;\n\t\t\tm.Style.Right = 28f;\n\t\t\tm.Style.Top = 300f \u002B i * 24f;\n\t\t\tm.Style.Display = DisplayMode.None;\n\t\t\troot.AddChild( m );\n\t\t\t_mateRows[i] = m;\n\t\t}\n\n\t\t// \u6B7B\u4EA1\u91CD\u751F\u9762\u677F\n\t\t_deathTitle = new Label() { Classes = \u0022death-title\u0022 };\n\t\t_deathTitle.Text = \u0022YOU WERE EATEN\u0022;\n\t\troot.AddChild( _deathTitle );\n\n\t\t_deathSub = new Label() { Classes = \u0022death-sub\u0022 };\n\t\t_deathSub.Text = \u0022PRESS SPACE / CLICK TO RESPAWN\u0022;\n\t\t// lambda \u6355\u83B7 this\uFF08\u5B9E\u4F8B\u65B9\u6CD5\uFF09\u2014\u2014\u65E0\u6355\u83B7\u7684\u9759\u6001 lambda \u70ED\u91CD\u8F7D\u540E\u65E0\u6CD5\u91CD\u6620\u5C04\uFF08\u5B9E\u6D4B\u70B9\u51FB\u629B NoMatchLambda\uFF09\n\t\t_deathSub.AddEventListener( \u0022onclick\u0022, () =\u003E OnDeathSubClicked() );\n\t\troot.AddChild( _deathSub );\n\n\t\t// \u6BD4\u8D5B\u89C4\u5219 UI\uFF08M5\uFF09\uFF1A\u9876\u90E8\u4E2D\u592E\u6A21\u5F0F\u002B\u5012\u8BA1\u65F6\uFF1B\u56E2\u961F\u8D5B\u5728\u4E0B\u9762\u52A0\u961F\u4F0D\u603B\u5206\u6761\n\t\t_matchInfo = new Label() { Classes = \u0022matchinfo\u0022 };\n\t\troot.AddChild( _matchInfo );\n\n\t\t_teamRows = new Label[6];\n\t\tfor ( int i = 0; i \u003C _teamRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar t = new Label() { Classes = \u0022teamscore\u0022 };\n\t\t\tt.Style.Position = PositionMode.Absolute;\n\t\t\tt.Style.Display = DisplayMode.None;\n\t\t\troot.AddChild( t );\n\t\t\t_teamRows[i] = t;\n\t\t}\n\n\t\t// \u540D\u724C\u6295\u5F71\u5C42\uFF1ALabel \u6C60 \u002B \u6309\u7403\u8EAB\u4EFD\u7A33\u5B9A\u7ED1\u5B9A\uFF0C\u5750\u6807\u6BCF\u5E27\u7531\u4E16\u754C\u4F4D\u7F6E\u6295\u5F71\uFF08\u89C1 UpdateNameTags\uFF09\n\t\t_tagPool = new List\u003CTagSlot\u003E();\n\t\t_tagMap = new Dictionary\u003CGuid, TagSlot\u003E();\n\t\t_pieceTagMap = new Dictionary\u003Cint, TagSlot\u003E();\n\t\tfor ( int i = 0; i \u003C GameConfig.NameTagPoolSize; i\u002B\u002B )\n\t\t{\n\t\t\tvar tag = new Label() { Classes = \u0022nametag\u0022 };\n\t\t\ttag.Style.Position = PositionMode.Absolute;\n\t\t\ttag.Style.Width = 160f;\n\t\t\ttag.Style.Display = DisplayMode.None;\n\t\t\troot.AddChild( tag );\n\t\t\t_tagPool.Add( new TagSlot { Label = tag } );\n\t\t}\n\n\t\t_built = true;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\tBuildUi();\n\n\t\tvar game = CircleroyaleGame.Current;\n\t\tif ( game is null || !_built ) return;\n\n\t\tbool over = MatchState.MatchOver;\n\n\t\tUpdateMass( game, over );\n\t\tUpdateLeaderboard( game, over );\n\t\tUpdateMates( game, over );\n\t\tUpdateMatchInfo( game );\n\t\tUpdateKillFeed();\n\t\tUpdateDeathPanel( game, over );\n\t\tUpdateBuff( game );\n\t\tUpdateTips();\n\t\tUpdatePowerSlots( game );\n\t\tUpdateBanners();\n\t\tGameAchievements.MatchTick( game );   // \u6210\u5C31\u6BCF\u5E27\u4F53\u68C0\uFF1A\u8D28\u91CF\u91CC\u7A0B\u7891/8\u8EAB\u4F53/\u5B58\u6D3B5\u5206\u949F/\u80CC\u5305\u53CC\u6EE1\n\t\tGameXp.MatchTick( game );             // \u7B49\u7EA7\uFF1A\u672C\u673A\u7B49\u7EA7\u5199\u8FDB\u81EA\u5DF1\u7684\u7403\uFF08owner [Sync]\uFF0C\u5168\u573A\u540D\u724C\u53EF\u89C1\uFF09\n\t\t// \u540D\u724C\u6295\u5F71\u6539\u7531 NeonCamera \u5728\u5199\u5B8C\u76F8\u673A\u59FF\u6001\u7684\u540C\u4E00\u65F6\u523B\u8C03\u7528\uFF08UpdateTagPositions\uFF09\uFF0C\n\t\t// \u4FDD\u8BC1\u6295\u5F71\u7528\u7684\u76F8\u673A\u4F4D\u7F6E/\u53D8\u7126\u4E0E\u6E32\u67D3\u5E27\u5B8C\u5168\u4E00\u81F4\u2014\u2014\u5F15\u64CE\u67E5\u8BE2\u6709\u5E27\u95F4\u5DEE\uFF0C\u5FEB\u901F\u6ED1\u5C4F\u65F6\u540D\u724C\u4F1A\u98DE\u51FA\u5C4F\uFF08\u5B9E\u6D4B\uFF09\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u540D\u724C\uFF1A\u628A\u6BCF\u9897\u7403\u7684\u4E16\u754C\u4F4D\u7F6E\u6295\u5F71\u5230\u5C4F\u5E55\u5750\u6807\u6446 Label\u3002\n\t/// \u4E0D\u624B\u63A8\u76F8\u673A\u8F74\u5411\u2014\u2014\u7528 CameraComponent.ScreenToWorld \u73B0\u573A\u6807\u5B9A\u0022\u4E16\u754C\u2194\u5C4F\u5E55\u50CF\u7D20\u0022\u7684\n\t/// \u7EBF\u6027\u6620\u5C04\uFF08\u6295\u5F71\u4E2D\u5FC3/\u53F3100px/\u4E0B100px \u4E09\u4E2A\u70B9\u89E3\u57FA\u5411\u91CF\uFF09\uFF0C\u5BF9\u4EFB\u4F55\u76F8\u673A\u671D\u5411\u4E0E\u7F29\u653E\u90FD\u6210\u7ACB\u3002\n\t/// Label \u6309\u7403 Id \u7A33\u5B9A\u7ED1\u5B9A\uFF1B\u8FB9\u7F18\u5E26\u6EDE\u56DE\uFF08\u51FA\u5C4F 100px \u624D\u9690\u85CF\uFF09\uFF0C\u9632\u7403\u5728\u8FB9\u754C\u6765\u56DE\u65F6\u95EA\u70C1\u3002\n\t/// \u003C/summary\u003E\n\t/// \u003Csummary\u003E\n\t/// \u540D\u724C\u6446\u653E\uFF08\u7531 NeonCamera \u5728\u5199\u5B8C\u76F8\u673A\u59FF\u6001\u540E\u8C03\u7528\u2014\u2014\u6295\u5F71\u4E0E\u6E32\u67D3\u7528\u540C\u4E00\u4EFD\u76F8\u673A\u6570\u636E\uFF0C\u96F6\u5E27\u95F4\u5DEE\uFF09\u3002\n\t/// \u89E3\u6790\u6295\u5F71\uFF1A\u76F8\u673A\u4E3A\u56FA\u5B9A\u6B63\u4EA4\u4FEF\u89C6\uFF08\u6EDA\u52A8 90\u00B0\uFF1A\u5C4F\u5E55\u53F3=\u4E16\u754C-Y\u3001\u5C4F\u5E55\u4E0B=\u4E16\u754C-X\uFF09\uFF0C\n\t/// \u53EA\u4F9D\u8D56\u76F8\u673A\u4F4D\u7F6E\u4E0E OrthoHeight \u4E24\u4E2A\u6E32\u67D3\u771F\u6E90\u3002\n\t/// \u003C/summary\u003E\n\tpublic void UpdateTagPositions( CircleroyaleGame game, Vector3 camPos, float orthoHeight )\n\t{\n\t\tif ( orthoHeight \u003C= 0.1f ) return;\n\t\tif ( _tagPool is null || _tagPool[0].Label is null || !_tagPool[0].Label.IsValid() ) return;\n\n\t\tvar panel = Panel;\n\t\tif ( !panel.IsValid() ) return;\n\n\t\tvar size = panel.Box.Rect.Size;\n\t\tif ( size.x \u003C 1f || size.y \u003C 1f ) return;\n\n\t\t// \u6BCF\u5E27\u5148\u6E05\u5360\u7528\u6807\u8BB0\uFF0C\u7403\u6B7B\u4EA1\u540E\u5176\u69FD\u4F4D\u624D\u80FD\u88AB\u65B0\u7403\u590D\u7528\uFF08\u6F0F\u6389\u8FD9\u6B65\u6C60\u5B50\u4F1A\u9010\u6E10\u8017\u5C3D\uFF09\n\t\tforeach ( var ts in _tagPool )\n\t\t{\n\t\t\tts.Used = false;\n\t\t}\n\n\t\t// ScreenToWorld \u662F\u7269\u7406\u50CF\u7D20\uFF1BStyle.Left/Top \u662F\u903B\u8F91\u5355\u4F4D\uFF08RootPanel \u6309 1080p \u57FA\u51C6\u7F29\u653E\uFF09\n\t\tvar rootPanel = panel.FindRootPanel();\n\t\tvar uiScale = rootPanel is not null \u0026\u0026 rootPanel.Scale \u003E 0.001f ? rootPanel.Scale : 1f;\n\n\t\t// \u89E3\u6790\u6295\u5F71\uFF1As = \u6BCF\u50CF\u7D20\u4E16\u754C\u5355\u4F4D\uFF1B\u5C4F\u5E55\u53F3=-Y\u3001\u5C4F\u5E55\u4E0B=-X\n\t\tvar s = orthoHeight / size.y;\n\n\t\t// \u961F\u53CB\u51FA\u5C4F\u6307\u793A\uFF08v0.7.8.12\uFF09\uFF1A\u56E2\u961F\u8D5B \u002B \u672C\u673A\u7403\u6709\u6548\u65F6\u542F\u7528\n\t\tvar local = game.LocalBall;\n\t\tbool allyMode = MatchState.IsTeam \u0026\u0026 local.IsValid();\n\t\tvar marginW = 56f * s;                      // \u8D34\u8FB9\u5185\u7F29\uFF08\u4E16\u754C\u5355\u4F4D\uFF09\n\t\tvar halfX = size.y * 0.5f * s;              // \u5C4F\u5E55\u9AD8\u5EA6\u65B9\u5411 \u2194 \u4E16\u754C X\n\t\tvar halfY = size.x * 0.5f * s;              // \u5C4F\u5E55\u5BBD\u5EA6\u65B9\u5411 \u2194 \u4E16\u754C Y\n\n\t\t// \u6295\u5F71 \u002B \u6446\u5B57\uFF08\u7403\u4E0E\u5206\u8EAB\u5171\u7528\u4E00\u4EFD\u903B\u8F91\uFF09\n\t\tvoid Place( TagSlot slot, Vector3 worldPos, string text )\n\t\t{\n\t\t\tvar px = size.x * 0.5f - ( worldPos.y - camPos.y ) / s;\n\t\t\tvar py = size.y * 0.5f - ( worldPos.x - camPos.x ) / s;\n\t\t\tbool inView = px \u003E -20f \u0026\u0026 px \u003C size.x \u002B 20f \u0026\u0026 py \u003E -20f \u0026\u0026 py \u003C size.y \u002B 20f;\n\n\t\t\tif ( !inView )\n\t\t\t{\n\t\t\t\tif ( slot.Shown )\n\t\t\t\t{\n\t\t\t\t\tslot.Label.Style.Display = DisplayMode.None;\n\t\t\t\t\tslot.Shown = false;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tslot.Label.Style.Display = DisplayMode.Flex;\n\t\t\tslot.Label.Style.Left = px / uiScale - 80f;\n\t\t\tslot.Label.Style.Top = py / uiScale \u002B 8f;\n\t\t\tslot.Label.Style.FontColor = new Color( 1f, 1f, 1f, 0.88f );   // \u590D\u4F4D\uFF08\u961F\u53CB\u51FA\u5C4F\u6307\u793A\u66FE\u8BBE\u4E3A\u961F\u4F0D\u8272\uFF09\n\t\t\tSetText( slot.Label, text );\n\t\t\tslot.Shown = true;\n\t\t}\n\n\t\tTagSlot TakeSlot() =\u003E _tagPool.FirstOrDefault( s2 =\u003E !s2.Used );\n\n\t\tforeach ( var b in game.Balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.Alive ) continue;   // \u6B7B\u4EA1/\u86F0\u4F0F\u7403\uFF1A\u5934\u50CF\u90FD\u9690\u85CF\uFF0C\u4E0D\u6302\u540D\u724C\uFF08\u6D6E\u7A7A\u5C38\u540D\uFF09\n\n\t\t\tif ( !_tagMap.TryGetValue( b.Id, out var slot ) )\n\t\t\t{\n\t\t\t\tslot = TakeSlot();\n\t\t\t\tif ( slot is null ) continue;   // \u6C60\u8017\u5C3D\n\t\t\t\t_tagMap[b.Id] = slot;\n\t\t\t}\n\t\t\tslot.Used = true;\n\n\t\t\t// \u961F\u53CB\u51FA\u5C4F\u6307\u793A\uFF08v0.7.8.12\uFF09\uFF1A\u4E3B\u7403\u94B3\u5230\u89C6\u53E3\u8D34\u8FB9\u2014\u2014\u4E16\u754C\u7A7A\u95F4\u7BAD\u5934\u6307\u5411\u771F\u5B9E\u65B9\u4F4D\uFF0C\n\t\t\t// \u540D\u5B57\u8D34\u5728\u7BAD\u5934\u5185\u4FA7\uFF08\u961F\u53CB\u8272\uFF09\u3002\u89C6\u91CE\u5185\u8D70\u4E0B\u65B9\u666E\u901A\u540D\u724C\u8DEF\u5F84\n\t\t\tif ( allyMode \u0026\u0026 b != local \u0026\u0026 b.TeamIndex \u003E= 0 \u0026\u0026 b.TeamIndex == local.TeamIndex )\n\t\t\t{\n\t\t\t\tvar cx = Math.Clamp( b.WorldPosition.x, camPos.x - halfX \u002B marginW, camPos.x \u002B halfX - marginW );\n\t\t\t\tvar cy = Math.Clamp( b.WorldPosition.y, camPos.y - halfY \u002B marginW, camPos.y \u002B halfY - marginW );\n\t\t\t\tvar dx = b.WorldPosition.x - cx;\n\t\t\t\tvar dy = b.WorldPosition.y - cy;\n\t\t\t\tvar dl = MathF.Sqrt( dx * dx \u002B dy * dy );\n\n\t\t\t\tif ( dl \u003E 0.001f )   // \u22480 = \u5728\u89C6\u53E3\u5185\uFF08\u94B3\u5236\u6CA1\u751F\u6548\uFF09\uFF0C\u843D\u666E\u901A\u540D\u724C\n\t\t\t\t{\n\t\t\t\t\tdx /= dl;\n\t\t\t\t\tdy /= dl;\n\n\t\t\t\t\tNeonRenderer.EdgeArrow( new Vector3( cx, cy, 0f ), new Vector2( dx, dy ), b.NeonColor, 30f * s );\n\n\t\t\t\t\tvar lx = cx - dx * 46f * s;   // \u540D\u5B57\u671D\u5C4F\u5185\u504F ~46px\n\t\t\t\t\tvar ly = cy - dy * 46f * s;\n\t\t\t\t\tvar px = size.x * 0.5f - ( ly - camPos.y ) / s;\n\t\t\t\t\tvar py = size.y * 0.5f - ( lx - camPos.x ) / s;\n\n\t\t\t\t\tslot.Label.Style.Display = DisplayMode.Flex;\n\t\t\t\t\tslot.Label.Style.Left = px / uiScale - 80f;\n\t\t\t\t\tslot.Label.Style.Top = py / uiScale \u002B 8f;\n\t\t\t\t\tSetText( slot.Label, TagText( b.PlayerName, b.TeamIndex ) );\n\t\t\t\t\tslot.Label.Style.FontColor = b.NeonColor;\n\t\t\t\t\tslot.Shown = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tPlace( slot, b.WorldPosition, TagText( b.PlayerName, b.TeamIndex, b.AccountLevel ) );\n\t\t}\n\n\t\t// \u5206\u8EAB\u540D\u724C\uFF08M4\uFF09\uFF1A\u540D\u5B57\u7ECF\u4E3B\u4EBA\u7403\u89E3\u6790\uFF08bot \u6709\u72EC\u7ACB\u5047 SteamId\uFF0C\u80FD\u7CBE\u786E\u5230\u54EA\u4E2A bot\uFF09\n\t\tforeach ( var c in game.Cells )\n\t\t{\n\t\t\tif ( c.PieceKind != CellPiece.Kind.SplitPiece ) continue;\n\n\t\t\tvar owner = game.FindBallBySteamId( c.OwnerSteamId );\n\t\t\tif ( !owner.IsValid() || !owner.Alive ) continue;\n\n\t\t\tif ( !_pieceTagMap.TryGetValue( c.Id, out var slot ) )\n\t\t\t{\n\t\t\t\tslot = TakeSlot();\n\t\t\t\tif ( slot is null ) continue;\n\t\t\t\t_pieceTagMap[c.Id] = slot;\n\t\t\t}\n\t\t\tslot.Used = true;\n\n\t\t\tPlace( slot, new Vector3( c.DrawPos.x, c.DrawPos.y, 0f ), TagText( owner.PlayerName, owner.TeamIndex, owner.AccountLevel ) );\n\t\t}\n\n\t\t// \u672C\u5E27\u672A\u7528\u5230\u7684\u69FD = \u5B9E\u4F53\u5DF2\u6B7B\u4EA1/\u5206\u8EAB\u5DF2\u5408\u5E76\uFF1A\u9690\u85CF\u5E76\u89E3\u9664\u7ED1\u5B9A\n\t\tfor ( int i = _tagMap.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar kv = _tagMap.ElementAt( i );\n\t\t\tif ( kv.Value.Used ) continue;\n\n\t\t\tkv.Value.Label.Style.Display = DisplayMode.None;\n\t\t\tkv.Value.Shown = false;\n\t\t\t_tagMap.Remove( kv.Key );\n\t\t}\n\t\tfor ( int i = _pieceTagMap.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar kv = _pieceTagMap.ElementAt( i );\n\t\t\tif ( kv.Value.Used ) continue;\n\n\t\t\tkv.Value.Label.Style.Display = DisplayMode.None;\n\t\t\tkv.Value.Shown = false;\n\t\t\t_pieceTagMap.Remove( kv.Key );\n\t\t}\n\t}\n\n\tvoid UpdateMass( CircleroyaleGame game, bool over )\n\t{\n\t\tif ( !_mass.IsValid() ) return;\n\n\t\t// \u6BD4\u8D5B\u7ED3\u675F\uFF1A\u6570\u503C\u7C7B HUD \u5168\u90E8\u8BA9\u4F4D\u7ED9\u7ED3\u7B97\u9762\u677F\n\t\t_mass.Style.Display = over ? DisplayMode.None : DisplayMode.Flex;\n\t\tif ( over ) return;\n\n\t\tvar ball = game.LocalBall;\n\t\tSetText( _mass, ball.IsValid() ? $\u0022{game.TotalMassFor( ball ):0}\u0022 : \u0022\u2014\u0022 );\n\t}\n\n\tvoid UpdateLeaderboard( CircleroyaleGame game, bool over )\n\t{\n\t\tif ( _rows is null || _rows[0] is null || !_rows[0].IsValid() ) return;\n\n\t\tif ( over )\n\t\t{\n\t\t\tfor ( int i = 0; i \u003C _rows.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tif ( _rows[i].IsValid() ) _rows[i].Text = \u0022\u0022;\n\t\t\t}\n\t\t\tif ( _selfRow.IsValid() ) _selfRow.Style.Display = DisplayMode.None;\n\t\t\treturn;\n\t\t}\n\n\t\t// \u6309\u603B\u8D28\u91CF\uFF08\u4E3B\u7403\u002B\u5206\u8EAB\uFF09\u964D\u5E8F\uFF1B\u7403\u6570 \u2264 BotFillTarget\uFF0C\u62F7\u8D1D\u6392\u5E8F\u7684\u5F00\u9500\u53EF\u5FFD\u7565\n\t\tvar list = new List\u003CBall\u003E();\n\t\tforeach ( var b in game.Balls )\n\t\t{\n\t\t\tif ( b.IsValid() \u0026\u0026 b.Alive ) list.Add( b );\n\t\t}\n\t\tvar massOf = new Dictionary\u003CBall, float\u003E();\n\t\tforeach ( var b in list )\n\t\t\tmassOf[b] = game.TotalMassFor( b );\n\t\tlist.Sort( ( x, y ) =\u003E massOf[y].CompareTo( massOf[x] ) );\n\n\t\tvar local = game.LocalBall;\n\t\tvar selfRank = -1;\n\t\tfor ( int i = 0; i \u003C list.Count; i\u002B\u002B )\n\t\t{\n\t\t\tif ( local.IsValid() \u0026\u0026 list[i] == local )\n\t\t\t{\n\t\t\t\tselfRank = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfor ( int i = 0; i \u003C _rows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar row = _rows[i];\n\t\t\tif ( !row.IsValid() ) continue;\n\n\t\t\tif ( i \u003E= list.Count )\n\t\t\t{\n\t\t\t\trow.Text = \u0022\u0022;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar ball = list[i];\n\t\t\tvar isSelf = local.IsValid() \u0026\u0026 ball == local;\n\t\t\tSetText( row, TeamModeRow( $\u0022{i \u002B 1}.\u0022, ball.PlayerName, $\u0022{massOf[ball]:0}\u0022, ball.TeamIndex ) );\n\t\t\trow.Style.FontColor = isSelf ? new Color( 0.21f, 0.94f, 1f ) : new Color( 1f, 1f, 1f, 0.55f );\n\t\t}\n\n\t\t// \u699C\u5916\u81EA\u8EAB\u884C\n\t\tvar showSelf = selfRank \u003E= _rows.Length;\n\t\tif ( _selfRow.IsValid() )\n\t\t{\n\t\t\t_selfRow.Style.Display = showSelf ? DisplayMode.Flex : DisplayMode.None;\n\t\t\tif ( showSelf )\n\t\t\t\tSetText( _selfRow, TeamModeRow( $\u0022#{selfRank \u002B 1}.\u0022, local.PlayerName, $\u0022{massOf[local]:0}\u0022, local.TeamIndex ) );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u6B7B\u4EA1\u9762\u677F\u70B9\u51FB\u91CD\u751F\uFF08\u5B9E\u4F8B\u65B9\u6CD5\uFF1A\u70ED\u91CD\u8F7D\u53EF\u91CD\u6620\u5C04\uFF09 \u003C/summary\u003E\n\tvoid OnDeathSubClicked() =\u003E CircleroyaleGame.Current?.TryRespawnLocal();\n\n\t/// \u003Csummary\u003E\n\t/// \u672C\u961F\u6210\u5458\u79EF\u5206\uFF08\u56E2\u961F\u8D5B\uFF0C\u6392\u884C\u699C\u4E0B\u65B9\uFF0Cv0.6.5.0\uFF09\uFF1A\u961F\u5934 \u0022MY TEAM [T#]\u0022 \u002B \u6210\u5458\u6309\u8D28\u91CF\u964D\u5E8F\uFF0C\n\t/// \u81EA\u5DF1\u90A3\u884C\u91D1\u8272\u9AD8\u4EAE\u3002\u666E\u901A\u8D5B / \u6CA1\u6709\u672C\u673A\u7403 / \u7ED3\u7B97\u671F\u6574\u5757\u9690\u85CF\u3002\n\t/// \u003C/summary\u003E\n\tvoid UpdateMates( CircleroyaleGame game, bool over )\n\t{\n\t\tif ( _mateRows is null || _mateRows[0] is null || !_mateRows[0].IsValid() ) return;\n\n\t\tvar local = game.LocalBall;\n\t\tbool show = !over \u0026\u0026 MatchState.IsTeam \u0026\u0026 local.IsValid();\n\n\t\tif ( !show )\n\t\t{\n\t\t\tforeach ( var r in _mateRows )\n\t\t\t{\n\t\t\t\tif ( r.IsValid() ) r.Style.Display = DisplayMode.None;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tint myTeam = Math.Max( 0, local.TeamIndex );\n\n\t\t// \u672C\u961F\u5B58\u6D3B\u6210\u5458\u6309\u603B\u8D28\u91CF\u964D\u5E8F\uFF08\u6309\u5757\u5206\u961F\u540E\u6BCF\u961F \u2264 TeamSize \u2264 4 \u4EBA\uFF0C\u76F4\u63A5\u6536\u96C6\u6392\u5E8F\uFF09\n\t\tvar mates = new List\u003CBall\u003E();\n\t\tforeach ( var b in game.Balls )\n\t\t{\n\t\t\tif ( b.IsValid() \u0026\u0026 b.Alive \u0026\u0026 b.TeamIndex == myTeam ) mates.Add( b );\n\t\t}\n\t\tvar massOf = new Dictionary\u003CBall, float\u003E();\n\t\tforeach ( var m in mates )\n\t\t\tmassOf[m] = game.TotalMassFor( m );\n\t\tmates.Sort( ( x, y ) =\u003E massOf[y].CompareTo( massOf[x] ) );\n\n\t\tfor ( int i = 0; i \u003C _mateRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar row = _mateRows[i];\n\t\t\tif ( !row.IsValid() ) continue;\n\n\t\t\tif ( i == 0 )\n\t\t\t{\n\t\t\t\trow.Style.Display = DisplayMode.Flex;\n\t\t\t\tSetText( row, $\u0022MY TEAM [T{myTeam \u002B 1}]  {mates.Sum( m =\u003E massOf[m] ):0}\u0022 );\n\t\t\t\trow.Style.FontColor = MatchState.TeamColor( myTeam );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tint k = i - 1;\n\t\t\tif ( k \u003E= mates.Count )\n\t\t\t{\n\t\t\t\trow.Style.Display = DisplayMode.None;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar mate = mates[k];\n\t\t\tbool self = mate == local;\n\t\t\trow.Style.Display = DisplayMode.Flex;\n\t\t\tSetText( row, $\u0022{( self ? \u0022\u003E\u0022 : \u0022 \u0022)} {mate.PlayerName}  {massOf[mate]:0}\u0022 );\n\t\t\trow.Style.FontColor = self ? new Color( 1f, 0.85f, 0.3f ) : new Color( 1f, 1f, 1f, 0.75f );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E \u540D\u724C\u6587\u672C\uFF08\u56E2\u961F\u8D5B\u5E26 [T#] \u961F\u4F0D\u524D\u7F00\uFF0Cv0.6.5.0 \u7528\u6237\u9700\u6C42\uFF09 \u003C/summary\u003E\n\tstring TagText( string name, int team, int level = 0 ) =\u003E\n\t\t( MatchState.IsTeam \u0026\u0026 team \u003E= 0 ? $\u0022[T{team \u002B 1}] \u0022 : \u0022\u0022 )\n\t\t\u002B ( level \u003E 0 ? $\u0022LV.{level} \u0022 : \u0022\u0022 )\n\t\t\u002B name;\n\n\t/// \u003Csummary\u003E \u6392\u884C\u699C\u884C\u6587\u672C\uFF08\u56E2\u961F\u8D5B\u5E26 T# \u524D\u7F00\uFF1B\u666E\u901A\u8D5B\u53EA\u6709\u540D\u6B21/\u540D\u5B57/\u5206\u6570\uFF09 \u003C/summary\u003E\n\tstring TeamModeRow( string rank, string name, string mass, int team )\n\t{\n\t\t// team\u003C0\uFF08\u86F0\u4F0F/\u65E0\u961F\u7403\uFF09\u4E0D\u663E\u793A\u961F\u4F0D\u524D\u7F00\u2014\u2014Math.Max(0,-1) \u66FE\u8BA9\u65E0\u961F bot \u5168\u6302 [T1]\uFF08v0.7.8.25\uFF09\n\t\treturn ( MatchState.IsTeam \u0026\u0026 team \u003E= 0 ) ? $\u0022{rank} [T{team \u002B 1}] {name}  {mass}\u0022 : $\u0022{rank}  {name}  {mass}\u0022;\n\t}\n\n\t// ---- \u6BD4\u8D5B\u89C4\u5219 UI\uFF08M5\uFF09----\n\n\tvoid UpdateMatchInfo( CircleroyaleGame game )\n\t{\n\t\tif ( !_matchInfo.IsValid() ) return;\n\n\t\tif ( MatchState.MatchOver )\n\t\t{\n\t\t\t_matchInfo.Style.Display = DisplayMode.None;\n\t\t\tHideTeamRows();\n\t\t\treturn;\n\t\t}\n\n\t\t_matchInfo.Style.Display = DisplayMode.Flex;\n\n\t\t// \u5012\u8BA1\u65F6\u683C\u5F0F m:ss\u2014\u2014\u5206\u949F\u5FC5\u987B\u622A\u65AD\u53D6\u6574\uFF08\u00220\u0022 \u683C\u5F0F\u4F1A\u56DB\u820D\u4E94\u5165\uFF0C44 \u79D2\u663E\u793A\u6210 \u00221:44\u0022 \u7684\u6559\u8BAD\uFF09\n\t\tvar total = (int)MathF.Ceiling( MathF.Max( 0f, MatchState.TimeLeft ) );\n\t\tvar mode = MatchState.IsTeam ? \u0022TEAM BATTLE\u0022 : \u0022FREE FOR ALL\u0022;\n\t\tSetText( _matchInfo, $\u0022{mode}   {total / 60}:{total % 60:00}\u0022 );\n\n\t\t// \u6700\u540E 30 \u79D2\u53D8\u7EA2\u63D0\u793A\n\t\t_matchInfo.Style.FontColor = total \u003C 30 ? new Color( 1f, 0.35f, 0.45f ) : new Color( 1f, 1f, 1f, 0.85f );\n\n\t\tif ( MatchState.IsTeam ) UpdateTeamScores( game );\n\t\telse HideTeamRows();\n\t}\n\n\t/// \u003Csummary\u003E \u56E2\u961F\u8D5B\u961F\u4F0D\u603B\u5206\u6761\uFF1A\u524D 5 \u540D\u961F\u4F0D \u002B \u672C\u961F\uFF08\u4E0D\u5728\u524D 5 \u65F6\u8865\u4E00\u6761\uFF09\uFF0C\u6309\u603B\u5206\u964D\u5E8F \u003C/summary\u003E\n\tvoid UpdateTeamScores( CircleroyaleGame game )\n\t{\n\t\tvar totals = new Dictionary\u003Cint, float\u003E();\n\t\tforeach ( var b in game.Balls )\n\t\t{\n\t\t\tif ( !b.IsValid() || !b.Alive ) continue;\n\t\t\tif ( b.TeamIndex \u003C 0 ) continue;   // \u65E0\u961F\u7403\u4E0D\u8FDB\u961F\u4F0D\u805A\u5408\u2014\u2014Math.Max(0,-1) \u66FE\u628A\u86F0\u4F0F\u8BEF\u590D\u6D3B\u7684 16 \u9897\u65E0\u961F bot \u5168\u704C\u8FDB T1\uFF08v0.7.8.25\uFF09\n\t\t\tvar team = b.TeamIndex;\n\t\t\ttotals.TryGetValue( team, out var m );\n\t\t\ttotals[team] = m \u002B game.TotalMassFor( b );\n\t\t}\n\n\t\tvar order = new List\u003Cint\u003E( totals.Keys );\n\t\torder.Sort( ( x, y ) =\u003E totals[y].CompareTo( totals[x] ) );\n\n\t\tvar local = game.LocalBall;\n\t\tvar myTeam = local.IsValid() ? Math.Max( 0, local.TeamIndex ) : -1;\n\n\t\tvar show = new List\u003Cint\u003E();\n\t\tforeach ( var t in order )\n\t\t{\n\t\t\tif ( show.Count \u003E= 5 ) break;\n\t\t\tshow.Add( t );\n\t\t}\n\t\tif ( myTeam \u003E= 0 \u0026\u0026 totals.ContainsKey( myTeam ) \u0026\u0026 !show.Contains( myTeam ) )\n\t\t\tshow.Add( myTeam );\n\t\tshow.Sort( ( x, y ) =\u003E totals[y].CompareTo( totals[x] ) );\n\n\t\t// \u6A2A\u6392\u5C45\u4E2D\uFF08\u903B\u8F91\u5BBD 1920\uFF09\uFF0C\u6BCF\u683C 170\n\t\tfloat width = 170f;\n\t\tfloat startX = 960f - show.Count * width / 2f;\n\n\t\tfor ( int i = 0; i \u003C _teamRows.Length; i\u002B\u002B )\n\t\t{\n\t\t\tvar row = _teamRows[i];\n\t\t\tif ( !row.IsValid() ) continue;\n\n\t\t\tif ( i \u003E= show.Count )\n\t\t\t{\n\t\t\t\trow.Style.Display = DisplayMode.None;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar team = show[i];\n\t\t\trow.Style.Display = DisplayMode.Flex;\n\t\t\trow.Style.Left = startX \u002B i * width;\n\t\t\tSetText( row, $\u0022T{team \u002B 1}  {totals[team]:0}\u0022 );\n\n\t\t\tbool mine = team == myTeam;\n\t\t\trow.Style.FontColor = mine ? new Color( 1f, 1f, 1f ) : MatchState.TeamColor( team );\n\t\t\trow.Style.Opacity = mine ? 1f : 0.75f;\n\t\t}\n\t}\n\n\tvoid HideTeamRows()\n\t{\n\t\tif ( _teamRows is null ) return;\n\t\tforeach ( var r in _teamRows )\n\t\t{\n\t\t\tif ( r.IsValid() ) r.Style.Display = DisplayMode.None;\n\t\t}\n\t}\n\n\t// ---- \u51FB\u6740\u64AD\u62A5\uFF08M5\u0022\u8C01\u5403\u4E86\u8C01\u0022\uFF09----\n\n\t/// \u003Csummary\u003E \u52A0\u4E00\u6761\u64AD\u62A5\uFF08CircleroyaleGame.OnBallEaten \u53CC\u7AEF\u8C03\u7528\uFF09\uFF1A\u81EA\u5DF1\u53C2\u4E0E\u7684\u9AD8\u4EAE\uFF08\u91D1=\u5403 \u7EA2=\u88AB\u5403\uFF09 \u003C/summary\u003E\n\tpublic void AddKillFeed( string eaterName, string eatenName, float massGained, long eaterSteamId, long eatenSteamId )\n\t{\n\t\tvar root = Panel;\n\t\tif ( root is null || !root.IsValid() ) return;\n\n\t\t// \u4E0A\u9650 6 \u6761\uFF0C\u6324\u6389\u6700\u8001\n\t\twhile ( _killFeed.Count \u003E= 6 )\n\t\t{\n\t\t\tif ( _killFeed[0].Label.IsValid() ) _killFeed[0].Label.Delete();\n\t\t\t_killFeed.RemoveAt( 0 );\n\t\t}\n\n\t\tbool iAte = GameSfx.IsMine( eaterSteamId );\n\t\tbool iDied = GameSfx.IsMine( eatenSteamId );\n\n\t\tvar l = new Label() { Classes = iAte ? \u0022killfeed good\u0022 : iDied ? \u0022killfeed bad\u0022 : \u0022killfeed\u0022 };\n\t\tvar text = $\u0022{eaterName}  \u27EB  {eatenName}\u0022;\n\t\tif ( iAte \u0026\u0026 massGained \u003E= 1f ) text \u002B= $\u0022   \u002B{massGained:0}\u0022;\n\t\tl.Text = text;\n\t\tl.Style.Position = PositionMode.Absolute;\n\t\tl.Style.Left = 30f;\n\t\troot.AddChild( l );\n\n\t\t_killFeed.Add( new KillFeedEntry { Label = l, Since = 0 } );\n\t}\n\n\tvoid UpdateKillFeed()\n\t{\n\t\tfor ( int i = _killFeed.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar e = _killFeed[i];\n\t\t\tif ( !e.Label.IsValid() || e.Since \u003E 4.5f )\n\t\t\t{\n\t\t\t\tif ( e.Label.IsValid() ) e.Label.Delete();\n\t\t\t\t_killFeed.RemoveAt( i );\n\t\t\t}\n\t\t}\n\n\t\t// \u81EA\u4E0A\u800C\u4E0B\u91CD\u6392\uFF08\u8001\u6761\u76EE\u5220\u9664\u540E\u4E0D\u4E32\u4F4D\uFF09\u002B \u5C3E\u6BB5\u6DE1\u51FA\n\t\tfor ( int i = 0; i \u003C _killFeed.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar e = _killFeed[i];\n\t\t\te.Label.Style.Top = 68f \u002B i * 26f;\n\t\t\te.Label.Style.Opacity = e.Since \u003C 3.5f ? 1f : Math.Clamp( 1f - ( e.Since - 3.5f ) / 1f, 0f, 1f );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u672C\u5730\u7403\u9053\u5177 buff \u72B6\u6001\uFF08v0.7.3.0\uFF09\uFF1A\u5DE6\u4E0B\u89D2\u64CD\u4F5C\u63D0\u793A\u4E0A\u65B9\uFF0C\u79CD\u7C7B\u540D \u002B \u5269\u4F59\u79D2 \u002B \u79CD\u7C7B\u8272\u3002\n\t/// \u5269\u4F59\u79D2\u6BCF\u5E27\u5728\u53D8\uFF08\u6587\u672C\u9632\u6296\u6321\u4E0D\u4F4F\uFF09\uFF0C\u989C\u8272\u53EA\u5728\u79CD\u7C7B\u5207\u6362\u65F6\u8D4B\u503C\u3002\n\t/// \u003C/summary\u003E\n\tvoid UpdateBuff( CircleroyaleGame game )\n\t{\n\t\tif ( _buff is null || !_buff.IsValid() ) return;\n\n\t\tvar ball = game.LocalBall;\n\t\tif ( !ball.IsValid() || !ball.Alive || ball.BuffKind == 255 || ball.BuffLeft \u003C= 0f )\n\t\t{\n\t\t\tSetText( _buff, \u0022\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tSetText( _buff, $\u0022{PowerUpManager.NameOf( ball.BuffKind )}  {ball.BuffLeft:0}s\u0022 );\n\t\tvar c = PowerUpManager.ColorOf( ball.BuffKind );\n\t\tif ( _buff.Style.FontColor != c ) _buff.Style.FontColor = c;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u73A9\u6CD5\u63D0\u793A\u8F6E\u64AD\uFF08v0.7.4.2\uFF09\uFF1A\u6BCF 8s \u6362\u4E00\u6761\uFF0C\u6362\u6761\u77AC\u95F4 0.5s \u900F\u660E\u5EA6\u6E10\u5165\uFF08Opacity \u4E0D\u89E6\u53D1\n\t/// \u5E03\u5C40\u91CD\u7B97\uFF0C\u6E10\u5165\u7ED3\u675F\u503C\u6052 1 \u4E0D\u518D\u5199\u6837\u5F0F\uFF09\uFF1B\u6587\u672C\u8D70 SetText \u9632\u6296\u3002\n\t/// \u003C/summary\u003E\n\tvoid UpdateTips()\n\t{\n\t\tif ( _tips is null || !_tips.IsValid() ) return;\n\n\t\tif ( _sinceTip \u003E 8f )\n\t\t{\n\t\t\t_tipIndex = ( _tipIndex \u002B 1 ) % Tips.Length;\n\t\t\t_sinceTip = 0;\n\t\t\tSetText( _tips, Tips[_tipIndex] );\n\t\t}\n\n\t\tvar op = Math.Clamp( _sinceTip / 0.5f, 0f, 1f );\n\t\tif ( _tips.Style.Opacity != op )\n\t\t\t_tips.Style.Opacity = op;   // \u6E10\u5165 0.5s \u5185\u9010\u5E27\u53D8\u5316\uFF1B\u7ED3\u675F\u540E\u540C\u503C\u4E0D\u518D\u5199\uFF08\u9632\u65E0\u8C13\u6837\u5F0F\u5237\u65B0\uFF09\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u80CC\u5305\u69FD\u663E\u793A\uFF08v0.7.5.0 \u4E3B\u52A8\u4F7F\u7528\uFF09\uFF1A\u53F3\u4E0B\u89D2 [Q]/[E] \u4E24\u683C\u2014\u2014\u6301\u6709\u65F6\u663E\u793A\u9053\u5177\u540D\uFF08\u79CD\u7C7B\u8272\uFF09\uFF0C\n\t/// \u7A7A\u69FD\u663E\u793A\u952E\u4F4D\u6697\u6DE1\u3002\u6587\u672C\u9632\u6296\uFF1B\u989C\u8272\u53EA\u5728\u79CD\u7C7B\u5207\u6362\u65F6\u8D4B\u503C\u3002\n\t/// \u003C/summary\u003E\n\tvoid UpdatePowerSlots( CircleroyaleGame game )\n\t{\n\t\tif ( _slotQ is null || !_slotQ.IsValid() || _slotE is null || !_slotE.IsValid() ) return;\n\n\t\tvar ball = game.LocalBall;\n\t\tif ( !ball.IsValid() )\n\t\t{\n\t\t\tif ( _slotQShown != 255 || _slotEShown != 255 )\n\t\t\t{\n\t\t\t\tSetText( _slotQ, \u0022\u0022 );\n\t\t\t\tSetText( _slotE, \u0022\u0022 );\n\t\t\t\t_slotQShown = 255;\n\t\t\t\t_slotEShown = 255;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tDrawSlot( _slotQ, ball.PowerA, \u0022[Q] \u0022, ref _slotQShown, \u0022CHARGING\u0022 );   // Q=\u65F6\u95F4\u5145\u80FD\u6280\u80FD\u4F4D\uFF08v0.7.8.30\uFF09\n\t\tDrawSlot( _slotE, ball.PowerB, \u0022[E] \u0022, ref _slotEShown );\n\t}\n\n\tvoid DrawSlot( Label label, byte kind, string keyPrefix, ref byte shownKind, string emptyText = \u0022\u2014 \u0022 )\n\t{\n\t\tif ( shownKind == kind ) return;\n\t\tshownKind = kind;\n\n\t\tif ( kind == 255 )\n\t\t{\n\t\t\tSetText( label, $\u0022{keyPrefix}{emptyText}\u0022 );\n\t\t\tlabel.Style.FontColor = new Color( 1f, 1f, 1f, 0.22f );\n\t\t\treturn;\n\t\t}\n\n\t\tSetText( label, $\u0022{keyPrefix}{PowerUpManager.NameOf( kind )} \u0022 );\n\t\tlabel.Style.FontColor = PowerUpManager.ColorOf( kind );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u4E2A\u4EBA\u9AD8\u5149\u6A2A\u5E45\uFF08v0.7.6.0\uFF09\uFF1A\u5C4F\u5E55\u4E2D\u4E0B\u5806\u53E0\u6700\u591A 3 \u6761\uFF0C\u5927\u53F7\u53D1\u5149\u5B57\u2014\u2014\n\t/// \u51FB\u6740\uFF08\u91D1\uFF09/ \u6361\u5230\u9053\u5177\uFF08\u79CD\u7C7B\u8272\uFF09/ \u4F7F\u7528\u4E0E\u5582\u517B\u89E6\u53D1\uFF08\u79CD\u7C7B\u8272\uFF09\u3002\n\t/// \u5F39\u5165\uFF08\u900F\u660E\u5EA6\u002B\u4E0B\u843D 20px \u56DE\u4F4D\uFF09\u2192 \u505C\u7559 \u2192 \u6DE1\u51FA\uFF1B\u6700\u8001\u5148\u5220\uFF0C\u4F4D\u7F6E\u9010\u5E27\u91CD\u6392\uFF08\u53D8\u4E86\u624D\u5199\u6837\u5F0F\uFF09\u3002\n\t/// \u003C/summary\u003E\n\tpublic void AddBanner( string text, Color color )\n\t{\n\t\tif ( _banners.Count \u003E= 3 )\n\t\t{\n\t\t\tvar oldest = _banners[0];\n\t\t\tif ( oldest.Label.IsValid() ) oldest.Label.Delete();\n\t\t\t_banners.RemoveAt( 0 );\n\t\t}\n\n\t\tvar l = new Label() { Classes = \u0022banner\u0022 };\n\t\tl.Text = text;\n\t\tl.Style.FontColor = color;\n\t\tPanel?.AddChild( l );\n\t\t_banners.Add( new Banner { Label = l, Since = 0 } );\n\t}\n\n\tvoid UpdateBanners()\n\t{\n\t\tfor ( int i = _banners.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar b = _banners[i];\n\t\t\tif ( b.Label is null || !b.Label.IsValid() )\n\t\t\t{\n\t\t\t\t_banners.RemoveAt( i );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar age = b.Since;\n\t\t\tif ( age \u003E 2.1f )\n\t\t\t{\n\t\t\t\tb.Label.Delete();\n\t\t\t\t_banners.RemoveAt( i );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// \u5806\u53E0\u4F4D\u7F6E\uFF1A\u6700\u65B0\u7684\u6700\u9760\u4E0B\uFF08\u8D34\u8FD1\u5C4F\u5E55\u4E2D\u4E0B\uFF09\uFF0C\u88AB\u6324\u6389\u540E\u5176\u4F59\u9010\u5E27\u56DE\u4F4D\n\t\t\tfloat bottom = 170f \u002B i * 46f;\n\t\t\tif ( b.ShownBottom != bottom )\n\t\t\t{\n\t\t\t\tb.ShownBottom = bottom;\n\t\t\t\tb.Label.Style.Bottom = bottom;\n\t\t\t}\n\n\t\t\tfloat opacity;\n\t\t\tif ( age \u003C 0.3f )\n\t\t\t{\n\t\t\t\topacity = age / 0.3f;\n\t\t\t\tb.Label.Style.PaddingTop = ( 1f - opacity ) * 20f;   // \u5F39\u5165\uFF1A\u4ECE\u4E0B\u65B9\u843D\u56DE\u539F\u4F4D\n\t\t\t}\n\t\t\telse if ( age \u003E 1.5f )\n\t\t\t{\n\t\t\t\topacity = 1f - ( age - 1.5f ) / 0.6f;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\topacity = 1f;\n\t\t\t}\n\n\t\t\tif ( b.Label.Style.Opacity != opacity )\n\t\t\t\tb.Label.Style.Opacity = opacity;\n\t\t}\n\t}\n\n\tvoid UpdateDeathPanel( CircleroyaleGame game, bool over )\n\t{\n\t\tvar dead = game.IsLocalDead \u0026\u0026 !over;   // \u7ED3\u7B97\u671F\u6B7B\u4EA1\u9762\u677F\u8BA9\u4F4D\n\t\tif ( _deathTitle.IsValid() )\n\t\t\t_deathTitle.Style.Display = dead ? DisplayMode.Flex : DisplayMode.None;\n\t\tif ( _deathSub.IsValid() )\n\t\t{\n\t\t\t_deathSub.Style.Display = dead ? DisplayMode.Flex : DisplayMode.None;\n\n\t\t\t// \u6B7B\u4EA1\u89C2\u6218\u63D0\u793A\uFF08v0.7.8.28\uFF09\uFF1A\u8DDF\u8C01\u770B\u6218\u5C40\u5199\u5728\u526F\u6807\u9898\u91CC\n\t\t\tvar spec = game.Camera?.SpectateName;\n\t\t\tvar text = string.IsNullOrEmpty( spec )\n\t\t\t\t? \u0022PRESS SPACE / CLICK TO RESPAWN\u0022\n\t\t\t\t: $\u0022SPECTATING {spec} \u2014 PRESS SPACE TO RESPAWN\u0022;\n\t\t\tif ( _deathSub.Text != text ) _deathSub.Text = text;\n\t\t}\n\t}\n}"}]}