Skip to content

Commit d3f90d8

Browse files
Add unlockable icon
1 parent dcef773 commit d3f90d8

File tree

7 files changed

+203
-1
lines changed

7 files changed

+203
-1
lines changed

include/mainwindow.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "newlayoutdialog.h"
3535
#include "message.h"
3636
#include "resizelayoutpopup.h"
37+
#include "unlockableicon.h"
3738

3839
#if __has_include(<QJSValue>)
3940
#include <QJSValue>
@@ -354,6 +355,8 @@ private slots:
354355
MapNavigation forwardNavigation;
355356
bool ignoreNavigationRecords = false;
356357

358+
UnlockableIcon unlockableMainTabIcon;
359+
357360
QAction *copyAction = nullptr;
358361
QAction *pasteAction = nullptr;
359362

@@ -460,7 +463,8 @@ private slots:
460463
MapListToolBar* getCurrentMapListToolBar();
461464
MapTree* getCurrentMapList();
462465
void setLocationComboBoxes(const QStringList &locations);
463-
466+
void overrideMainTabIcons(const QIcon& icon);
467+
void tryUnlockMainTabIcon(const Map* map);
464468
QObjectList shortcutableObjects() const;
465469
void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false);
466470

include/ui/unlockableicon.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#ifndef UNLOCKABLEICON_H
2+
#define UNLOCKABLEICON_H
3+
4+
// Manages an icon loaded from a mildly obfuscated data file containing the icon's pixmap and a key.
5+
// The icon can only be accessed by inputting the correct key.
6+
7+
#include <QObject>
8+
#include <QIcon>
9+
#include <QString>
10+
#include <QSet>
11+
12+
class UnlockableIcon : public QObject
13+
{
14+
Q_OBJECT
15+
public:
16+
UnlockableIcon(QObject* parent = nullptr);
17+
UnlockableIcon(const QString& dataFilepath, QObject* parent = nullptr);
18+
~UnlockableIcon() {};
19+
20+
// Create the obfuscated data file to load an unlockable icon from.
21+
// Normally unused, this is only needed to update the resource data file.
22+
static bool createDataFile(const QString& dataFilepath, const QString& key, const QPixmap& icon);
23+
24+
bool load(const QString& dataFilepath);
25+
void clear();
26+
27+
// Try to unlock the icon by matching the next character in the key.
28+
// Progress resets if the character is not a match.
29+
void tryUnlock(const QChar& c);
30+
31+
// Try to unlock the icon by matching the next character in the key.
32+
// Progress resets if none of the characters in the set are a match.
33+
void tryUnlock(const QSet<QChar>& cSet);
34+
35+
// Try to unlock the icon by matching the remaining characters in the key.
36+
// Progress resets if any character in the string is not a match.
37+
void tryUnlock(const QString& key);
38+
39+
bool isUnlocked() const { return m_loaded && m_keyIndex >= m_key.length(); }
40+
QIcon icon() const { return isUnlocked() ? m_icon : QIcon(); }
41+
42+
signals:
43+
void unlocked(const QIcon& icon);
44+
45+
private:
46+
QIcon m_icon;
47+
QString m_key;
48+
quint32 m_keyIndex = 0;
49+
bool m_loaded = false;
50+
51+
bool canUnlock() const { return m_loaded && m_keyIndex < m_key.length(); }
52+
bool tryKeyMatch(const QSet<QChar>& cSet);
53+
};
54+
55+
56+
57+
#endif // UNLOCKABLEICON_H

porymap.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ SOURCES += src/core/advancemapparser.cpp \
148148
src/ui/regionmappropertiesdialog.cpp \
149149
src/ui/colorpicker.cpp \
150150
src/ui/loadingscreen.cpp \
151+
src/ui/unlockableicon.cpp \
151152
src/config.cpp \
152153
src/editor.cpp \
153154
src/main.cpp \
@@ -268,6 +269,7 @@ HEADERS += include/core/advancemapparser.h \
268269
include/ui/regionmappropertiesdialog.h \
269270
include/ui/colorpicker.h \
270271
include/ui/loadingscreen.h \
272+
include/ui/unlockableicon.h \
271273
include/config.h \
272274
include/editor.h \
273275
include/mainwindow.h \

