upd
This commit is contained in:
138
Code/UI/Chat.razor
Normal file
138
Code/UI/Chat.razor
Normal file
@@ -0,0 +1,138 @@
|
||||
@using System;
|
||||
@using Sandbox.UI;
|
||||
@namespace Sandbox
|
||||
@inherits PanelComponent
|
||||
@implements Component.INetworkListener
|
||||
<root>
|
||||
|
||||
<div class="output">
|
||||
@foreach (var entry in Entries)
|
||||
{
|
||||
<div class="chat_entry">
|
||||
@if (entry.steamid > 0)
|
||||
{
|
||||
<div class="avatar" style="background-image: url( avatar:@entry.steamid )"></div>
|
||||
}
|
||||
<div class="author">@entry.author</div>
|
||||
<div class="message">@entry.message</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="input">
|
||||
<TextEntry @ref="InputBox" onsubmit="@ChatFinished"></TextEntry>
|
||||
</div>
|
||||
|
||||
</root>
|
||||
|
||||
@code
|
||||
{
|
||||
|
||||
public static Chat Instance;
|
||||
public Chat() => Instance = this;
|
||||
public static bool IsActive => Instance.InputBox.HasFocus;
|
||||
|
||||
public static void Open()
|
||||
{
|
||||
Instance.InputBox.Focus();
|
||||
}
|
||||
|
||||
public static void AddText(string text)
|
||||
{
|
||||
Instance.AddTextInternal(text);
|
||||
}
|
||||
|
||||
public event Action<string> OnChat;
|
||||
public TextEntry InputBox;
|
||||
|
||||
public record Entry(ulong steamid, string author, string message, RealTimeSince timeSinceAdded);
|
||||
List<Entry> Entries = new();
|
||||
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
if (InputBox is null)
|
||||
return;
|
||||
|
||||
Panel.AcceptsFocus = false;
|
||||
|
||||
if (Input.Pressed("chat"))
|
||||
Open();
|
||||
|
||||
if (Entries.RemoveAll(x => x.timeSinceAdded > 20.0f) > 0)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
if (InputBox.HasFocus && Input.EscapePressed)
|
||||
{
|
||||
Input.EscapePressed = false;
|
||||
ChatClosed();
|
||||
}
|
||||
|
||||
SetClass("open", InputBox.HasFocus);
|
||||
}
|
||||
|
||||
void ChatFinished()
|
||||
{
|
||||
var text = InputBox.Text;
|
||||
Mouse.Visibility = MouseVisibility.Auto;
|
||||
|
||||
OnChat?.Invoke(text);
|
||||
OnChat = null;
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
AddTextInternal(InputBox.Text);
|
||||
InputBox.Text = "";
|
||||
}
|
||||
|
||||
void ChatClosed()
|
||||
{
|
||||
var text = InputBox.Text;
|
||||
InputBox.Text = "";
|
||||
OnChat = null;
|
||||
}
|
||||
|
||||
[Rpc.Broadcast]
|
||||
public void AddTextInternal(string message)
|
||||
{
|
||||
message = message.Truncate(300);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return;
|
||||
|
||||
var author = Rpc.Caller.DisplayName;
|
||||
var steamid = Rpc.Caller.SteamId;
|
||||
|
||||
Log.Info($"{author}: {message}");
|
||||
|
||||
Entries.Add(new Entry(steamid, author, message, 0.0f));
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
[Rpc.Broadcast]
|
||||
void AddSystemText(string message)
|
||||
{
|
||||
message = message.Truncate(300);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return;
|
||||
|
||||
Entries.Add(new Entry(0, "ℹ️", message, 0.0f));
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
void Component.INetworkListener.OnConnected(Connection channel)
|
||||
{
|
||||
if (IsProxy) return;
|
||||
|
||||
AddSystemText($"{channel.DisplayName} has joined the game");
|
||||
}
|
||||
|
||||
void Component.INetworkListener.OnDisconnected(Connection channel)
|
||||
{
|
||||
if (IsProxy) return;
|
||||
|
||||
AddSystemText($"{channel.DisplayName} has left the game");
|
||||
}
|
||||
}
|
||||
70
Code/UI/Chat.razor.scss
Normal file
70
Code/UI/Chat.razor.scss
Normal file
@@ -0,0 +1,70 @@
|
||||
Chat {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 200px;
|
||||
bottom: 200px;
|
||||
width: 600px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
border-radius: 20px;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
font-size: 17px;
|
||||
font-family: Poppins;
|
||||
gap: 10px;
|
||||
|
||||
.output {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
gap: 5px;
|
||||
|
||||
.chat_entry {
|
||||
padding: 2px;
|
||||
gap: 10px;
|
||||
text-shadow: 2px 2px 2px #000a;
|
||||
|
||||
.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: 4px;
|
||||
aspect-ratio: 1;
|
||||
min-width: 32px;
|
||||
max-width: 32px;
|
||||
}
|
||||
|
||||
.author {
|
||||
color: #2d95ce;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
color: white;
|
||||
|
||||
.textentry {
|
||||
align-items: flex-start;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
.input {
|
||||
border-radius: 8px;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 8px;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Code/UI/GUI.razor
Normal file
25
Code/UI/GUI.razor
Normal file
@@ -0,0 +1,25 @@
|
||||
@using Sandbox
|
||||
@using Sandbox.UI
|
||||
@inherits PanelComponent
|
||||
@namespace Sandbox
|
||||
|
||||
<root>
|
||||
<div class="crosshair"></div>
|
||||
</root>
|
||||
|
||||
<style>
|
||||
.crosshair {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
}
|
||||
95
Code/UI/Scoreboard.razor
Normal file
95
Code/UI/Scoreboard.razor
Normal file
@@ -0,0 +1,95 @@
|
||||
@using Sandbox;
|
||||
@using Sandbox.UI;
|
||||
@inherits PanelComponent
|
||||
|
||||
<root class="@(Visible ? "" : "hidden")">
|
||||
@* <div class="decoration top-left"></div> *@
|
||||
@* <div class="decoration top-right"></div> *@
|
||||
@* <div class="decoration bottom-left"></div> *@
|
||||
@* <div class="decoration bottom-right"></div> *@
|
||||
|
||||
<label class="title">Players</label>
|
||||
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<label class="column nick">Player</label>
|
||||
<label class="column status">Status</label>
|
||||
<label class="column ping">Ping</label>
|
||||
</div>
|
||||
|
||||
<div class="player-list">
|
||||
@if (Dedugan.All is not null)
|
||||
{
|
||||
foreach (var ded in Dedugan.All)
|
||||
{
|
||||
<div class="player" onclick="@(() => OpenProfile(ded.Connection))">
|
||||
<div class="avatar">
|
||||
<img src="avatar:@ded.SteamID"/>
|
||||
</div>
|
||||
<label class="column nick">@ded.Connection.DisplayName</label>
|
||||
<label class="column status">@GetPlayerStatus(ded.Connection)</label>
|
||||
<label class="column ping">@ded.Connection.Ping</label>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</root>
|
||||
|
||||
@code
|
||||
{
|
||||
public static bool Visible => Input.Down("Score");
|
||||
private NetworkManager _networkManager;
|
||||
|
||||
private NetworkManager NetworkManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_networkManager == null || !_networkManager.IsValid)
|
||||
{
|
||||
_networkManager = Scene.Directory.FindByName("Network Manager")
|
||||
.FirstOrDefault()?
|
||||
.GetComponent<NetworkManager>();
|
||||
}
|
||||
return _networkManager;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnabled()
|
||||
{
|
||||
// Кэшируем NetworkManager при включении компонента
|
||||
_networkManager = Scene.Directory.FindByName("Network Manager")
|
||||
.FirstOrDefault()?
|
||||
.GetComponent<NetworkManager>();
|
||||
}
|
||||
|
||||
private string GetPlayerStatus(Connection conn)
|
||||
{
|
||||
var playerObj = Dedugan.GetByID( conn.Id ).GameObject;
|
||||
return playerObj?.IsValid == true ? "In Game" : "Connecting";
|
||||
}
|
||||
|
||||
private void OpenProfile(Connection connection)
|
||||
{
|
||||
Log.Info($"Opening profile: {connection.SteamId}");
|
||||
Game.Overlay.ShowPlayer(connection.SteamId);
|
||||
}
|
||||
|
||||
protected override int BuildHash()
|
||||
{
|
||||
if (!Visible || Dedugan.All == null)
|
||||
return -1;
|
||||
|
||||
var hash = new System.HashCode();
|
||||
hash.Add(Visible);
|
||||
|
||||
foreach (var ded in Dedugan.All)
|
||||
{
|
||||
hash.Add(ded.Id);
|
||||
hash.Add(ded.Connection.Ping);
|
||||
hash.Add(ded.Name);
|
||||
}
|
||||
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
}
|
||||
191
Code/UI/Scoreboard.razor.scss
Normal file
191
Code/UI/Scoreboard.razor.scss
Normal file
@@ -0,0 +1,191 @@
|
||||
Scoreboard {
|
||||
background: linear-gradient(135deg, #0a1a2b 0%, #08111f 100%);
|
||||
border: 3px solid #2a3d54;
|
||||
border-radius: 16px;
|
||||
//box-shadow:
|
||||
// 0 0 15px rgba(0, 150, 255, 0.2),
|
||||
// inset 0 0 10px rgba(0, 100, 200, 0.1);
|
||||
font-family: 'Orbitron', 'Poppins', sans-serif;
|
||||
position: absolute;
|
||||
width: 60%;
|
||||
height: 70vh;
|
||||
top: 15vh;
|
||||
left: 20%;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.decoration {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 2px solid rgba(0, 180, 255, 0.25);
|
||||
opacity: 0.8;
|
||||
box-shadow: 0 0 8px rgba(100, 200, 255, 0.1);
|
||||
|
||||
&.top-left {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
border-radius: 12px 0 0 0;
|
||||
}
|
||||
|
||||
&.top-right {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
border-left: none;
|
||||
border-bottom: none;
|
||||
border-radius: 0 12px 0 0;
|
||||
}
|
||||
|
||||
&.bottom-left {
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-radius: 0 0 0 12px;
|
||||
}
|
||||
|
||||
&.bottom-right {
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
border-radius: 0 0 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 42px;
|
||||
color: #a0e0ff;
|
||||
text-align: center;
|
||||
//text-shadow:
|
||||
// 0 0 10px rgba(100, 200, 255, 0.7),
|
||||
// 0 0 20px rgba(80, 180, 255, 0.4);
|
||||
letter-spacing: 4px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgba(10, 25, 40, 0.6);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #253a50;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
//box-shadow: inset 0 0 20px rgba(0, 30, 60, 0.5);
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 15px 20px 15px 85px;
|
||||
background: linear-gradient(90deg, #0f2a42 0%, #0a1d30 100%);
|
||||
border-bottom: 2px solid #1e3a5c;
|
||||
text-shadow: 0 0 5px rgba(100, 200, 255, 0.5);
|
||||
|
||||
.column {
|
||||
font-size: 18px;
|
||||
color: #6eb4ff;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
|
||||
&.nick {
|
||||
flex: 3;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&.status {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.ping {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.player-list {
|
||||
pointer-events: all;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
.player {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 25px;
|
||||
transition: all 0.2s ease;
|
||||
width: 100%;
|
||||
//background: rgba(15, 30, 50, 0.4);
|
||||
border-bottom: 1px solid rgba(40, 80, 120, 0.2);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
//background: rgba(25, 60, 90, 0.4);
|
||||
box-shadow: 0 0 15px rgba(0, 150, 255, 0.1);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-right: 20px;
|
||||
border: 2px solid #2a4a6b;
|
||||
box-shadow: 0 0 8px rgba(100, 180, 255, 0.2);
|
||||
background: #0c1a2a;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
font-size: 17px;
|
||||
color: #c0e8ff;
|
||||
text-shadow: 0 0 5px rgba(100, 180, 255, 0.3);
|
||||
|
||||
&.nick {
|
||||
flex: 3;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&.status {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #6ecbff;
|
||||
}
|
||||
|
||||
&.ping {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #88d6ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user