#include "player_target.h" #include "../../client/settings/cache_settings.h" #include "../../game/player/player_logic.h" #include "../../interface/pixel_map_generator.h" #include "../game_scene.h" #include #include #include #include #include #include PlayerCounter::PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent) : AbstractCounter(state, player, false, false, parent) { } QRectF PlayerCounter::boundingRect() const { return {0, 0, 50, 30}; } void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { const int radius = 15; const qreal border = 1.5; // The box is drawn with a border-wide stroke straddling the path, so the // visible outline spans [inset, inset + border]. Fills that must not cover // the outline (e.g. the life-change flash) use a path inset by `border`. const auto makePath = [](qreal inset) { QPainterPath path(QPointF(50 - inset, inset)); path.lineTo(radius, inset); path.arcTo(inset, inset, 2 * radius, 2 * radius, 90, 90); path.lineTo(inset, 30 - inset); path.lineTo(50 - inset, 30 - inset); path.closeSubpath(); return path; }; QPainterPath path = makePath(border / 2); QPen pen(QColor(100, 100, 100)); pen.setWidthF(border); painter->setPen(pen); painter->setBrush(hovered ? QColor(50, 50, 50, 160) : QColor(0, 0, 0, 160)); painter->drawPath(path); QRectF translatedRect = path.controlPointRect(); QSize translatedSize = translatedRect.size().toSize(); QFont font("Serif"); font.setWeight(QFont::Bold); font.setPixelSize(qMax(qRound(translatedSize.height() / 1.3), 9)); painter->setFont(font); painter->setPen(Qt::white); painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value)); // Life-change flash: emerald on gain, red on loss, decaying over a few ticks. if (flashAlpha > 0) { painter->save(); QColor flashColor = flashDelta > 0 ? QColor(52, 224, 122) : QColor(239, 68, 68); flashColor.setAlphaF(0.45 * flashAlpha); painter->setPen(Qt::NoPen); painter->setBrush(flashColor); painter->setOpacity(0.85); painter->drawPath(makePath(border)); painter->restore(); } } void PlayerCounter::onValueChanged(int oldValue, int newValue) { flashDelta = newValue - oldValue; if (flashDelta == 0) { return; } if (!SettingsCache::instance().userInterface().getLifeCounterAnimationsEnabled()) { flashAlpha = 0.0; return; } flashAlpha = 1.0; flashClock.start(); if (scene()) { static_cast(scene())->registerAnimationItem(this); } } bool PlayerCounter::animationEvent() { flashAlpha = 1.0 - flashClock.elapsed() / flashDurationMs; if (flashAlpha <= 0.0) { flashAlpha = 0.0; return false; } update(); return true; } PlayerTarget::PlayerTarget(PlayerLogic *_owner, QGraphicsItem *parentItem) : ArrowTarget(_owner, parentItem), playerCounter(nullptr) { setCacheMode(DeviceCoordinateCache); const std::string &bmp = _owner->getPlayerInfo()->getUserInfo()->avatar_bmp(); if (!fullPixmap.loadFromData((const uchar *)bmp.data(), static_cast(bmp.size()))) { fullPixmap = QPixmap(); } } PlayerTarget::~PlayerTarget() { // Explicit deletion is necessary in spite of parent/child relationship // as we need this object to be alive to receive the destroyed() signal. delete playerCounter; } QRectF PlayerTarget::boundingRect() const { return {0, 0, 160, 64}; } void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { const ServerInfo_User *const info = owner->getPlayerInfo()->getUserInfo(); const qreal border = 2; QRectF avatarBoundingRect = boundingRect().adjusted(border, border, -border, -border); QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect); QSize translatedSize = translatedRect.size().toSize(); QPixmap cachedPixmap; const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" + QString::number(info->user_level()) + "_" + QString::number(fullPixmap.cacheKey()); if (!QPixmapCache::find(cacheKey, &cachedPixmap)) { cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height()); QPainter tempPainter(&cachedPixmap); // pow(foo, 0.5) equals to sqrt(foo), but using sqrt(foo) in this context will produce a compile error with // MSVC++ QRadialGradient grad(translatedRect.center(), qPow(translatedSize.width() * translatedSize.width() + translatedSize.height() * translatedSize.height(), 0.5) / 2); grad.setColorAt(1, Qt::black); grad.setColorAt(0, QColor(180, 180, 180)); tempPainter.fillRect(QRectF(0, 0, translatedSize.width(), translatedSize.height()), grad); if (fullPixmap.isNull()) { int sideLength = translatedSize.height(); QPixmap tempPixmap = UserLevelPixmapGenerator::generatePixmap( sideLength, UserLevelFlags(info->user_level()), info->pawn_colors(), false, QString::fromStdString(info->privlevel())); int x = (translatedSize.width() - sideLength) / 2; int y = 0; tempPainter.drawPixmap(x, y, tempPixmap); } else { QPixmap tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); int x = (translatedSize.width() - tempPixmap.width()) / 2; int y = (translatedSize.height() - tempPixmap.height()) / 2; tempPainter.drawPixmap(x, y, tempPixmap); } QPixmapCache::insert(cacheKey, cachedPixmap); } painter->save(); resetPainterTransform(painter); painter->translate((translatedSize.width() - cachedPixmap.width()) / 2.0, 0); painter->drawPixmap(translatedRect, cachedPixmap, cachedPixmap.rect()); painter->restore(); QRectF nameRect = QRectF(0, boundingRect().height() - 20, 110, 20); painter->fillRect(nameRect, QColor(0, 0, 0, 160)); QRectF translatedNameRect = painter->combinedTransform().mapRect(nameRect); painter->save(); resetPainterTransform(painter); QString name = QString::fromStdString(info->name()); if (name.size() > 13) { name = name.mid(0, 10) + "..."; } QFont font; font.setPixelSize(qMax(qRound(translatedNameRect.height() / 1.5), 9)); painter->setFont(font); painter->setPen(Qt::white); painter->drawText(translatedNameRect, Qt::AlignVCenter | Qt::AlignLeft, " " + name); painter->restore(); QPen pen(QColor(100, 100, 100)); pen.setWidth(border); pen.setJoinStyle(Qt::RoundJoin); painter->setPen(pen); painter->drawRect(boundingRect().adjusted(border / 2, border / 2, -border / 2, -border / 2)); if (getBeingPointedAt()) { painter->fillRect(boundingRect(), QBrush(QColor(255, 0, 0, 100))); } } AbstractCounter *PlayerTarget::addCounter(CounterState *state) { if (playerCounter) { disconnect(playerCounter, nullptr, this, nullptr); playerCounter->delCounter(); } playerCounter = new PlayerCounter(state, owner, this); playerCounter->setPos(boundingRect().width() - playerCounter->boundingRect().width(), boundingRect().height() - playerCounter->boundingRect().height()); connect(playerCounter, &PlayerCounter::destroyed, this, &PlayerTarget::counterDeleted); return playerCounter; } void PlayerTarget::counterDeleted() { playerCounter = nullptr; }