resources/images.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
<file>images/Entities_16x16.png</file>
103103
<file>images/pokemon_icon_placeholder.png</file>
104104
<file>images/porysplash.gif</file>
105+
<file>images/unlockable_tab_icon.dat</file>
105106
<file>icons/clipboard.ico</file>
106107
<file>icons/map_go.ico</file>
107108
</qresource>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TUlTU0lOR05PN�iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAACohjseAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEXRFWHRfcV9pY29PcmlnRGVwdGgANBP37ZUAAAPjSURBVGiB1VqtuqMwED2734pKJPLKPkJlH2EfAYlERlZGIpE8Ao+A7CNUXlkZiWMFHW4YksDdSdre8318/LZkOpOcMzMFEiPPsnG4D/N2qZrxUjXzMb/Gz6Xv/xPDiBA+b3fnda1bKFXM50oV0Lqdj+kZKX6Lv+E/wA2gc6WKqMYBLzAw5Cm+j4HkIQqsB2yHpg/0zKUuRe9ObuBwraFUtbpuG01e5fsYSB6ix0JjuNb4OOaLfdMqVGczP2cbxUNYguQGloUGANxa5dwTYs47G8lDtDobHE7rEJ3Rt87LMbz3FORZNppOj6bTIx3bey4C+P1Xj38TXKXs2WyFI31/8jnYPOZaiOt8135EmHKNyfcUinyLFaK/YhgRAgnmLZ7jCoeOL3UpGuNTDByuNY4PuiAQTRxO1WxMw6jj1ipkf5VojMlpQusW1XnNe8AkAm4t0LST8cSZMQV3cg+aTo/kpa0QBdYq5pAf3jtETacXC8XkNbU4ByYP8+MfEaLHQi+S3rLIcDgVACZv8XsAUPdTuE7PrUP7O3hKPmjz3J4c0E58pXhKiFK4HZnw5t4l2MZKaSI5XIUk1zkXA7Gk2lMS3rrPFtdsD30c88W9W6vg4s23RUhI7ykfSt8v9uCeQYTKEz7JRguNtCYjRijlIW/Y4plvW96Ujk9ME3wOue5zmWaXLWxvcbUTA2IDfZVr4EtuuejBpo23RihE7ZJEKLP3hWiM3oSYRE2nR04DLrgq2sBa3fBQfXk+mGfZuNVgoXnqEtlloYPZhTSbEINWSV/JwceBXLX4+FA6viQe5KtgaKX9vN2diS15/m7Maz3oqnXadVDyhun0SqWEVE4sJSP+dXxhxOssnCIIdk3GtfhI52AUovfldkoVKAuNstCo+wx1n+Hzdp+P7ZI+X2jeJh/0hVGoBbZVVKLPNq0Sz0Gx2K7OZpUOAV8hNlxrAHA+A7g9RcY9ajKi8UWZg5S/2cUinxzzzUUOyvZfzoOm00GO862ivm6SvfrGKN0nLzpRF/dwqlCdzaLLS73DqYK23FOIShElRF0akqc9vn9W2G1sjrrPXq9Ft+TUxzGfUyqucLZK89XZiAu/YnynsRmqsKVSMk/9IxCvwWzxZAyyF/Pg3g6Qb9Bc/QzXGnaz5uX4Tji6MndbmNP2Y0PU15fgpB/Tc9FX0b19P9/z/LNSJRN9Du75v5l9P0ToWyXJPUiSTbiMdNU7XRXv1fcLiV7swb3zxeXVLWNjlO6fng/yH4TSKTvreOsWtt1U8eV6tocoT+SFq7LIUPfy8USnCd5r4NhafOx9DLpIRhO+soTvP9s+b0sXmX+4yahShjMNpAAAAABJRU5ErkJggg==

src/mainwindow.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ void MainWindow::initCustomUI() {
334334
ui->mainTabBar->setTabIcon(i, mainTabIcons.value(i));
335335
}
336336

337+
this->unlockableMainTabIcon.load(":/images/unlockable_tab_icon.dat");
338+
337339
// Create map header data widget
338340
this->mapHeaderForm = new MapHeaderForm();
339341
ui->layout_HeaderData->addWidget(this->mapHeaderForm);
@@ -343,6 +345,12 @@ void MainWindow::initCustomUI() {
343345
ui->graphicsView_Map->setResizeAnchor(QGraphicsView::ViewportAnchor::AnchorUnderMouse);
344346
}
345347

348+
void MainWindow::overrideMainTabIcons(const QIcon& icon) {
349+
for (int i = 1; i < ui->mainTabBar->count(); i++) {
350+
ui->mainTabBar->setTabIcon(i, icon);
351+
}
352+
}
353+
346354
void MainWindow::initExtraSignals() {
347355
connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged);
348356

@@ -2285,6 +2293,8 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index)
22852293
ui->stackedWidget_MapEvents->setCurrentIndex(1);
22862294
} else if (index == MainTab::Connections) {
22872295
ui->graphicsView_Connections->setFocus(); // Avoid opening tab with focus on something editable
2296+
connect(this, &MainWindow::mapOpened, this, &MainWindow::tryUnlockMainTabIcon, Qt::UniqueConnection);
2297+
connect(&this->unlockableMainTabIcon, &UnlockableIcon::unlocked, this, &MainWindow::overrideMainTabIcons, Qt::UniqueConnection);
22882298
}
22892299

22902300
if (!editor->map) return;
@@ -2294,6 +2304,18 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index)
22942304
}
22952305
}
22962306

