This commit is contained in:
Oscar
2025-06-26 01:56:08 +03:00
parent 5c9be94aba
commit 8b61aa1d7b
89 changed files with 7072 additions and 333 deletions

View File

@@ -1,138 +1,156 @@
@using System;
@using Sandbox.UI;
@using Sandbox
@using System
@using Sandbox.UI
@namespace Sandbox
@inherits PanelComponent
@implements Component.INetworkListener
<root>
<root>
<div class="output">
@foreach (var entry in Entries)
@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>
<ChatEntry Type="@entry.Type"
SteamID="@entry.SteamID"
Author="@entry.Author"
Message="@entry.Message"
IsTemporary="@entry.IsTemporary"/>
}
</div>
<div class="input">
<TextEntry @ref="InputBox" onsubmit="@ChatFinished"></TextEntry>
<div class="input-container">
<TextEntry @ref=" InputBox" onsubmit="@ChatFinished"/>
</div>
</root>
@code
{
@code {
public static Chat Instance;
public Chat() => Instance = this;
public static bool IsActive => Instance.InputBox.HasFocus;
public static bool IsActive = false;
public static void Open()
public enum MessageType
{
Instance.InputBox.Focus();
Player,
System,
Admin,
Notification
}
public static void AddText(string text)
public record Entry
{
Instance.AddTextInternal(text);
public Entry( Chat.MessageType type, ulong steamID, string author, string message, RealTimeSince timeSinceAdded, bool isTemporary )
{
Type = type;
SteamID = steamID;
Author = author;
Message = message;
TimeSinceAdded = timeSinceAdded;
IsTemporary = isTemporary;
}
public Chat.MessageType Type { get; internal set; }
public ulong SteamID { get; internal set; }
public string Author { get; internal set; }
public string Message { get; internal set; }
public RealTimeSince TimeSinceAdded { get; internal set; }
public bool IsTemporary { get; internal set; }
}
public event Action<string> OnChat;
public TextEntry InputBox;
public record Entry(ulong steamid, string author, string message, RealTimeSince timeSinceAdded);
List<Entry> Entries = new();
public TextEntry InputBox;
public event Action<string> OnChat;
protected override void OnUpdate()
{
if (InputBox is null)
return;
if ( InputBox is null ) return;
Panel.AcceptsFocus = false;
if (Input.Pressed("chat"))
if ( Input.Pressed( "chat" ) )
Open();
if (Entries.RemoveAll(x => x.timeSinceAdded > 20.0f) > 0)
{
StateHasChanged();
}
if (InputBox.HasFocus && Input.EscapePressed)
if ( InputBox.HasFocus && Input.EscapePressed )
{
Input.EscapePressed = false;
ChatClosed();
}
SetClass("open", InputBox.HasFocus);
SetClass( "open", InputBox.HasFocus );
}
public static void Open()
{
IsActive = true;
Instance.InputBox?.Focus();
foreach ( var entry in Instance.Entries )
entry.IsTemporary = false;
Instance.StateHasChanged();
}
public static void AddMessage( MessageType type, string message, ulong steamId = 0, string author = "" ) => Instance?.AddMessageInternal( type, message, steamId, author );
[ConCmd( "say" )]
public static void Say( string message )
{
Instance?.AddTextInternal( message );
}
[Rpc.Broadcast]
public void AddTextInternal( string message )
{
if ( string.IsNullOrWhiteSpace( message ) ) return;
AddMessageInternal( MessageType.Player, message.Truncate( 300 ), Rpc.Caller.SteamId, Rpc.Caller.DisplayName );
}
void AddMessageInternal( MessageType type, string message, ulong steamId = 0, string author = "" )
{
Entries.Add( new Entry( type, steamId, author, message, 0, !IsActive ) );
StateHasChanged();
Log.Info( $"[{type}] {author}: {message}" );
}
void ScrollToBottom()
{
var panel = Panel.Children.First();
panel.ScrollVelocity = 0;
panel.ScrollOffset = 0;
}
void ChatFinished()
{
IsActive = false;
var text = InputBox.Text;
Mouse.Visibility = MouseVisibility.Auto;
OnChat?.Invoke(text);
OnChat = null;
if (string.IsNullOrWhiteSpace(text))
return;
AddTextInternal(InputBox.Text);
InputBox.Text = "";
if ( !string.IsNullOrWhiteSpace( text ) )
{
OnChat?.Invoke( text );
AddTextInternal( text );
}
ScrollToBottom();
OnChat = null;
}
void ChatClosed()
{
var text = InputBox.Text;
IsActive = false;
InputBox.Text = "";
ScrollToBottom();
OnChat = null;
}
[Rpc.Broadcast]
public void AddTextInternal(string message)
void Component.INetworkListener.OnConnected( Connection channel )
{
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();
if ( IsProxy ) return;
AddMessageInternal( MessageType.System, $"{channel.DisplayName} has joined the game" );
}
[Rpc.Broadcast]
void AddSystemText(string message)
void Component.INetworkListener.OnDisconnected( Connection channel )
{
message = message.Truncate(300);
if (string.IsNullOrWhiteSpace(message))
return;
Entries.Add(new Entry(0, "", message, 0.0f));
StateHasChanged();
if ( IsProxy ) return;
AddMessageInternal( MessageType.System, $"{channel.DisplayName} has left the game" );
}
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");
}
}

View File

