Chat log viewer
This commit is contained in:
parent
2fdf763481
commit
3fd9bbeae7
@ -30,6 +30,8 @@ namespace spades {
|
||||
spades::ui::UIManager@ manager;
|
||||
spades::ui::UIElement@ activeUI;
|
||||
|
||||
ChatLogWindow@ chatLogWindow;
|
||||
|
||||
ClientMenu@ clientMenu;
|
||||
|
||||
bool shouldExit = false;
|
||||
@ -48,6 +50,7 @@ namespace spades {
|
||||
@clientMenu = ClientMenu(this);
|
||||
clientMenu.Bounds = manager.RootElement.Bounds;
|
||||
|
||||
@chatLogWindow = ChatLogWindow(this);
|
||||
}
|
||||
|
||||
void MouseEvent(float x, float y) {
|
||||
@ -143,6 +146,10 @@ namespace spades {
|
||||
void CloseUI() {
|
||||
@ActiveUI = null;
|
||||
}
|
||||
|
||||
void RecordChatLog(string text, Vector4 color) {
|
||||
chatLogWindow.Record(text, color);
|
||||
}
|
||||
}
|
||||
|
||||
class ClientMenu: spades::ui::UIElement {
|
||||
@ -154,7 +161,7 @@ namespace spades {
|
||||
@this.ui = ui;
|
||||
@this.helper = ui.helper;
|
||||
|
||||
float winW = 180.f, winH = 94.f;
|
||||
float winW = 180.f, winH = 32.f * 4.f - 2.f;
|
||||
float winX = (Manager.Renderer.ScreenWidth - winW) * 0.5f;
|
||||
float winY = (Manager.Renderer.ScreenHeight - winH) * 0.5f;
|
||||
|
||||
@ -181,15 +188,22 @@ namespace spades {
|
||||
}
|
||||
{
|
||||
spades::ui::Button button(Manager);
|
||||
button.Caption = "Setup";
|
||||
button.Caption = "Chat Log";
|
||||
button.Bounds = AABB2(winX, winY + 32.f, winW, 30.f);
|
||||
button.Activated = EventHandler(this.OnChatLog);
|
||||
AddChild(button);
|
||||
}
|
||||
{
|
||||
spades::ui::Button button(Manager);
|
||||
button.Caption = "Setup";
|
||||
button.Bounds = AABB2(winX, winY + 64.f, winW, 30.f);
|
||||
button.Activated = EventHandler(this.OnSetup);
|
||||
AddChild(button);
|
||||
}
|
||||
{
|
||||
spades::ui::Button button(Manager);
|
||||
button.Caption = "Disconnect";
|
||||
button.Bounds = AABB2(winX, winY + 64.f, winW, 30.f);
|
||||
button.Bounds = AABB2(winX, winY + 96.f, winW, 30.f);
|
||||
button.Activated = EventHandler(this.OnDisconnect);
|
||||
AddChild(button);
|
||||
}
|
||||
@ -205,6 +219,10 @@ namespace spades {
|
||||
PreferenceView al(this, opt);
|
||||
al.Run();
|
||||
}
|
||||
private void OnChatLog(spades::ui::UIElement@ sender) {
|
||||
@ui.ActiveUI = @ui.chatLogWindow;
|
||||
ui.chatLogWindow.ScrollToEnd();
|
||||
}
|
||||
private void OnDisconnect(spades::ui::UIElement@ sender) {
|
||||
ui.shouldExit = true;
|
||||
}
|
||||
@ -298,32 +316,41 @@ namespace spades {
|
||||
sayButton.Enable = field.Text.length > 0;
|
||||
}
|
||||
|
||||
bool IsTeamChat {
|
||||
get final { return isTeamChat; }
|
||||
set {
|
||||
if(isTeamChat == value) return;
|
||||
isTeamChat = value;
|
||||
teamButton.Toggled = isTeamChat;
|
||||
globalButton.Toggled = not isTeamChat;
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSetGlobal(spades::ui::UIElement@ sender) {
|
||||
teamButton.Toggled = false;
|
||||
globalButton.Toggled = true;
|
||||
isTeamChat = false;
|
||||
UpdateState();
|
||||
IsTeamChat = false;
|
||||
}
|
||||
private void OnSetTeam(spades::ui::UIElement@ sender) {
|
||||
teamButton.Toggled = true;
|
||||
globalButton.Toggled = false;
|
||||
isTeamChat = true;
|
||||
UpdateState();
|
||||
IsTeamChat = true;
|
||||
}
|
||||
|
||||
private void OnFieldChanged(spades::ui::UIElement@ sender) {
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
private void OnCancel(spades::ui::UIElement@ sender) {
|
||||
private void Close() {
|
||||
@ui.ActiveUI = null;
|
||||
}
|
||||
|
||||
private void OnCancel(spades::ui::UIElement@ sender) {
|
||||
Close();
|
||||
}
|
||||
private void OnSay(spades::ui::UIElement@ sender) {
|
||||
if(isTeamChat)
|
||||
ui.helper.SayTeam(field.Text);
|
||||
else
|
||||
ui.helper.SayGlobal(field.Text);
|
||||
@ui.ActiveUI = null;
|
||||
Close();
|
||||
}
|
||||
|
||||
void HotKey(string key) {
|
||||
@ -341,6 +368,186 @@ namespace spades {
|
||||
}
|
||||
}
|
||||
|
||||
class ChatLogSayWindow: ClientChatWindow {
|
||||
ChatLogWindow@ owner;
|
||||
|
||||
ChatLogSayWindow(ChatLogWindow@ own, bool isTeamChat) {
|
||||
super(own.ui, isTeamChat);
|
||||
@owner = own;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
owner.SayWindowClosed();
|
||||
@this.Parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
class ChatLogWindow: spades::ui::UIElement {
|
||||
|
||||
float contentsTop, contentsHeight;
|
||||
|
||||
ClientUI@ ui;
|
||||
private ClientUIHelper@ helper;
|
||||
|
||||
private spades::ui::TextViewer@ viewer;
|
||||
ChatLogSayWindow@ sayWindow;
|
||||
|
||||
private spades::ui::UIElement@ sayButton1;
|
||||
private spades::ui::UIElement@ sayButton2;
|
||||
|
||||
ChatLogWindow(ClientUI@ ui) {
|
||||
super(ui.manager);
|
||||
@this.ui = ui;
|
||||
@this.helper = ui.helper;
|
||||
|
||||
@Font = Manager.RootElement.Font;
|
||||
this.Bounds = Manager.RootElement.Bounds;
|
||||
|
||||
float contentsWidth = 700.f;
|
||||
float contentsLeft = (Manager.Renderer.ScreenWidth - contentsWidth) * 0.5f;
|
||||
contentsHeight = Manager.Renderer.ScreenHeight - 200.f;
|
||||
contentsTop = (Manager.Renderer.ScreenHeight - contentsHeight - 106.f) * 0.5f;
|
||||
{
|
||||
spades::ui::Label label(Manager);
|
||||
label.BackgroundColor = Vector4(0, 0, 0, 0.4f);
|
||||
label.Bounds = Bounds;
|
||||
AddChild(label);
|
||||
}
|
||||
{
|
||||
spades::ui::Label label(Manager);
|
||||
label.BackgroundColor = Vector4(0, 0, 0, 0.8f);
|
||||
label.Bounds = AABB2(0.f, contentsTop - 13.f, Size.x, contentsHeight + 27.f);
|
||||
AddChild(label);
|
||||
}
|
||||
{
|
||||
spades::ui::Button button(Manager);
|
||||
button.Caption = "Close";
|
||||
button.Bounds = AABB2(
|
||||
contentsLeft + contentsWidth - 150.f,
|
||||
contentsTop + contentsHeight - 30.f
|
||||
, 150.f, 30.f);
|
||||
button.Activated = spades::ui::EventHandler(this.OnOkPressed);
|
||||
AddChild(button);
|
||||
}
|
||||
{
|
||||
spades::ui::Button button(Manager);
|
||||
button.Caption = "Say Global";
|
||||
button.Bounds = AABB2(
|
||||
contentsLeft,
|
||||
contentsTop + contentsHeight - 30.f
|
||||
, 150.f, 30.f);
|
||||
button.Activated = spades::ui::EventHandler(this.OnGlobalChat);
|
||||
AddChild(button);
|
||||
@this.sayButton1 = button;
|
||||
}
|
||||
{
|
||||
spades::ui::Button button(Manager);
|
||||
button.Caption = "Say Team";
|
||||
button.Bounds = AABB2(
|
||||
contentsLeft + 155.f,
|
||||
contentsTop + contentsHeight - 30.f
|
||||
, 150.f, 30.f);
|
||||
button.Activated = spades::ui::EventHandler(this.OnTeamChat);
|
||||
AddChild(button);
|
||||
@this.sayButton2 = button;
|
||||
}
|
||||
{
|
||||
spades::ui::TextViewer viewer(Manager);
|
||||
AddChild(viewer);
|
||||
viewer.Bounds = AABB2(contentsLeft, contentsTop, contentsWidth, contentsHeight - 40.f);
|
||||
@this.viewer = viewer;
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollToEnd() {
|
||||
viewer.Layout();
|
||||
viewer.ScrollToEnd();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
@ui.ActiveUI = null;
|
||||
}
|
||||
|
||||
void SayWindowClosed() {
|
||||
@sayWindow = null;
|
||||
sayButton1.Enable = true;
|
||||
sayButton2.Enable = true;
|
||||
}
|
||||
|
||||
private void OnOkPressed(spades::ui::UIElement@ sender) {
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnTeamChat(spades::ui::UIElement@ sender) {
|
||||
if(sayWindow !is null) {
|
||||
sayWindow.IsTeamChat = true;
|
||||
return;
|
||||
}
|
||||
sayButton1.Enable = false;
|
||||
sayButton2.Enable = false;
|
||||
ChatLogSayWindow wnd(this, true);
|
||||
AddChild(wnd);
|
||||
wnd.Bounds = this.Bounds;
|
||||
@this.sayWindow = wnd;
|
||||
@manager.ActiveElement = wnd.field;
|
||||
}
|
||||
|
||||
private void OnGlobalChat(spades::ui::UIElement@ sender) {
|
||||
if(sayWindow !is null) {
|
||||
sayWindow.IsTeamChat = false;
|
||||
return;
|
||||
}
|
||||
sayButton1.Enable = false;
|
||||
sayButton2.Enable = false;
|
||||
ChatLogSayWindow wnd(this, false);
|
||||
AddChild(wnd);
|
||||
wnd.Bounds = this.Bounds;
|
||||
@this.sayWindow = wnd;
|
||||
@manager.ActiveElement = wnd.field;
|
||||
}
|
||||
|
||||
void HotKey(string key) {
|
||||
if(sayWindow !is null) {
|
||||
UIElement::HotKey(key);
|
||||
return;
|
||||
}
|
||||
if(IsEnabled and (key == "Escape")) {
|
||||
Close();
|
||||
} else if(IsEnabled and (key == "y")) {
|
||||
OnTeamChat(this);
|
||||
} else if(IsEnabled and (key == "t")) {
|
||||
OnGlobalChat(this);
|
||||
} else {
|
||||
UIElement::HotKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
void Record(string text, Vector4 color) {
|
||||
viewer.AddLine(text, this.IsVisible, color);
|
||||
}
|
||||
|
||||
void Render() {
|
||||
Vector2 pos = ScreenPosition;
|
||||
Vector2 size = Size;
|
||||
Renderer@ r = Manager.Renderer;
|
||||
Image@ img = r.RegisterImage("Gfx/White.tga");
|
||||
|
||||
r.ColorNP = Vector4(1, 1, 1, 0.08f);
|
||||
r.DrawImage(img,
|
||||
AABB2(pos.x, pos.y + contentsTop - 15.f, size.x, 1.f));
|
||||
r.DrawImage(img,
|
||||
AABB2(pos.x, pos.y + contentsTop + contentsHeight + 15.f, size.x, 1.f));
|
||||
r.ColorNP = Vector4(1, 1, 1, 0.2f);
|
||||
r.DrawImage(img,
|
||||
AABB2(pos.x, pos.y + contentsTop - 14.f, size.x, 1.f));
|
||||
r.DrawImage(img,
|
||||
AABB2(pos.x, pos.y + contentsTop + contentsHeight + 14.f, size.x, 1.f));
|
||||
|
||||
UIElement::Render();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ClientUI@ CreateClientUI(Renderer@ renderer, AudioDevice@ audioDevice,
|
||||
Font@ font, ClientUIHelper@ helper) {
|
||||
return ClientUI(renderer, audioDevice, font, helper);
|
||||
|
@ -227,6 +227,9 @@ namespace spades {
|
||||
}else if(Hover) {
|
||||
color = Vector4(0.4f, 0.4f, 0.4f, 0.7f);
|
||||
}
|
||||
if(!IsEnabled) {
|
||||
color.w *= 0.5f;
|
||||
}
|
||||
renderer.ColorNP = color;
|
||||
|
||||
DrawSliceImage(renderer, image, pos.x, pos.y, size.x, size.y, 12.f);
|
||||
@ -239,8 +242,13 @@ namespace spades {
|
||||
size -= Vector2(16.f, 16.f);
|
||||
txtPos = pos + (size - txtSize) * Alignment;
|
||||
|
||||
font.DrawShadow(text, txtPos, 1.f,
|
||||
Vector4(1.f, 1.f, 1.f, 1.f), Vector4(0.f, 0.f, 0.f, 0.4f));
|
||||
if(IsEnabled){
|
||||
font.DrawShadow(text, txtPos, 1.f,
|
||||
Vector4(1.f, 1.f, 1.f, 1.f), Vector4(0.f, 0.f, 0.f, 0.4f));
|
||||
}else{
|
||||
font.DrawShadow(text, txtPos, 1.f,
|
||||
Vector4(1.f, 1.f, 1.f, 0.5f), Vector4(0.f, 0.f, 0.f, 0.1f));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1367,24 +1375,31 @@ namespace spades {
|
||||
void ScrollToTop() {
|
||||
scrollBar.ScrollTo(0.0);
|
||||
}
|
||||
|
||||
void ScrollToEnd() {
|
||||
scrollBar.ScrollTo(scrollBar.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
class TextViewerModel: ListViewModel {
|
||||
UIManager@ manager;
|
||||
string[]@ lines = array<string>();
|
||||
Vector4[]@ colors = array<spades::Vector4>();
|
||||
Font@ font;
|
||||
float width;
|
||||
private void AddLine(string text) {
|
||||
void AddLine(string text, Vector4 color) {
|
||||
int startPos = 0;
|
||||
int minEnd = 1, maxEnd = text.length;
|
||||
if(font.Measure(text).x <= width) {
|
||||
lines.insertLast(text);
|
||||
colors.insertLast(color);
|
||||
return;
|
||||
}
|
||||
// find line-break point by binary search (O(n log n))
|
||||
while(startPos < int(text.length)) {
|
||||
if(minEnd >= maxEnd) {
|
||||
lines.insertLast(text.substr(startPos, maxEnd - startPos));
|
||||
colors.insertLast(color);
|
||||
startPos = maxEnd;
|
||||
minEnd = startPos + 1;
|
||||
maxEnd = text.length;
|
||||
@ -1404,12 +1419,13 @@ namespace spades {
|
||||
this.width = width;
|
||||
string[]@ lines = text.split("\n");
|
||||
for(uint i = 0; i < lines.length; i++)
|
||||
AddLine(lines[i]);
|
||||
AddLine(lines[i], Vector4(1.f, 1.f, 1.f, 1.f));
|
||||
}
|
||||
int NumRows { get { return int(lines.length); } }
|
||||
UIElement@ CreateElement(int row) {
|
||||
Label i(manager);
|
||||
i.Text = lines[row];
|
||||
i.TextColor = colors[row];
|
||||
return i;
|
||||
}
|
||||
void RecycleElement(UIElement@ elem) {}
|
||||
@ -1440,6 +1456,7 @@ namespace spades {
|
||||
|
||||
class TextViewer: ListViewBase {
|
||||
private string text;
|
||||
private TextViewerModel@ textmodel;
|
||||
|
||||
TextViewer(UIManager@ manager) {
|
||||
super(manager);
|
||||
@ -1450,7 +1467,25 @@ namespace spades {
|
||||
get final { return text; }
|
||||
set {
|
||||
text = value;
|
||||
@Model = TextViewerModel(Manager, text, Font, ItemWidth);
|
||||
@textmodel = TextViewerModel(Manager, text, Font, ItemWidth);
|
||||
@Model = textmodel;
|
||||
}
|
||||
}
|
||||
|
||||
void AddLine(string line, bool autoscroll = false, Vector4 color = Vector4(1.f, 1.f, 1.f, 1.f)) {
|
||||
if(textmodel is null) {
|
||||
this.Text = "";
|
||||
}
|
||||
if(autoscroll){
|
||||
this.Layout();
|
||||
if(this.scrollBar.Value < this.scrollBar.MaxValue) {
|
||||
autoscroll = false;
|
||||
}
|
||||
}
|
||||
textmodel.AddLine(line, color);
|
||||
if(autoscroll) {
|
||||
this.Layout();
|
||||
this.ScrollToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2691,13 +2691,32 @@ namespace spades {
|
||||
void Client::PlayerSentChatMessage(spades::client::Player *p,
|
||||
bool global,
|
||||
const std::string &msg){
|
||||
std::string s;
|
||||
if(global)
|
||||
s = "[Global] ";
|
||||
s += ChatWindow::TeamColorMessage(p->GetName(), p->GetTeamId());
|
||||
s += ": ";
|
||||
s += msg;
|
||||
chatWindow->AddMessage(s);
|
||||
{
|
||||
std::string s;
|
||||
if(global)
|
||||
s = "[Global] ";
|
||||
s += ChatWindow::TeamColorMessage(p->GetName(), p->GetTeamId());
|
||||
s += ": ";
|
||||
s += msg;
|
||||
chatWindow->AddMessage(s);
|
||||
}
|
||||
{
|
||||
std::string s;
|
||||
if(global)
|
||||
s = "[Global] ";
|
||||
s += p->GetName();
|
||||
s += ": ";
|
||||
s += msg;
|
||||
|
||||
auto col = p->GetTeamId() < 2 ?
|
||||
world->GetTeam(p->GetTeamId()).color :
|
||||
IntVector3::Make(255, 255, 255);
|
||||
|
||||
scriptedUI->RecordChatLog(s,
|
||||
MakeVector4(col.x / 255.f, col.y / 255.f,
|
||||
col.z / 255.f, 0.8f));
|
||||
|
||||
}
|
||||
if(global)
|
||||
NetLog("[Global] %s (%s): %s",
|
||||
p->GetName().c_str(),
|
||||
@ -2709,6 +2728,7 @@ namespace spades {
|
||||
world->GetTeam(p->GetTeamId()).name.c_str(),
|
||||
msg.c_str());
|
||||
|
||||
|
||||
if((!IsMuted()) && (int)cg_chatBeep) {
|
||||
Handle<IAudioChunk> chunk = audioDevice->RegisterSound("Sounds/Feedback/Chat.wav");
|
||||
audioDevice->PlayLocal(chunk, AudioParam());
|
||||
@ -2718,9 +2738,7 @@ namespace spades {
|
||||
void Client::ServerSentMessage(const std::string &msg) {
|
||||
chatWindow->AddMessage(msg);
|
||||
NetLog("%s", msg.c_str());
|
||||
if(msg.find(playerName) != std::string::npos){
|
||||
printf("Mention: %s\n", msg.c_str());
|
||||
}
|
||||
scriptedUI->RecordChatLog(msg, Vector4::Make(1.f, 1.f, 1.f, 0.8f));
|
||||
}
|
||||
|
||||
#pragma mark - Follow / Spectate
|
||||
@ -3359,20 +3377,52 @@ namespace spades {
|
||||
}
|
||||
|
||||
void Client::PlayerLeaving(spades::client::Player *p) {
|
||||
std::string msg;
|
||||
msg = "Player " + chatWindow->TeamColorMessage(p->GetName(), p->GetTeamId());
|
||||
msg += " has left";
|
||||
chatWindow->AddMessage(msg);
|
||||
{
|
||||
std::string msg;
|
||||
msg = "Player " + chatWindow->TeamColorMessage(p->GetName(), p->GetTeamId());
|
||||
msg += " has left";
|
||||
chatWindow->AddMessage(msg);
|
||||
}
|
||||
{
|
||||
std::string msg;
|
||||
msg = "Player " + p->GetName();
|
||||
msg += " has left";
|
||||
|
||||
auto col = p->GetTeamId() < 2 ?
|
||||
world->GetTeam(p->GetTeamId()).color :
|
||||
IntVector3::Make(255, 255, 255);
|
||||
|
||||
scriptedUI->RecordChatLog(msg,
|
||||
MakeVector4(col.x / 255.f, col.y / 255.f,
|
||||
col.z / 255.f, 0.8f));
|
||||
}
|
||||
}
|
||||
|
||||
void Client::PlayerJoinedTeam(spades::client::Player *p) {
|
||||
std::string msg;
|
||||
msg = p->GetName();
|
||||
msg += " joined ";
|
||||
msg += chatWindow->TeamColorMessage(world->GetTeam(p->GetTeamId()).name,
|
||||
p->GetTeamId());
|
||||
msg += " team";
|
||||
chatWindow->AddMessage(msg);
|
||||
{
|
||||
std::string msg;
|
||||
msg = p->GetName();
|
||||
msg += " joined ";
|
||||
msg += chatWindow->TeamColorMessage(world->GetTeam(p->GetTeamId()).name,
|
||||
p->GetTeamId());
|
||||
msg += " team";
|
||||
chatWindow->AddMessage(msg);
|
||||
}
|
||||
{
|
||||
std::string msg;
|
||||
msg = p->GetName();
|
||||
msg += " joined ";
|
||||
msg += world->GetTeam(p->GetTeamId()).name;
|
||||
msg += " team";
|
||||
|
||||
auto col = p->GetTeamId() < 2 ?
|
||||
world->GetTeam(p->GetTeamId()).color :
|
||||
IntVector3::Make(255, 255, 255);
|
||||
|
||||
scriptedUI->RecordChatLog(msg,
|
||||
MakeVector4(col.x / 255.f, col.y / 255.f,
|
||||
col.z / 255.f, 0.8f));
|
||||
}
|
||||
}
|
||||
|
||||
void Client::GrenadeDestroyedBlock(spades::IntVector3 blk){
|
||||
@ -3405,6 +3455,8 @@ namespace spades {
|
||||
NetLog("%s", msg.c_str());
|
||||
centerMessageView->AddMessage(msg);
|
||||
|
||||
scriptedUI->RecordChatLog(msg, MakeVector4(1.f, 1.f, 1.f, 0.8f));
|
||||
|
||||
if(world->GetLocalPlayer()){
|
||||
if(teamId == world->GetLocalPlayer()->GetTeamId()){
|
||||
Handle<IAudioChunk> chunk = audioDevice->RegisterSound("Sounds/Feedback/Win.wav");
|
||||
|
@ -212,6 +212,21 @@ namespace spades {
|
||||
c.ExecuteChecked();
|
||||
}
|
||||
|
||||
void ClientUI::RecordChatLog(const std::string &msg,
|
||||
Vector4 color) {
|
||||
SPADES_MARK_FUNCTION();
|
||||
if(!ui){
|
||||
return;
|
||||
}
|
||||
static ScriptFunction func("ClientUI", "void RecordChatLog(string, Vector4)");
|
||||
ScriptContextHandle c = func.Prepare();
|
||||
std::string k = msg;
|
||||
c->SetObject(&*ui);
|
||||
c->SetArgObject(0, reinterpret_cast<void*>(&k));
|
||||
c->SetArgObject(1, reinterpret_cast<void*>(&color));
|
||||
c.ExecuteChecked();
|
||||
}
|
||||
|
||||
void ClientUI::EnterClientMenu() {
|
||||
SPADES_MARK_FUNCTION();
|
||||
if(!ui){
|
||||
|
@ -73,6 +73,8 @@ namespace spades {
|
||||
|
||||
bool NeedsInput();
|
||||
|
||||
void RecordChatLog(const std::string&, Vector4 color);
|
||||
|
||||
void EnterClientMenu();
|
||||
void EnterGlobalChatWindow();
|
||||
void EnterTeamChatWindow();
|
||||
|
Loading…
x
Reference in New Issue
Block a user