2307+
void MainWindow::tryUnlockMainTabIcon(const Map* map) {
2308+
if (!map || this->unlockableMainTabIcon.isUnlocked()) return;
2309+
const Layout* layout = map->layout();
2310+
if (!layout) return;
2311+
2312+
QSet<QChar> chars;
2313+
if (!layout->name.isEmpty()) chars.insert(layout->name.at(0));
2314+
const QString tilesetName = Tileset::stripPrefix(layout->tileset_secondary_label);
2315+
if (!tilesetName.isEmpty()) chars.insert(tilesetName.at(0));
2316+
this->unlockableMainTabIcon.tryUnlock(chars);
2317+
}
2318+
22972319
void MainWindow::on_actionZoom_In_triggered() {
22982320
editor->scaleMapView(1);
22992321
}

src/ui/unlockableicon.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#include "unlockableicon.h"
2+
#include <QFile>
3+
#include <QBuffer>
4+
5+
// TODO: QT_VERSION checks
6+
// TODO: Obfuscate the activation method?
7+
8+
constexpr int Version = QDataStream::Qt_6_8;
9+
10+
UnlockableIcon::UnlockableIcon(QObject* parent) : QObject(parent) {};
11+
UnlockableIcon::UnlockableIcon(const QString& dataFilepath, QObject* parent) : UnlockableIcon(parent) {
12+
load(dataFilepath);
13+
};
14+
15+
bool UnlockableIcon::createDataFile(const QString& filepath, const QString& key, const QPixmap& icon) {
16+
QFile file(filepath);
17+
if (!file.open(QIODevice::WriteOnly)) return false;
18+
19+
QDataStream out(&file);
20+
out.setVersion(Version);
21+
22+
QByteArray key64 = key.toUtf8().toBase64();
23+
out << (quint8)key.length();
24+
out << (quint8)key64.length();
25+
for (const auto& byte : key64) out << byte;
26+
27+
QByteArray iconData;
28+
QBuffer buffer(&iconData);
29+
buffer.open(QIODevice::WriteOnly);
30+
icon.save(&buffer, "PNG");
31+
buffer.close();
32+
33+
QByteArray iconData64 = iconData.toBase64();
34+
out << (quint16)iconData.length();
35+
out << (quint16)iconData64.length();
36+
for (const auto& byte : iconData64) out << byte;
37+
38+
file.close();
39+
return true;
40+
}
41+
42+
bool UnlockableIcon::load(const QString& dataFilepath) {
43+
clear();
44+
45+
QFile file(dataFilepath);
46+
if (!file.open(QIODevice::ReadOnly)) return false;
47+
48+
QDataStream in(&file);
49+
in.setVersion(Version);
50+
51+
quint8 keyLength = 0;
52+
quint8 key64Length = 0;
53+
in >> keyLength;
54+
in >> key64Length;
55+
if (keyLength == 0 || key64Length == 0) return false;
56+
57+
QByteArray key64(key64Length,0);
58+
for (quint8 i = 0; i < key64Length; i++) in >> key64[i];
59+
QString key = QString(QByteArray::fromBase64(key64));
60+
if (key.length() != keyLength) return false;
61+
62+
quint16 iconDataLength = 0;
63+
quint16 iconData64Length = 0;
64+
in >> iconDataLength;
65+
in >> iconData64Length;
66+
if (iconDataLength == 0 || iconData64Length == 0) return false;
67+
68+
QByteArray iconData64(iconData64Length,0);
69+
for (quint16 i = 0; i < iconData64Length; i++) in >> iconData64[i];
70+
QByteArray iconData = QByteArray::fromBase64(iconData64);
71+
if (iconData.length() != iconDataLength) return false;
72+
73+
QPixmap iconPixmap;
74+
iconPixmap.loadFromData(iconData);
75+
if (iconPixmap.isNull()) return false;
76+
77+
m_icon = QIcon(iconPixmap);
78+
m_key = key;
79+
m_loaded = true;
80+
return true;
81+
}
82+
83+
void UnlockableIcon::clear() {
84+
m_icon = QIcon();
85+
m_key = QString();
86+
m_keyIndex = 0;
87+
m_loaded = false;
88+
}
89+
90+
bool UnlockableIcon::tryKeyMatch(const QSet<QChar>& cSet) {
91+
if (m_keyIndex >= m_key.length()) return false;
92+
if (!cSet.contains(m_key.at(m_keyIndex))) {
93+
m_keyIndex = 0;
94+
return false;
95+
}
96+
if (++m_keyIndex == m_key.length()) {
97+
emit unlocked(m_icon);
98+
}
99+
return true;
100+
}
101+
102+
void UnlockableIcon::tryUnlock(const QSet<QChar>& cSet) {
103+
if (canUnlock()) tryKeyMatch(cSet);
104+
}
105+
106+
void UnlockableIcon::tryUnlock(const QChar& c) {
107+
tryUnlock(QSet<QChar>{c});
108+
}
109+
110+
void UnlockableIcon::tryUnlock(const QString& key) {
111+
if (!canUnlock()) return;
112+
for (const QChar& c : key) {
113+
if (!tryKeyMatch(QSet<QChar>{c})) return;
114+
}
115+
}

0 commit comments

Comments
 (0)