Merge pull request #1 from Continuum-Universes-Studio/feature/continuumatrice-zone-bands

Continuumatrice: colored Front / Support / Command battlefield
This commit is contained in:
Christian Daniel Jensen 2026-11-02 00:21:11 -07:00 • committed by GitHub
commit ccab482668
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 173 additions and 17 deletions

View file

@ -12,6 +12,15 @@
#include <QBrush>
#include <QColor>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QFont>
#include <QFontDatabase>
#include <QHash>
#include <QPen>
#include <QSettings>
#include <QStringList>
#include <QGradient>
#include <QGraphicsItem>
#include <QGraphicsScene>
@ -71,6 +80,7 @@ TableZone::TableZone(TableZoneLogic *_logic, bool _mirrored, QGraphicsItem *pare
void TableZone::updateBg()
{
reloadBattlefieldAppearance();
update();
}
@ -126,6 +136,8 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
painter->fillRect(boundingRect(), brush);
}
paintBattlefieldRows(painter);
if (active) {
paintZoneOutline(painter);
} else {
@ -141,7 +153,6 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
painter->fillRect(boundingRect(), shimmerColor);
}
paintLandDivider(painter);
}
void TableZone::onPlaymatChanged(bool active)
@ -179,21 +190,127 @@ void TableZone::paintZoneOutline(QPainter *painter)
painter->fillRect(QRectF(width - BOX_LINE_WIDTH, 0, BOX_LINE_WIDTH, height), QBrush(grad1));
}
/**
Render a division line for land placement
@painter QPainter object
/**
* Draw Front, Support, and Command on Cockatrice's existing three-row table.
* This changes presentation only, not gameplay or network-zone identifiers.
*/
void TableZone::paintLandDivider(QPainter *painter)
void TableZone::paintBattlefieldRows(QPainter *painter)
{
// Place the line 2 grid heights down then back it off just enough to allow
// some space between a 3-card stack and the land area.
qreal separatorY = MARGIN_TOP + 2 * (CardDimensions::HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
if (isInverted()) {
separatorY = height - separatorY;
painter->save();
const qreal rowStride = CardDimensions::HEIGHT + PADDING_Y;
const bool inverted = isInverted();
QFont font(zoneLabelFontFamily);
font.setPixelSize(18);
painter->setFont(font);
for (int visualRow = 0; visualRow < TableZoneLogic::ROW_COUNT; ++visualRow) {
// Table logical grid Y: 0 = Command, 1 = Support, 2 = Front.
const int row = inverted ? TableZoneLogic::ROW_COUNT - 1 - visualRow : visualRow;
QString label;
QColor color;
switch (row) {
case 0:
label = tr("COMMAND ZONE");
color = commandZoneColor;
break;
case 1:
label = tr("SUPPORT ZONE");
color = supportZoneColor;
break;
default:
label = tr("FRONT ZONE");
color = frontZoneColor;
break;
}
const qreal top = visualRow * rowStride;
const qreal bottom = visualRow == TableZoneLogic::ROW_COUNT - 1 ? height : top + rowStride;
painter->fillRect(QRectF(0, top, width, bottom - top), color);
const QRectF labelRect(16, top + 3, qMax(0, width - 32), 24);
painter->setPen(QColor(0, 0, 0, 210));
painter->drawText(labelRect.translated(1, 1), Qt::AlignHCenter | Qt::AlignVCenter, label);
painter->setPen(QColor(250, 248, 244));
painter->drawText(labelRect, Qt::AlignHCenter | Qt::AlignVCenter, label);
if (visualRow > 0) {
painter->setPen(QPen(QColor(255, 255, 255, 95), 2));
painter->drawLine(QPointF(0, top), QPointF(width, top));
}
}
painter->restore();
}
/**
* Each theme may provide a battlefield.ini and/or Continuumorphous font file.
* Optional assets avoid build failures when the proprietary font is absent.
*/
void TableZone::reloadBattlefieldAppearance()
{
frontZoneColor = QColor(142, 52, 61, 128);
supportZoneColor = QColor(40, 105, 129, 128);
commandZoneColor = QColor(94, 66, 140, 128);
const QString themePath = themeManager->getCurrentThemePath();
if (!themePath.isEmpty()) {
const QString iniPath = QDir(themePath).filePath(QStringLiteral("battlefield.ini"));
if (QFileInfo::exists(iniPath)) {
QSettings settings(iniPath, QSettings::IniFormat);
const auto readColor = [&settings](const QString &key, const QColor &fallback) {
const QColor parsed(settings.value(key, fallback.name(QColor::HexArgb)).toString());
return parsed.isValid() ? parsed : fallback;
};
frontZoneColor = readColor(QStringLiteral("Zones/Front"), frontZoneColor);
supportZoneColor = readColor(QStringLiteral("Zones/Support"), supportZoneColor);
commandZoneColor = readColor(QStringLiteral("Zones/Command"), commandZoneColor);
}
}
QStringList fontPaths;
if (!themePath.isEmpty()) {
const QDir themeDir(themePath);
fontPaths << themeDir.filePath(QStringLiteral("fonts/Continuumorphous-Regular.ttf"))
<< themeDir.filePath(QStringLiteral("fonts/Continuumorphous-Regular.otf"));
}
const QDir binaryDir(QCoreApplication::applicationDirPath());
fontPaths << binaryDir.filePath(QStringLiteral("fonts/Continuumorphous-Regular.ttf"))
<< binaryDir.filePath(QStringLiteral("fonts/Continuumorphous-Regular.otf"));
static QHash<QString, QString> loadedFamilies;
zoneLabelFontFamily.clear();
for (const QString &path : fontPaths) {
if (!QFileInfo::isFile(path)) {
continue;
}
if (loadedFamilies.contains(path)) {
zoneLabelFontFamily = loadedFamilies.value(path);
} else {
const int id = QFontDatabase::addApplicationFont(path);
const QStringList families = QFontDatabase::applicationFontFamilies(id);
if (!families.isEmpty()) {
zoneLabelFontFamily = families.constFirst();
loadedFamilies.insert(path, zoneLabelFontFamily);
}
}
if (!zoneLabelFontFamily.isEmpty()) {
break;
}
}
if (zoneLabelFontFamily.isEmpty()) {
const QStringList installed = QFontDatabase::families();
if (installed.contains(QStringLiteral("Continuumorphous Regular"), Qt::CaseInsensitive)) {
zoneLabelFontFamily = QStringLiteral("Continuumorphous Regular");
} else if (installed.contains(QStringLiteral("Continuumorphous"), Qt::CaseInsensitive)) {
zoneLabelFontFamily = QStringLiteral("Continuumorphous");
} else {
zoneLabelFontFamily = QFont().family();
}
}
painter->setPen(QColor(255, 255, 255, 40));
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
}
void TableZone::handleDropEvent(const QList<CardDragItem *> &dragItems,

View file

@ -11,6 +11,7 @@
#include "../card_dimensions.h"
#include "select_zone.h"
#include <QColor>
#include <QElapsedTimer>
#include <QMap>
#include <QPixmap>
@ -47,10 +48,10 @@ private:
*/
static const int MARGIN_LEFT = 20;
static const int MARGIN_RIGHT = 15;
static const int MARGIN_TOP = 10;
static const int MARGIN_BOTTOM = 30;
static const int MARGIN_TOP = 30; // Space for zone labels.
static const int MARGIN_BOTTOM = 26;
static const int PADDING_X = 35;
static const int PADDING_Y = 30;
static const int PADDING_Y = 50;
/*
Minimum width of the table zone including margins.
@ -61,7 +62,7 @@ private:
Offset sizes when cards are stacked on each other in the grid
*/
static const int STACKED_CARD_OFFSET_X = CardDimensions::WIDTH / 3;
static const int STACKED_CARD_OFFSET_Y = PADDING_Y / 3;
static const int STACKED_CARD_OFFSET_Y = 8; // Avoid covering the next zone label.
/*
Width of the box line drawn in the margin around the active player's area
@ -100,6 +101,11 @@ private:
bool mirrored = false;
bool playmatActive = false;
QColor frontZoneColor;
QColor supportZoneColor;
QColor commandZoneColor;
QString zoneLabelFontFamily;
[[nodiscard]] bool isInverted() const;
private slots:
@ -220,7 +226,8 @@ private:
qreal damageShimmerAlpha = 0.0;
void paintZoneOutline(QPainter *painter);
void paintLandDivider(QPainter *painter);
void paintBattlefieldRows(QPainter *painter);
void reloadBattlefieldAppearance();
/*
Calculates card stack widths so mapping functions work properly

View file

@ -0,0 +1,32 @@
# Continuumatrice battlefield style
Cockatrice's three visual table rows are reused as **Front**, **Support**, and **Command**. This is purely a display change: the server still sees a single `table` zone.
| Card XML `<tablerow>` | Position |
| --- | --- |
| `0` | Front |
| `1` | Support |
| `2` | Command |
## Optional color customization
Create `battlefield.ini` in the active Cockatrice theme directory:
```ini
[Zones]
Front = #A08E343D
Support = #A0286981
Command = #A05E428C
```
The Qt color notation is `#AARRGGBB`, including transparency. Without this file, built-in red, teal, and purple shades are used. Reload the theme to apply changes. The row order follows the player's mirrored/inverted layout, so Front always faces the battlefield center.
## Font
To render labels using your own **Continuumorphous Regular**, copy either `Continuumorphous-Regular.ttf` or `Continuumorphous-Regular.otf` into `fonts/` inside the current theme directory, or into a `fonts/` directory beside the Continuumatrice executable.
The font is also detected if already installed as "Continuumorphous Regular" or "Continuumorphous". If not found, a Qt default font is used. The font asset is not included in this change; no resource entry was added that would break builds when the file is missing.
## Compatibility
The existing drag/drop, row mapping, stacking, card counters, playmats, and multiplayer protocols are preserved. This does **not** enforce gameplay placement restrictions.