/* Copyright (c) 2013 yvt This file is part of OpenSpades. OpenSpades is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenSpades is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenSpades. If not, see . */ #include "ChatWindow.h" #include "IRenderer.h" #include "IFont.h" #include #include "Client.h" #include "World.h" #include #include #include #include DEFINE_SPADES_SETTING(cg_chatHeight, "30"); DEFINE_SPADES_SETTING(cg_killfeedHeight, "26"); namespace spades { namespace client{ ChatWindow::ChatWindow(Client *cli, IRenderer* rend, IFont *fnt, bool killfeed): client(cli), renderer(rend), font(fnt), killfeed(killfeed){ firstY = 0.f; mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/a-Rifle.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/b-SMG.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/c-Shotgun.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/d-Headshot.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/e-Melee.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/f-Grenade.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/g-Falling.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/h-Teamchange.png") ); mKillImages.push_back( renderer->RegisterImage("Gfx/Killfeed/i-Classchange.png") ); for( size_t n = 0; n < mKillImages.size(); ++n ) { if( mKillImages[n]->GetHeight() > GetLineHeight() ) { SPRaise( "Kill image (%d) height too big ", n ); } } } ChatWindow::~ChatWindow(){} float ChatWindow::GetWidth() { return renderer->ScreenWidth() / 2; } float ChatWindow::GetHeight() { float prop = killfeed ? (float)cg_killfeedHeight : (float)cg_chatHeight; return renderer->ScreenHeight() * prop * 0.01f; } float ChatWindow::GetLineHeight() { return 20.f; } static bool isWordChar(char c){ return isalnum(c) || c == '\''; } std::string ChatWindow::killImage( int type, int weapon ) { std::string tmp = "xx"; tmp[0] = 7; switch( type ) { case KillTypeWeapon: switch( weapon ) { case 0: case 1: case 2: tmp[1] = 'a' + weapon; break; default: return ""; } break; case KillTypeHeadshot: case KillTypeMelee: case KillTypeGrenade: case KillTypeFall: case KillTypeTeamChange: case KillTypeClassChange: tmp[1] = 'a' + 2 + type; break; default: return ""; } return tmp; } void ChatWindow::AddMessage(const std::string &msg){ SPADES_MARK_FUNCTION(); // get visible message string std::string str; float x = 0.f, maxW = GetWidth(); float lh = GetLineHeight(), h = lh; size_t wordStart = std::string::npos; size_t wordStartOutPos = 0; for(size_t i = 0; i < msg.size(); i++){ if(msg[i] > MsgColorMax && msg[i] != 13 && msg[i] != 10){ if(isWordChar(msg[i])){ if(wordStart == std::string::npos){ wordStart = msg.size(); wordStartOutPos = str.size(); } }else{ wordStart = std::string::npos; } float w = font->Measure(std::string(&msg[i],1)).x; if(x + w > maxW){ if(wordStart != std::string::npos && wordStart != str.size()){ // adding a part of word. // do word wrapping std::string s = msg.substr(wordStart, i - wordStart + 1); float nw = font->Measure(s).x; if(nw <= maxW){ // word wrap succeeds w = nw; x = w; h += lh; str.insert(wordStartOutPos, "\n"); goto didWordWrap; } } x = 0; h += lh; str += 13; } x += w; str += msg[i]; didWordWrap:; }else if(msg[i] == 13 || msg[i] == 10){ x = 0; h += lh; str += 13; }else{ str += msg[i]; } } entries.push_front( ChatEntry( msg, h, 0.f, 15.f ) ); firstY -= h; } std::string ChatWindow::ColoredMessage(const std::string & msg, char c){ SPADES_MARK_FUNCTION_DEBUG(); std::string s; s += c; s += msg; s += MsgColorRestore; return s; } std::string ChatWindow::TeamColorMessage(const std::string &msg, int team){ SPADES_MARK_FUNCTION_DEBUG(); switch( team ) { case 0: return ColoredMessage(msg, MsgColorTeam1); case 1: return ColoredMessage(msg, MsgColorTeam2); case 2: return ColoredMessage(msg, MsgColorTeam3); default: return msg; } } static Vector4 ConvertColor(IntVector3 v){ return MakeVector4((float)v.x / 255.f, (float)v.y / 255.f, (float)v.z / 255.f, 1.f); } Vector4 ChatWindow::GetColor(char c){ World* w = client ? client->GetWorld() : NULL; switch(c){ case MsgColorTeam1: return w ? ConvertColor(w->GetTeam(0).color) : MakeVector4( 0, 1, 0, 1 ); case MsgColorTeam2: return w ? ConvertColor(w->GetTeam(1).color) : MakeVector4( 0, 0, 1, 1 ); case MsgColorTeam3: return w ? ConvertColor(w->GetTeam(2).color) : MakeVector4( 1, 1, 0, 1 ); case MsgColorRed: return MakeVector4(1,0,0,1); case MsgColorBlack: return MakeVector4(0,0,0,1); case MsgColorSysInfo: return MakeVector4(0,1,0,1); default: return MakeVector4(1,1,1,1); } } void ChatWindow::Update(float dt) { if(firstY < 0.f){ firstY += dt * std::max(100.f, -firstY); if(firstY > 0.f) firstY = 0.f; } float height = GetHeight(); float y = firstY; for(std::list::iterator it = entries.begin(); it != entries.end(); ){ ChatEntry& ent = *it; if(y + ent.height > height){ // should fade out ent.fade -= dt * 4.f; if(ent.fade < 0.f){ ent.fade = 0.f; std::list::iterator er = it++; entries.erase( er ); continue; } }else if(y + ent.height > 0.f){ // should fade in ent.fade += dt * 4.f; if(ent.fade > 1.f) ent.fade = 1.f; } ent.timeFade -= dt; if(ent.timeFade < 0.f){ std::list::iterator er = it++; entries.erase( er ); continue; } y += ent.height; ++it; } } IImage* ChatWindow::imageForIndex( char index ) { int real = index - 'a'; if( real >= 0 && real < (int)mKillImages.size() ) { return mKillImages[real]; } return NULL; } void ChatWindow::Draw() { SPADES_MARK_FUNCTION(); float winX = 4.f; float winY = killfeed ? 8.f : renderer->ScreenHeight() - GetHeight() - 60.f; std::list::iterator it; float lHeight = GetLineHeight(); float y = firstY; Vector4 shadowColor = {0, 0, 0, 0.8f}; for(it = entries.begin(); it != entries.end(); ++it){ ChatEntry& ent = *it; std::string msg = ent.msg; Vector4 color = GetColor(MsgColorRestore); float tx = 0.f, ty = y; float fade = ent.fade; if(ent.timeFade < 1.f) { fade *= ent.timeFade; } shadowColor.w = .8f * fade; color.w = fade; std::string ch = "aaaaaa"; //let's not make a new object for each character. // note: UTF-8's longest character is 6 bytes for(size_t i = 0; i < msg.size(); i++){ if(msg[i] == 13 || msg[i] == 10){ tx = 0.f; ty += lHeight; }else if(msg[i] <= MsgColorMax && msg[i] >= 1){ if( msg[i] == MsgImage ) { IImage* kill = NULL; if( i+1 < msg.size() && (kill = imageForIndex(msg[i+1])) ) { Vector4 colorpm = color; colorpm.x *= colorpm.w; colorpm.y *= colorpm.w; colorpm.z *= colorpm.w; renderer->SetColorAlphaPremultiplied(colorpm); renderer->DrawImage( kill, MakeVector2( tx + winX, ty + winY ) ); tx += kill->GetWidth(); ++i; } else { // just ignore invalid icon specifier } } else { color = GetColor(msg[i]); color.w = fade; } }else{ size_t ln = 0; GetCodePointFromUTF8String(msg, i, &ln); ch.resize(ln); for(size_t k = 0; k < ln; k++) ch[k] = msg[i + k]; i += ln - 1; font->DrawShadow(ch, MakeVector2(tx + winX, ty + winY), 1.f, color, shadowColor); tx += font->Measure(ch).x; } } y += ent.height; } } } }