@@ -4,67 +4,87 @@ Chat {
left: 200px;
bottom: 200px;
width: 600px;
justify-content: center;
align-items: center;
font-weight: bold;
border-radius: 20px;
justify-content: flex-end;
font-family: Poppins;
flex-direction: column;
align-items: stretch;
font-size: 17px;
font-family: Poppins;
gap: 10px;
.output {
box-shadow: none;
padding: 2px;
border-radius: 12px;
flex-grow: 1;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
gap: 5px;
min-height: 256px;
max-height: 256px;
overflow-y: scroll;
}
.chat_entry {
padding: 2px;
gap: 10px;
text-shadow: 2px 2px 2px #000a;
&:not(.open) {
.output {
ChatEntry {
opacity: 0;
.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;
&.temporary {
animation: temporaryMessageFadeOut 4s forwards;
}
}
}
}
.input {
.input-container {
color: white;
.textentry {
align-items: flex-start;
white-space: normal;
background-color: transparent;
border: none;
color: #fff;
font-size: 15px;
font-weight: 500;
width: 100%;
max-width: 100%;
transition: all 0.1s ease;
transform: translateY(10px);
border-radius: 12px;
overflow: hidden;
}
}
&.open {
.input {
border-radius: 8px;
background-color: rgba(0,0,0,0.2);
backdrop-filter: blur(10px);
padding: 8px;
pointer-events: all;
pointer-events: all;
.input-container {
transform: translateY(0);
.textentry {
background-color: rgba(30,30,40,0.8);
box-shadow: 0 4px 20px rgba(0,0,0,0.25);
backdrop-filter: blur(15px);
transform: translateY(0px);
}
}
.output {
background: linear-gradient( to top, rgba(10,10,20,0.9) 0%, rgba(10,10,20,0.6) 100% );
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
backdrop-filter: blur(15px);
}
}
}
@keyframes temporaryMessageFadeOut {
0%, 90% {
opacity: 1;
}
100% {
opacity: 0;
}
}

41
Code/UI/ChatEntry.razor Normal file
View File

@@ -0,0 +1,41 @@
@using Sandbox.UI
@namespace Sandbox
@inherits Panel
<root class="@( IsTemporary ? "temporary" : "" )">
<div class="meta">
@if ( Type == Chat.MessageType.Player && SteamID > 0 )
{
<div class="avatar" style="background-image: url( avatar:@SteamID )"></div>
}
else
{
<div class="type-icon">
@switch ( Type )
{
case Chat.MessageType.System:
<i>system_update_alt</i>
break;
case Chat.MessageType.Admin:
<i>verified_user</i>
break;
case Chat.MessageType.Notification:
<i>notifications</i>
break;
}
</div>
}
<label class="author">@Author</label>
</div>
<label class="message">@Message</label>
</root>
@code {
public Chat.MessageType Type { get; set; }
public ulong SteamID { get; set; }
public string Message { get; set; }
public string Author { get; set; }
public bool IsTemporary { get; set; }
}

View File

@@ -0,0 +1,90 @@
ChatEntry {
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.28);
backdrop-filter: blur(2px);
flex-shrink: 0;
overflow: hidden;
padding: 2px;
.meta {
flex-shrink: 0;
gap: 10px;
}
.avatar, .type-icon {
width: 28px;
height: 28px;
border-radius: 6px;
background-color: rgba(100, 150, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
overflow: hidden;
i {
font-family: 'Material Icons';
font-size: 16px;
}
}
.avatar {
background-size: cover;
background-position: center;
}
.author {
color: #a8e063;
font-size: 14px;
white-space: nowrap;
text-overflow: ellipsis;
flex-shrink: 0;
max-width: 150px;
overflow: hidden;
font-weight: bold;
}
.message {
color: #fff;
align-content: flex-end;
text-align: left;
justify-content: center;
font-size: 15px;
padding-left: 10px;
font-weight: bold;
}
&.type-system {
background-color: rgba(100, 200, 255, 0.12);
.author {
color: #6ec6ff;
}
.type-icon {
background-color: rgba(100, 200, 255, 0.2);
}
}
&.type-admin {
.author {
color: #ff9e80;
}
.type-icon {
background-color: rgba(255, 100, 100, 0.2);
}
}
&.type-notification {
background-color: rgba(255, 224, 130, 0.15);
.author {
color: #ffd54f;
}
.type-icon {
background-color: rgba(255, 200, 50, 0.25);
}
}
}

View File

@@ -1,13 +0,0 @@
using System.Collections.Generic;
namespace Sandbox;
public static class ChatHistory
{
public static List<Chat.Entry> Entries { get; private set; } = new();
public static void Add(ulong steamid, string author, string message)
{
Entries.Add(new Chat.Entry(steamid, author, message, 0.0f));
}
}

View File

@@ -13,20 +13,18 @@ public sealed class TeleportMazeButton : InteractionButton
public override bool Press( IPressable.Event e )
{
base.Press( e );
if ( Maze.IsValid() )
{
Maze.RpcRequestMaze();
}
else
{
// Log.Info( "pressed teleport maze" );
// return false;
}
return false;
// if ( Maze.IsValid() )
// {
// Maze.RpcRequestMaze();
// }
// else
// {
// Log.Info( "pressed teleport maze" );
// return false;
// }
//
// DoTeleport();
// return true;
DoTeleport();
return true;
}
private async void DoTeleport()