From 0932dc2bb3c0d660a417d6737224cfd4f4f6d250 Mon Sep 17 00:00:00 2001 From: Cubitect Date: Mon, 4 Jan 2021 15:07:27 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 2 + .gitmodules | 3 + README.md | 13 + aboutdialog.cpp | 22 + aboutdialog.h | 26 + aboutdialog.ui | 102 ++++ cubiomes | 1 + cubiomes-viewer.pro | 78 +++ filterdialog.cpp | 295 ++++++++++++ filterdialog.h | 48 ++ filterdialog.ui | 310 ++++++++++++ gotodialog.cpp | 39 ++ gotodialog.h | 28 ++ gotodialog.ui | 101 ++++ icons.qrc | 31 ++ icons/426997539678997085.png | Bin 0 -> 1270 bytes icons/desert.png | Bin 0 -> 1110 bytes icons/desert_d.png | Bin 0 -> 527 bytes icons/hut.png | Bin 0 -> 644 bytes icons/hut_d.png | Bin 0 -> 489 bytes icons/igloo.png | Bin 0 -> 418 bytes icons/igloo_d.png | Bin 0 -> 433 bytes icons/jungle.png | Bin 0 -> 848 bytes icons/jungle_d.png | Bin 0 -> 579 bytes icons/mansion.png | Bin 0 -> 414 bytes icons/mansion_d.png | Bin 0 -> 421 bytes icons/monument.png | Bin 0 -> 1189 bytes icons/monument_d.png | Bin 0 -> 554 bytes icons/outpost.png | Bin 0 -> 541 bytes icons/outpost_d.png | Bin 0 -> 646 bytes icons/portal.png | Bin 0 -> 515 bytes icons/portal_d.png | Bin 0 -> 468 bytes icons/ruins.png | Bin 0 -> 642 bytes icons/ruins_d.png | Bin 0 -> 417 bytes icons/shipwreck.png | Bin 0 -> 381 bytes icons/shipwreck_d.png | Bin 0 -> 387 bytes icons/spawn.png | Bin 0 -> 413 bytes icons/spawn_d.png | Bin 0 -> 276 bytes icons/stronghold.png | Bin 0 -> 753 bytes icons/stronghold_d.png | Bin 0 -> 645 bytes icons/village.png | Bin 0 -> 421 bytes icons/village_d.png | Bin 0 -> 424 bytes main.cpp | 25 + mainwindow.cpp | 649 +++++++++++++++++++++++++ mainwindow.h | 119 +++++ mainwindow.ui | 701 +++++++++++++++++++++++++++ mapview.cpp | 353 ++++++++++++++ mapview.h | 85 ++++ protobasedialog.cpp | 26 + protobasedialog.h | 25 + protobasedialog.ui | 80 ++++ quad.cpp | 705 +++++++++++++++++++++++++++ quad.h | 227 +++++++++ quadlistdialog.cpp | 189 ++++++++ quadlistdialog.h | 38 ++ quadlistdialog.ui | 205 ++++++++ search.cpp | 894 +++++++++++++++++++++++++++++++++++ search.h | 263 +++++++++++ searchthread.cpp | 166 +++++++ searchthread.h | 53 +++ 60 files changed, 5902 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 README.md create mode 100644 aboutdialog.cpp create mode 100644 aboutdialog.h create mode 100644 aboutdialog.ui create mode 160000 cubiomes create mode 100644 cubiomes-viewer.pro create mode 100644 filterdialog.cpp create mode 100644 filterdialog.h create mode 100644 filterdialog.ui create mode 100644 gotodialog.cpp create mode 100644 gotodialog.h create mode 100644 gotodialog.ui create mode 100644 icons.qrc create mode 100644 icons/426997539678997085.png create mode 100644 icons/desert.png create mode 100644 icons/desert_d.png create mode 100644 icons/hut.png create mode 100644 icons/hut_d.png create mode 100644 icons/igloo.png create mode 100644 icons/igloo_d.png create mode 100644 icons/jungle.png create mode 100644 icons/jungle_d.png create mode 100644 icons/mansion.png create mode 100644 icons/mansion_d.png create mode 100644 icons/monument.png create mode 100644 icons/monument_d.png create mode 100644 icons/outpost.png create mode 100644 icons/outpost_d.png create mode 100644 icons/portal.png create mode 100644 icons/portal_d.png create mode 100644 icons/ruins.png create mode 100644 icons/ruins_d.png create mode 100644 icons/shipwreck.png create mode 100644 icons/shipwreck_d.png create mode 100644 icons/spawn.png create mode 100644 icons/spawn_d.png create mode 100644 icons/stronghold.png create mode 100644 icons/stronghold_d.png create mode 100644 icons/village.png create mode 100644 icons/village_d.png create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 mapview.cpp create mode 100644 mapview.h create mode 100644 protobasedialog.cpp create mode 100644 protobasedialog.h create mode 100644 protobasedialog.ui create mode 100644 quad.cpp create mode 100644 quad.h create mode 100644 quadlistdialog.cpp create mode 100644 quadlistdialog.h create mode 100644 quadlistdialog.ui create mode 100644 search.cpp create mode 100644 search.h create mode 100644 searchthread.cpp create mode 100644 searchthread.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25bedb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cubiomes-viewer.pro.user + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5384a9d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cubiomes"] + path = cubiomes + url = https://github.com/Cubitect/cubiomes.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..73aeb6a --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Cubiomes Viewer + +Cubiomes Viewer provides a graphical interface for the efficient and flexible seed-finding utilities provided by [cubiomes](https://github.com/Cubitect/cubiomes) and a map viewer for the Minecraft biomes and structure generation. + +The tool is designed for high performance but is currently limited to overworld features for Minecraft 1.7 - 1.16. + + +## Download + +Precompiled binaries can be found for Linux and Windows under [Releases on github](https://github.com/Cubitect/cubiomes-viewer/releases). The builds are statically linked against [Qt](https://www.qt.io) and should run as-is on most newer distributions. + + + diff --git a/aboutdialog.cpp b/aboutdialog.cpp new file mode 100644 index 0000000..a07c2c1 --- /dev/null +++ b/aboutdialog.cpp @@ -0,0 +1,22 @@ +#include "aboutdialog.h" +#include "ui_aboutdialog.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + QString text = ui->label->text(); + text.replace("_DATE_", QString(__DATE__)); + text.replace("_QT_MAJOR_", QString::number(QT_VERSION_MAJOR)); + text.replace("_QT_MINOR_", QString::number(QT_VERSION_MINOR)); + text.replace("_MAJOR_", QString::number(VERS_MAJOR)); + text.replace("_MINOR_", QString::number(VERS_MINOR)); + text.replace("_PATCH_", QString::number(VERS_PATCH)); + ui->label->setText(text); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} diff --git a/aboutdialog.h b/aboutdialog.h new file mode 100644 index 0000000..539620d --- /dev/null +++ b/aboutdialog.h @@ -0,0 +1,26 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +#define VERS_MAJOR 1 +#define VERS_MINOR 0 +#define VERS_PATCH 1 + +namespace Ui { +class AboutDialog; +} + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = nullptr); + ~AboutDialog(); + +private: + Ui::AboutDialog *ui; +}; + +#endif // ABOUTDIALOG_H diff --git a/aboutdialog.ui b/aboutdialog.ui new file mode 100644 index 0000000..ce42604 --- /dev/null +++ b/aboutdialog.ui @@ -0,0 +1,102 @@ + + + AboutDialog + + + + 0 + 0 + 587 + 282 + + + + About + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:16pt; font-weight:600;">Cubiomes-Viewer _MAJOR_._MINOR_._PATCH_</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Built: _DATE_</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">URL: <a href="https://github.com/Cubitect/cubiomes-viewer"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/Cubitect/cubiomes-viewer</span></a></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">License: <a href="https://www.gnu.org/licenses/gpl-3.0.en.html"><span style=" text-decoration: underline; color:#0000ff;">GPLv3</span></a></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:12pt; font-weight:600;">Components</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:12pt; font-weight:600;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">— Minecraft biome and structure generation from <a href="https://github.com/Cubitect/cubiomes/"><span style=" text-decoration: underline; color:#0000ff;">cubiomes</span></a>, licensed under <a href="https://mit-license.org/"><span style=" text-decoration: underline; color:#0000ff;">MIT</span></a>.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">— Cross platform GUI toolkit: Qt _QT_MAJOR_._QT_MINOR_, available under <a href="https://www.qt.io/licensing/"><span style=" text-decoration: underline; color:#0000ff;">(L)GPLv3</span></a>.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">— Colors and icons are in part modified from <a href="https://github.com/toolbox4minecraft/amidst"><span style=" text-decoration: underline; color:#0000ff;">Amidst</span></a>, licensed under GPLv3.</p></body></html> + + + + + + + + + + :/icons/426997539678997085.png + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + buttonBox + accepted() + AboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/cubiomes b/cubiomes new file mode 160000 index 0000000..6426de5 --- /dev/null +++ b/cubiomes @@ -0,0 +1 @@ +Subproject commit 6426de5366f0ef7d4e10b1277804a68570c2db4f diff --git a/cubiomes-viewer.pro b/cubiomes-viewer.pro new file mode 100644 index 0000000..f6e7b0c --- /dev/null +++ b/cubiomes-viewer.pro @@ -0,0 +1,78 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2020-07-11T11:37:33 +# +#------------------------------------------------- + +# For a release with binary compatibility, cubiomes should be compiled for the +# default achitecture. +QT += core widgets +LIBS += -lm $$PWD/cubiomes/libcubiomes.a + +win32: { + LIBS += -static -static-libgcc -static-libstdc++ +} + +QMAKE_CFLAGS = -fwrapv +QMAKE_CXXFLAGS = $$QMAKE_CFLAGS +QMAKE_CXXFLAGS_RELEASE *= -O3 + +TARGET = cubiomes-viewer +#TEMPLATE = app + +CONFIG += static + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +#DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + aboutdialog.cpp \ + gotodialog.cpp \ + protobasedialog.cpp \ + filterdialog.cpp \ + quadlistdialog.cpp \ + mainwindow.cpp \ + mapview.cpp \ + quad.cpp \ + search.cpp \ + searchthread.cpp \ + main.cpp + +HEADERS += \ + cubiomes/finders.h \ + cubiomes/generator.h \ + cubiomes/javarnd.h \ + cubiomes/layers.h \ + cubiomes/util.h \ + aboutdialog.h \ + gotodialog.h \ + protobasedialog.h \ + filterdialog.h \ + quadlistdialog.h \ + mainwindow.h \ + mapview.h \ + quad.h \ + search.h \ + searchthread.h + +FORMS += \ + aboutdialog.ui \ + gotodialog.ui \ + mainwindow.ui \ + protobasedialog.ui \ + filterdialog.ui \ + quadlistdialog.ui + +RESOURCES += \ + icons.qrc + +DISTFILES += diff --git a/filterdialog.cpp b/filterdialog.cpp new file mode 100644 index 0000000..3ceb2ad --- /dev/null +++ b/filterdialog.cpp @@ -0,0 +1,295 @@ +#include "filterdialog.h" +#include "ui_filterdialog.h" + +#include +#include + + +#define SETUP_BIOME_CHECKBOX(B) do {\ + biomecboxes[B] = new QCheckBox(#B);\ + ui->gridLayoutBiomes->addWidget(biomecboxes[B], B % 128, B / 128);\ + } while (0) + +FilterDialog::FilterDialog(MainWindow *parent, Condition *initcond) : + QDialog(parent, Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint), + ui(new Ui::FilterDialog) +{ + memset(&cond, 0, sizeof(cond)); + ui->setupUi(this); + + for (int i = 1; i < FILTER_MAX; i++) + { + const FilterInfo &ft = g_filterinfo.list[i]; + if (ft.icon) + ui->comboBoxType->addItem(QIcon(ft.icon), ft.name, i); + else + ui->comboBoxType->addItem(ft.name, i); + } + + int initindex = 0; + QVector existing = parent->getConditions(); + for (Condition c : existing) + { + const FilterInfo &ft = g_filterinfo.list[c.type]; + if (initcond) + { + if (c.save == initcond->save) + continue; + if (c.save == initcond->relative) + initindex = ui->comboBoxRelative->count(); + } + ui->comboBoxRelative->addItem(QString::asprintf("[%02d] %s", c.save, ft.name), c.save); + } + + ui->lineRadius->setValidator(new QIntValidator(this)); + ui->lineEditX1->setValidator(new QIntValidator(this)); + ui->lineEditZ1->setValidator(new QIntValidator(this)); + ui->lineEditX2->setValidator(new QIntValidator(this)); + ui->lineEditZ2->setValidator(new QIntValidator(this)); + + memset(biomecboxes, 0, sizeof(biomecboxes)); + + SETUP_BIOME_CHECKBOX(ocean); + SETUP_BIOME_CHECKBOX(plains); + SETUP_BIOME_CHECKBOX(desert); + SETUP_BIOME_CHECKBOX(mountains); + SETUP_BIOME_CHECKBOX(forest); + SETUP_BIOME_CHECKBOX(taiga); + SETUP_BIOME_CHECKBOX(swamp); + SETUP_BIOME_CHECKBOX(river); + SETUP_BIOME_CHECKBOX(frozen_ocean); + SETUP_BIOME_CHECKBOX(frozen_river); + SETUP_BIOME_CHECKBOX(snowy_tundra); + SETUP_BIOME_CHECKBOX(snowy_mountains); + SETUP_BIOME_CHECKBOX(mushroom_fields); + SETUP_BIOME_CHECKBOX(mushroom_field_shore); + SETUP_BIOME_CHECKBOX(beach); + SETUP_BIOME_CHECKBOX(desert_hills); + SETUP_BIOME_CHECKBOX(wooded_hills); + SETUP_BIOME_CHECKBOX(taiga_hills); + SETUP_BIOME_CHECKBOX(mountain_edge); + SETUP_BIOME_CHECKBOX(jungle); + SETUP_BIOME_CHECKBOX(jungle_hills); + SETUP_BIOME_CHECKBOX(jungle_edge); + SETUP_BIOME_CHECKBOX(deep_ocean); + SETUP_BIOME_CHECKBOX(stone_shore); + SETUP_BIOME_CHECKBOX(snowy_beach); + SETUP_BIOME_CHECKBOX(birch_forest); + SETUP_BIOME_CHECKBOX(birch_forest_hills); + SETUP_BIOME_CHECKBOX(dark_forest); + SETUP_BIOME_CHECKBOX(snowy_taiga); + SETUP_BIOME_CHECKBOX(snowy_taiga_hills); + SETUP_BIOME_CHECKBOX(giant_tree_taiga); + SETUP_BIOME_CHECKBOX(giant_tree_taiga_hills); + SETUP_BIOME_CHECKBOX(wooded_mountains); + SETUP_BIOME_CHECKBOX(savanna); + SETUP_BIOME_CHECKBOX(savanna_plateau); + SETUP_BIOME_CHECKBOX(badlands); + SETUP_BIOME_CHECKBOX(wooded_badlands_plateau); + SETUP_BIOME_CHECKBOX(badlands_plateau); + SETUP_BIOME_CHECKBOX(warm_ocean); + SETUP_BIOME_CHECKBOX(lukewarm_ocean); + SETUP_BIOME_CHECKBOX(cold_ocean); + SETUP_BIOME_CHECKBOX(deep_warm_ocean); + SETUP_BIOME_CHECKBOX(deep_lukewarm_ocean); + SETUP_BIOME_CHECKBOX(deep_cold_ocean); + SETUP_BIOME_CHECKBOX(deep_frozen_ocean); + + SETUP_BIOME_CHECKBOX(sunflower_plains); + SETUP_BIOME_CHECKBOX(desert_lakes); + SETUP_BIOME_CHECKBOX(gravelly_mountains); + SETUP_BIOME_CHECKBOX(flower_forest); + SETUP_BIOME_CHECKBOX(taiga_mountains); + SETUP_BIOME_CHECKBOX(swamp_hills); + SETUP_BIOME_CHECKBOX(ice_spikes); + SETUP_BIOME_CHECKBOX(modified_jungle); + SETUP_BIOME_CHECKBOX(modified_jungle_edge); + SETUP_BIOME_CHECKBOX(tall_birch_forest); + SETUP_BIOME_CHECKBOX(tall_birch_hills); + SETUP_BIOME_CHECKBOX(dark_forest_hills); + SETUP_BIOME_CHECKBOX(snowy_taiga_mountains); + SETUP_BIOME_CHECKBOX(giant_spruce_taiga); + SETUP_BIOME_CHECKBOX(giant_spruce_taiga_hills); + SETUP_BIOME_CHECKBOX(modified_gravelly_mountains); + SETUP_BIOME_CHECKBOX(shattered_savanna); + SETUP_BIOME_CHECKBOX(shattered_savanna_plateau); + SETUP_BIOME_CHECKBOX(eroded_badlands); + SETUP_BIOME_CHECKBOX(modified_wooded_badlands_plateau); + SETUP_BIOME_CHECKBOX(modified_badlands_plateau); + SETUP_BIOME_CHECKBOX(bamboo_jungle); + SETUP_BIOME_CHECKBOX(bamboo_jungle_hills); + + custom = false; + + if (initcond) + { + cond = *initcond; + + ui->comboBoxType->setCurrentIndex(cond.type); + ui->comboBoxRelative->setCurrentIndex(initindex); + + updateMode(); + + if (!ui->groupBoxBiomes->isEnabled()) + ui->spinBox->setValue(cond.count); + + ui->lineEditX1->setText(QString::number(cond.x1)); + ui->lineEditZ1->setText(QString::number(cond.z1)); + ui->lineEditX2->setText(QString::number(cond.x2)); + ui->lineEditZ2->setText(QString::number(cond.z2)); + + if (cond.x1 == cond.z1 && cond.x1 == -cond.x2 && cond.x1 == -cond.z2) + { + ui->lineRadius->setText(QString::number(cond.x2 * 2)); + if (ui->buttonArea->isEnabled()) + { + custom = false; + ui->buttonArea->setChecked(false); + } + } + else + { + if (ui->buttonArea->isEnabled()) + { + ui->buttonArea->setChecked(true); + custom = true; + } + } + + for (int i = 0; i < 64; i++) + { + if ((cond.bfilter.riverToFind | cond.bfilter.oceanToFind) & (1ULL << i)) + if (biomecboxes[i]) + biomecboxes[i]->setChecked(true); + + if (cond.bfilter.riverToFindM & (1ULL << i)) + if (biomecboxes[i+128]) + biomecboxes[i+128]->setChecked(true); + } + + if (cond.bfilter.riverToFind & (1ULL << (bamboo_jungle & 0x3f))) + biomecboxes[bamboo_jungle]->setChecked(true); + if (cond.bfilter.riverToFindM & (1ULL << (bamboo_jungle_hills - 128))) + biomecboxes[bamboo_jungle_hills]->setChecked(true); + } + + updateMode(); +} + +FilterDialog::~FilterDialog() +{ + delete ui; +} + +void FilterDialog::updateMode() +{ + int filterindex = ui->comboBoxType->currentData().toInt(); + const FilterInfo &ft = g_filterinfo.list[filterindex]; + + ui->groupBoxPosition->setEnabled(filterindex != F_SELECT); + + ui->buttonArea->setEnabled(ft.area); + + ui->labelSquareArea->setEnabled(!custom && ft.area); + ui->lineRadius->setEnabled(!custom && ft.area); + + ui->labelX1->setEnabled((custom && ft.coord) || !ft.area); + ui->labelZ1->setEnabled((custom && ft.coord) || !ft.area); + ui->labelX2->setEnabled(custom && ft.area); + ui->labelZ2->setEnabled(custom && ft.area); + ui->lineEditX1->setEnabled((custom && ft.coord) || !ft.area); + ui->lineEditZ1->setEnabled((custom && ft.coord) || !ft.area); + ui->lineEditX2->setEnabled(custom && ft.area); + ui->lineEditZ2->setEnabled(custom && ft.area); + + ui->labelSpinBox->setEnabled(ft.count); + ui->spinBox->setEnabled(ft.count); + + ui->groupBoxBiomes->setEnabled(ft.biomes); + QString s = "Location"; + if (ft.step > 1) + s += QString::asprintf(" (rounded to a multiple of %d)", ft.step); + ui->groupBoxPosition->setTitle(s); + ui->textDescription->setText(ft.desription); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(filterindex != F_SELECT); +} + +void FilterDialog::on_comboBoxType_activated(int) +{ + updateMode(); +} + +void FilterDialog::on_buttonUncheck_clicked() +{ + for (int i = 0; i < 256; i++) + if (biomecboxes[i]) + biomecboxes[i]->setChecked(false); +} + +void FilterDialog::on_buttonCheck_clicked() +{ + for (int i = 0; i < 256; i++) + if (biomecboxes[i]) + biomecboxes[i]->setChecked(true); +} + +void FilterDialog::on_buttonCheckMajor_clicked() +{ + int majorcnt = sizeof(BIOMES_L_BIOME_256) / sizeof(BIOMES_L_BIOME_256[0]); + + for (int i = 0; i < majorcnt; i++) + { + int b = BIOMES_L_BIOME_256[i]; + if (biomecboxes[b]) + biomecboxes[b]->setChecked(true); + } +} + +void FilterDialog::on_buttonBox_accepted() +{ + cond.type = ui->comboBoxType->currentIndex(); + cond.relative = ui->comboBoxRelative->currentData().toInt(); + cond.count = ui->spinBox->text().toInt(); + + if (ui->lineRadius->isEnabled()) + { + int d = ui->lineRadius->text().toInt(); + cond.x1 = (-d) >> 1; + cond.z1 = (-d) >> 1; + cond.x2 = (d) >> 1; + cond.z2 = (d) >> 1; + } + else + { + cond.x1 = ui->lineEditX1->text().toInt(); + cond.z1 = ui->lineEditZ1->text().toInt(); + cond.x2 = ui->lineEditX2->text().toInt(); + cond.z2 = ui->lineEditZ2->text().toInt(); + } + + int r = g_filterinfo.list[cond.type].step; + if (r) + { + cond.rx = (cond.x1) / r - (cond.x1 < 0); + cond.rz = (cond.z1) / r - (cond.x1 < 0); + cond.rw = (cond.x2 + r-1) / r - (cond.x2 + r-1 < 0) - cond.rx; + cond.rh = (cond.z2 + r-1) / r - (cond.z2 + r-1 < 0) - cond.rz; + } + + if (ui->groupBoxBiomes->isEnabled()) + { + int b[256], n = 0; + for (int i = 0; i < 256; i++) + if (biomecboxes[i] && biomecboxes[i]->isChecked()) + b[n++] = i; + + cond.bfilter = setupBiomeFilter(b, n); + cond.count = n; + } +} + +void FilterDialog::on_buttonArea_toggled(bool checked) +{ + custom = checked; + updateMode(); +} diff --git a/filterdialog.h b/filterdialog.h new file mode 100644 index 0000000..a401516 --- /dev/null +++ b/filterdialog.h @@ -0,0 +1,48 @@ +#ifndef FILTERDIALOG_H +#define FILTERDIALOG_H + +#include +#include + +#include "mainwindow.h" +#include "search.h" + +namespace Ui { +class FilterDialog; +} + + +class FilterDialog : public QDialog +{ + Q_OBJECT + +public: + + explicit FilterDialog(MainWindow *parent = 0, Condition *initcond = 0); + ~FilterDialog(); + + void updateMode(); + +private slots: + void on_comboBoxType_activated(int); + + void on_buttonUncheck_clicked(); + + void on_buttonCheck_clicked(); + + void on_buttonCheckMajor_clicked(); + + void on_buttonBox_accepted(); + + void on_buttonArea_toggled(bool checked); + +private: + Ui::FilterDialog *ui; + QCheckBox *biomecboxes[256]; + bool custom; + +public: + Condition cond; +}; + +#endif // FILTERDIALOG_H diff --git a/filterdialog.ui b/filterdialog.ui new file mode 100644 index 0000000..a409113 --- /dev/null +++ b/filterdialog.ui @@ -0,0 +1,310 @@ + + + FilterDialog + + + + 0 + 0 + 600 + 505 + + + + New Filter + + + + + + + + Filter type: + + + + + + + + Please select + + + + + + + + Description + + + + + + + 0 + 0 + + + + + 16777215 + 80 + + + + Qt::NoTextInteraction + + + + + + + + + + + + Biomes + + + + + + + + uncheck all + + + + + + + check all + + + + + + + check all major + + + + + + + + + true + + + + + 0 + 0 + 560 + 63 + + + + + QLayout::SetMinAndMaxSize + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Location + + + + + + Instances within area = + + + + + + + + Word origin + + + + + + + + 1 + + + 99 + + + + + + + Location is relative to: + + + + + + + Within centred square of side = + + + + + + + 0 + + + + + + + Custom area + + + true + + + + + + + + + X<sub>1</sub>= + + + + + + + 0 + + + + + + + Z<sub>1</sub>= + + + + + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + X<sub>2</sub>= + + + + + + + 0 + + + + + + + Z<sub>2</sub>= + + + + + + + 0 + + + + + + + + + + + + + + buttonBox + accepted() + FilterDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FilterDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/gotodialog.cpp b/gotodialog.cpp new file mode 100644 index 0000000..0fbb98a --- /dev/null +++ b/gotodialog.cpp @@ -0,0 +1,39 @@ +#include "gotodialog.h" +#include "ui_gotodialog.h" + +#include "mainwindow.h" + +#include + +GotoDialog::GotoDialog(MainWindow *parent, qreal x, qreal z) + : QDialog(parent) + , ui(new Ui::GotoDialog) + , mainwindow(parent) +{ + ui->setupUi(this); + + QDoubleValidator *val = new QDoubleValidator(-3e7, 3e7, 0, this); + ui->lineX->setValidator(val); + ui->lineZ->setValidator(val); + + ui->lineX->setText(QString::asprintf("%.1f", x)); + ui->lineZ->setText(QString::asprintf("%.1f", z)); +} + +GotoDialog::~GotoDialog() +{ + delete ui; +} + +void GotoDialog::on_buttonBox_clicked(QAbstractButton *button) +{ + QDialogButtonBox::StandardButton b = ui->buttonBox->standardButton(button); + + if (b == QDialogButtonBox::Ok || b == QDialogButtonBox::Apply) + mainwindow->mapGoto(ui->lineX->text().toDouble(), ui->lineZ->text().toDouble()); + else if (b == QDialogButtonBox::Reset) + { + ui->lineX->setText("0"); + ui->lineZ->setText("0"); + } +} diff --git a/gotodialog.h b/gotodialog.h new file mode 100644 index 0000000..1e75c2b --- /dev/null +++ b/gotodialog.h @@ -0,0 +1,28 @@ +#ifndef GOTODIALOG_H +#define GOTODIALOG_H + +#include +#include + +namespace Ui { +class GotoDialog; +} +class MainWindow; + +class GotoDialog : public QDialog +{ + Q_OBJECT + +public: + explicit GotoDialog(MainWindow *parent, qreal x, qreal z); + ~GotoDialog(); + +private slots: + void on_buttonBox_clicked(QAbstractButton *button); + +private: + Ui::GotoDialog *ui; + MainWindow *mainwindow; +}; + +#endif // GOTODIALOG_H diff --git a/gotodialog.ui b/gotodialog.ui new file mode 100644 index 0000000..f8820d6 --- /dev/null +++ b/gotodialog.ui @@ -0,0 +1,101 @@ + + + GotoDialog + + + Coordinates + + + + + + Coordinates: + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + X + + + + + + + + + + Z + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + + + buttonBox + rejected() + GotoDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + buttonBox + accepted() + GotoDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + diff --git a/icons.qrc b/icons.qrc new file mode 100644 index 0000000..9cf4274 --- /dev/null +++ b/icons.qrc @@ -0,0 +1,31 @@ + + + icons/desert.png + icons/igloo.png + icons/jungle.png + icons/shipwreck.png + icons/spawn.png + icons/stronghold.png + icons/village.png + icons/mansion.png + icons/mansion_d.png + icons/village_d.png + icons/desert_d.png + icons/hut.png + icons/hut_d.png + icons/igloo_d.png + icons/jungle_d.png + icons/monument.png + icons/monument_d.png + icons/outpost.png + icons/outpost_d.png + icons/portal.png + icons/portal_d.png + icons/ruins.png + icons/ruins_d.png + icons/shipwreck_d.png + icons/spawn_d.png + icons/stronghold_d.png + icons/426997539678997085.png + + diff --git a/icons/426997539678997085.png b/icons/426997539678997085.png new file mode 100644 index 0000000000000000000000000000000000000000..9f03ac3dd25bfe1b991da84c2629f912aa56daad GIT binary patch literal 1270 zcmV5&+NG1h>3=XQV%J8A!cj)0hr#`C`XhH4l%v2 zQI_`p#<#z4u?pN3$EGq6hwNi18EtX&twvrpIK<^sZ<&rsssJOpNX4-!DvkvJFqur2 zJ(?i1;}`39{C!EHlC6f>+F(WzZnYsu?WJ5ONhHYAL5+T?u94hftIQ602nK*W9Ri@ zga|DMQe>IPt#9gSP@VX{WE?B3V?LVASYaJMecx)P5fCA?cV6oer^A9SL?ta79MVok zQ9~9h&py`H+IfALZ?5&rc2{@6bf>#`LVGXRL`-2HS2Eh-$b;kC?sI(GeTVI$6gd3p z*L-+p0057=w}N<2`!3)D@#4(@7|})S7gJkXoRZ!aX6eU{=G#l0ugaBek$?bfOjXB+ zmvOz&w9&lb&z7Ah_SEphxe+PktBW@+7h=lHBzk*&vcCU=v&>$4L`k&=K-SMcldH{n zWpII#YLEQx%05`6or3Mr6-jJF7x4%40Jw5@j+R6Xzde}e<(%~na)H@Pj{qdOi2kFE z|2P5woDOV{t`INYoU|SM!90xUBDdBW0F)bLOb)!5934rPAl1JqVu*1pdzJkYivofx|d?gHZ>PdW~33!T_gL*b(* zY01}BJ%x9D0CQ7y@`Y*L8o^RW#2>@1gL6E5;>^b4_#eEE{S)nQJ zk`5;LN-`@KvAtsGc)=nairEhj%2rMI^N z)9fMjcnxWb3O9=La{$shAcVOA0{$R?6w&VYN)aM*QQGS%Cqhcy5^=KgfK13t$I%nJ zvp~QfBxXbj_=B|jz1Dkg!*dSB!im6+9RQeS4=z_F5K7jMCIv*aIdp2i4KIYDVcE^%`!AjN!k*2AzLywP|(2`VIA8-gaTt^LGA&JiXtGj zWNdDrP>`F2j!SKOXelj}w)7MVyedDTbtC}71pp*J1mMleKJk|y0kG%o?9X%4h=onBFo^PT`y`G`?u5i4ToA&! z;Wc4+ZQ8CGb{K|TrXibl#I(|{fq%B@nHDV*F%Fn@Oh3l;dxSnW$D(JNRy$P-x7CZy zDtYS)Ojj=4fn|4=WH%SZ*A(Jv?b2PXx?3^Nkc)5ZY$*b$VB0TPQ8qmbD>rb7-z}UM zRIFpuh-m~d_Sy737%~_@**L;N^h~+9%Io0R^h~#9(2qhkT|a6abXf*)#}sO1g>VS7 zKUU7ydL2`Uq2FnIgj!f`%cM-)pj+-(f!kMMI)+Sp-O$2Jl}gsO=Hj5pgv~I3+9v-9 z$u}_mRzRv&cEGZ>E#LI>utc-;+jellVP^T99G7v(Z5dK6T;EueI874{Q=iv0ZdhT2 z*95Ti2JU3z4z|-cpq#4-VUXL(M(u2??vYGX5kR?^b6g>5^dJ+k^tx5ot5LNEJdnfG z>#_~06|E@pL?x}2%~U=Oiq@Qy%ktJa!Bu#^P9doe`)0k)vF*V8IyS3O(Kmh2R#53Q zv(?J>AjS{i65PSHYWr-uA;iG48^>(=UYV%c@8M&}gu^(ZlDGL?9G7`y9fv#!S2|N4 z^gwDw6NZd?oooqIj@gH$!mFFU8SN4svkxxGTjb&fzv~H(jL1Y)8u;#phrjI?D(9-6 z24{D7cgf$yHWFVFLA!i0Rgf6-?PVgoR+;`y4FEC9e-+VnF6J$wJW!jFnRdW`FfQ&) zVp-M{mtc-(q^9H^H5V0}I!2;U$Pky$)jN^*F0H&fUO-N&i_T1-Qp-=|Q%`(MrE(uiZx4o`qrvX(ahpvj ze(_TbH?Oql>-x`%i+}v$hl(q^E%aDkM#-;ZqTozc|9fSxI(i@(4O(B;^uOOESEMl> zEGOg(kDnLP0^`;R`(*xu`J0QXOy*4N$z?!U&5oBm%P%5M7hju}S6f_ITasK>^hHDVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r2R1cVuy_d%DX%mVIng;}bfo;x+HC@m*uRw3)qIbyjHY3;HhOAs@wI zBcxR&I`OjC3ORuY&5UPyj;~*NTvrfSwf9iP^_B}?*ReEio;x#!y{Udzu~y#it_vGF zoPL_l@m?+<@IgXp{=$n}eTyz6yzNq2xBSee8>`K#Bis7^e=t9h{pt<}%e2nB=h?HO zRZs4$&$jHDe(QR4=bQCg6PcNeElyrjvA+D(^1XsV%KyFFOjyIu|IOPSxZCyql|4Og z)OwvxA3V3#r{l-U8)n;>crVB0Y%-D8*`y=eF2kZ{*FSvFVdQ&MBb@0LUl5c>n+a literal 0 HcmV?d00001 diff --git a/icons/hut.png b/icons/hut.png new file mode 100644 index 0000000000000000000000000000000000000000..2b0c01edab6b03b97ec37983a885f97eb24f42fb GIT binary patch literal 644 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz7!3-q58RK68DVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C*E$;1l8sq?3F8fBXG^ z!M^`Mwu_64PUL@n$KRQmnQ7_i>1hxGNP%E-Qc^MyCnbX@5CNj%Kp;LoE-n%XqJRj5 zLx3O{2tkxT5cmP1zn8a{mzOsPKv`by?(S|7?B?d?4#aK_!84u%-N9HAlmS>sbD^K}7w@~@ zrAdbq?uzA9f6+|lSup$9GJm7Xw`FWzCd^$_$amoDk)44?)0w1W7B^>JWfuLYeCVZn z%e|a;KO612ZQ{jsI1WG6>N;f6by$V-@z+TPzL$CXaIcYvMoNP3C z=Dejgg7fajuCow+<{Ei#RdnE!?^@?qPYPHWWmK!QCCaMSGigKHNCka-r`T* z>iXYZ|GzSs%8SUUu)LoM^qOjkYeY#(Vo9o1a#1RfVlXl=GSD@!&^0m)F*33;HnuV{ u)HX1(GB7xFRj3_BLvDUbW?ChR216?YGq{Gr&g>094Gf;HelF{r5}E+YeBM9+ literal 0 HcmV?d00001 diff --git a/icons/hut_d.png b/icons/hut_d.png new file mode 100644 index 0000000000000000000000000000000000000000..c910881212831829656b8fae6688a5596056dd4d GIT binary patch literal 489 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz70VEhAw|rm$QY`6?zK#qG8~eHcB(ehe3dtTp zz6=aiY77hwEes65fIJS9 zBkO0({8OIxDV4p~Eznr3X7k}}&HRjcZntwM-M9%9vUtw0IGvT>$#MI(HVIGgmrTL22 zexCn}X-A=x@QLddn}I%4Epd$~Nl7e8wMs5Z1yT$~21W+D1{S(Th9O2qR>sCwh9=qu vMpgy}a!FQ)Q8eV{r(~v8qG&L*GBmIUftDnm{r-UW|v*M>k literal 0 HcmV?d00001 diff --git a/icons/igloo.png b/icons/igloo.png new file mode 100644 index 0000000000000000000000000000000000000000..ee8eea302e683237656e0bc20ce720bb7ca7d381 GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAd3?%E9GuQzsmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5ln@H=32_C|K=A+n|Hq#{ zzx)5ceah+co(^I_A;yv*zhDN3XE)M7oFs2|7p64sniL?1v%n*=n1O-s2naJy)#j51 z3bL1Y`ns||;Sd$oW`E)@#t9V4_H=O!;kaJfcaXQifQLDeImpm4h)v-o)0UYHf7=gc zPJF;vH`j04)68Goiu}CuG*`ag(h@VZTUO-Krdd`ds^5=ITd28P@lHb=s|H(&rs;N} zur$S;_Ip=|P53lJ~K z+ueoXe|!I#{XiaPfk$L90|Va?5N4dJ%_j{MWH0gbb!C6TAu6h^;--;a4HTN@>Eak7 zak=+`;i|(9Jgyg$li6a&j{)pXPAKvW3&7Kff~yW|_s#?V4RE z!yl2+5*ln2cCyINzsNsq{gpj`JuBC&_TC_#HtX9&)tNi3HW|mxFEsl9=gIm%%rn`I zbH8%=rU6~3TH+c}l9E`GYL#4+3Zxi}42%qP4J>qx3`2|ztxSxqjLozSjI0a{;x$8W dp=ij>PsvQH#H~TEWA?E)!ikIy(CM`z-=3N=r*;&6;KI zZEja?=kM=t?rz@Q-EHn~-rCw~5n*BOW4>j}7V8Y_prD}8(9mVemTlg=xvj0usncop z?Aea3j+U{O_4W13moK*|wCU>VG7m6!?sYzJ;6P1Hja`*pPEJmGdb(|?tyPAVRjQSJ zt^I-p3(CvOZA)xZQc^7AEUl8QETb#~0|WQ$*<ul?#OP5-uSXo9|S_E6T zy1Et?7TVX@Pna+vGc(hv%W3lD$?fg!GiT0pYKfk4=rJmz#kc&H|6fVg?4jBOuH;Rhv&5D9B#o>Fdh=ghNzR&A1?R z&PSjg22U5q5Q)pZ7hVP(au8riIL0$~0o$UEb&I-Pd_@!!TpR^m4*queaO`)*fAygC74GDbS)|y!*r4--QS#wiZWSWic>9VENlJ$wo73Veyz-=`*nusZW}2iu z=b0uTc_w{fzVC$BX&W}|c=Ge~GNbFqz52pGw=Rf?^0wNge`ME$i=U3AMLc{pZQGZ! zc@M6Zy(*mcv~5$Y$=ibdZ>!gSJDAuwWu}O>Rml|_yMvj{kvAg`%vn^-r=N0q&z0W= zPvgC}S?TPNc6e3$$UgQR=Pq8!s?T?8zdaP(^!v*h*>~5cevOa2-&D>p=Y*u>!Z(XQ z{rsw%5N*FT?)P?+ACvzxbi9*i+_6!89WZ!QOI#yLQW8s2t&)pUffR$0fsui(frYM- xVTh5Tm9d4DsiC%kk(Gf#uT{@-6b-rgDVb@NxHbGqHvvTegQu&X%Q~loCICe&QZ4`h literal 0 HcmV?d00001 diff --git a/icons/jungle_d.png b/icons/jungle_d.png new file mode 100644 index 0000000000000000000000000000000000000000..00c207adee70286445388dae7077cba1ad7e8a7c GIT binary patch literal 579 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-0VEi_>>^hHDVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r2R1cVux0;8V-)X1rmAgCa?VZU4!Go)^I_;S(myW zT#SC(H0Qu&4jIw3zp+cLrhGG(&*HG?NnG1sqCO+&?kx=#rmW&8ExB!%+O}TUxw4t5 zjQ4dUZ%N+v*LC*~{J;L)mNQMZ`qJ+BZ=rQT3hD({bN)_lo{?zI^LovE=L12<-hIF1 z>g#!G;%9SNmK{&MuDCBr>3{kyHh_)QYVrk-7ngi&-g9n`GU7O4(*4x2@>9r(cVDj; zI5@ak$J!KX-|G&{Zq{XF`qO-`Ci80H`q`_NPHkJzuyc~M^6x{%Zr3NxsgQd)e||k< zV_SXOilt}lfPt!7;u=wsl30>zm0Xkxq!^40j0|)QEOd>K7(8A5T-G@yGywoZ9NaPh literal 0 HcmV?d00001 diff --git a/icons/mansion.png b/icons/mansion.png new file mode 100644 index 0000000000000000000000000000000000000000..e1dded2838a79b24215069dea743eadd9adf2ed3 GIT binary patch literal 414 zcmeAS@N?(olHy`uVBq!ia0vp^B0wy~!VDx`*CzS_DVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C&q?@Ck7R(m*hE_Ux_+ z6D%z)C(oSu|NnoZG@FdPypF!U3Dc$pMMMBa_DxDv0IFjw3GxeOaCmkj4a7vL>4nJ@ErkR#;MwT(m+A>5>H=O_9vXIV#-P!Yde6BUAmooejz}-WLZ$u&W(kYvxKxZCqG-&*LPv2 z?29vZWo37FCNnv@v)?%$peAr2gGHD@ILX>}4_D_kpy{e5t`Q|Ei6yC4$wjF^iowXh z$UxV?QrEyV#K_3X*wo6{MBBi~%D|v)o8Utf4Y~O#nQ4`{HGF@)G8U+T!PC{xWt~$( F69CC!d#eBd literal 0 HcmV?d00001 diff --git a/icons/mansion_d.png b/icons/mansion_d.png new file mode 100644 index 0000000000000000000000000000000000000000..4d5deebfb7d63010ddb456d0058d2e32ae96f744 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^B0wy~0VEhEy-;2Vq*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!uqB*-tAfuU^jSql&^ z$=lt9;eUJonf*W>XMsm#F#`kN5fEmas?8@26l5>)^mS!_!XYZkDl4S*EFLJ-<>}%W zqH%uhMP0r_2?8w-moHH)Rha0J7bxO5aUSb&&K-=;GXi+I@*D(Y7_KVUI4V96Zu!S~ zEvM{Ln%Yd?qW#8M7nbDgZ+URRODKiu#4X$Q?#weXPGJ#s=T=?+oYNZI|NPdmbHc*a z?%&glPKu@-lbgYvm74zRtVnow{gx?5^2Ic)Pk!(Evvh6^W7RCt)Tjd=9spgXTH+c} zl9E`GYL#4+3Zxi}42%qP4J>qx3`2|ztqe@9j4ZVcjI0a{Zt$pOplHa=PsvQH#I0dv S;Jhn94Gf;HelF{r5}E*tD203g literal 0 HcmV?d00001 diff --git a/icons/monument.png b/icons/monument.png new file mode 100644 index 0000000000000000000000000000000000000000..f3f5e068533de30ba33996a6f090c559d5dab796 GIT binary patch literal 1189 zcmZ8fYfMvj9Q~tqIEiK(A_GRorXV7OzN~U-f!Y>s+k4vr7yIH~F1>9BZEtU1n9-I} z>Vub#O~A=(Bbmz*i0==ZH7=O>7*%H`j_NWOKQP^N#LdmP%^7yhK5fbG{7%lvIbVMH z_3AV=t5;>O0syR5!ty$n*FML}@s<>o0&rp=?eAs|tMe$h4h0}s06=s% z0DrMo^hW^NxB%QI0e}txkm(tZ*NOpHX4WB?f(;}s1)~*E4bx17+%g(hdYcgsb*VDz z4@VyCCac|q%H4#A8Q5pJKnYL6rITpIT_TT1Y6XN!-HHowLF%wRV&(dQfOk4_$}q%g!m z2nd9k*hlY=S$o=dMBJ#KLIPGntpW?X8uzx*@9nYov@2Rokflz9qe5I0JABB#FWeY* zs{$0>!4MIjn9!I*v?=H^MBKYZ22i>QX(8N0J&i%P&Sy1uFnEX|!#>n+(d@K0hCNh& zm`A9^R;*T8Y3~d1&?>%0%Ga?Y7wTj}q)MROF4SyCy_R})WjWRGv^Do)Sypp@Q024C z#E;C+UA3)$eg69F=ckX<`Rv5ow2UDZ?b#2oTq5GB_uG;;t|aF!&(B?!dU3=-HgqyX z*fVo_Dmj<<=IccA`s_^ns4>K#Z8j{zRFRl~)WR;@5b?l1QtfTl`mN@68ueP6LSAE+ zcN>nBQFWyxigyLnehMPB#tyfnNoVf#(7hd|n6JuGFT!9+Q>`&Xi+0oyQLny}k=U@U zq*g)Wj(0oFU0!Ez#L?GD1U=4ysMv(G>5a#%ApphOj^3oA1+US45%IKt*q z_)57Hdv%T?^HR8c-l^j!Pxw7P?{B|6`ZX=e;q2M1kY6~Tzv1JOSsg9Rb=eRz(O_BgYPlUZtvyZ-FEdtmtL^p4WT z^b_lfKXX5O;o1H%ky_=t6{nxRGjV1nw(QK4pKpJ<1U6neC+i+~O|o-*h)f0FJ;Ke< z8+XpK2h9&C3;}C%zy?u18%u!C<#S88+e-L6jL(Av+aMlyGnWr>xyMp_fBH{>W~{CD c_Wu=lTOqFS--7n$lOeVMlnRaf=yo#p4M-N9QySVymVb?_T7ABm!=Fg4?|_MbiMl?VA!daxJHzuB$lLFB^RXvDF!10 zBLiIn3tc0_5F7^5P$T(A5a5> Mr>mdKI;Vst0AoJLs{jB1 literal 0 HcmV?d00001 diff --git a/icons/outpost.png b/icons/outpost.png new file mode 100644 index 0000000000000000000000000000000000000000..15622224c8cbbd3dc1f671fa6994a8d4628185eb GIT binary patch literal 541 zcmeAS@N?(olHy`uVBq!ia0vp^Qa~)t!3-orPvl1eDVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C%fe@Ck7R(*OVe2a;1~ z&+eKq!CX_IG(xd0Rn5}U5-6^%z~gQv66q|Nk(cLbCfb>;OkH}&M2EHR8%s5q>PZ}u5UgGKN%Kn5yR8&>s z`w_DupwK~27sn8d^J_2N=4)2qVM{pf_(a_G-v89=%^jwvH+7o^a?5L)zWg#v?Yp8{ z@A@acEg!U75AYwa*9g)mOw07s>{?=Cud#BYigS0)(u%ri)}_H4OC(P>ubb7a`o8#` z_|rFk6%IVtxO&9KAfvTxt=s890oKKpQ5McRw~wV|E?4z9;qdZa=Mp&$?HL=yWJ1q& zUSZpCk$Y{I<-0k3rd|hKu3H!t{J-MlI9c;i(%k%_i}k(Ggg i%9)Q-P&DM`r(~v8;?@v4(NhDcfx*+&&t;ucLK6Tyv$RqG literal 0 HcmV?d00001 diff --git a/icons/outpost_d.png b/icons/outpost_d.png new file mode 100644 index 0000000000000000000000000000000000000000..22d860883d03948125943306e18e2c05c8d350a4 GIT binary patch literal 646 zcmeAS@N?(olHy`uVBq!ia0vp^Qa~)t!2%>t$cH}wQY`6?zK#qG8~eHcB(ehe3dtTp zz6=aiY77hwEes65fIz`d2;f#z;Uj)Zj{W#Tl9JSU((QQY{* z^_`WWjhkhD-}}7(yk-B>*(IKr4By*m%Lz%Sp4aJrob>z0KcAaY^6lF%-hcf@qgD8L zEuW!X!#~fL|8MVp|9N-D+l}Y*{my^6B=Oz(CMThPhwY2&ARfio~vZ-#BAls+q2fjEVj#av8YZHOH}Xm2raTN z$Ye{~mpO6ulHXpR`&M>JPmNh6yDfTU%+q5>LUwph@|`s+V&^9n$5kJ>;+IO5Amj`|M1USYhrX|QswlMfiIPwqx3`2|zt&GjBOpUY+jI0a{d~crJj-nwqKP5A*61N7MmQrq@1_n=8KbLh*2~7a2 C-~@yK literal 0 HcmV?d00001 diff --git a/icons/portal.png b/icons/portal.png new file mode 100644 index 0000000000000000000000000000000000000000..c738c6baa70072c79f911416f5343a0e795b4c1c GIT binary patch literal 515 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD3KH36XFV_Q+Q6-N?!|P z+3U`@txfA@pV1vx#%)0HkB|bW2on&H`1~y3^BH4BX}uP^YqP!XEe(ItW`Dij z@j|=9_3EJGO$mn^Vve-CUa9duS?hAX+Usx{93p^r=85sDEfH31!Z9ZwB zAbW|YuPgf#4pC7)ZM&3Jyg;FBPZ!4!iOb0e3X%m3jLSIG#b;TtoAdMQ%k$gwcPa4O z+uH{;NxM#%@9q%LAsDN=eZmF~;aOgJb%BP3X?b-fZ-piW=}3rqo<6Z@QTFqRlfx4Z z2dvasedJtonwmYox&4KWEwcRVOWIW?GB9kp!M1pjc*#egwW=kq5hW>!C8<`)MX5lF z!N|bKK-a)R*T^u$$k58z!pg`<+rY@mz+jvBd}S03x%nxXX_aUi3{9*|j3FAHGSxf= PYGCkm^>bP0l+XkK3&N&f literal 0 HcmV?d00001 diff --git a/icons/portal_d.png b/icons/portal_d.png new file mode 100644 index 0000000000000000000000000000000000000000..4193b10c3e1c6df146db58674c3b4d1af48b407e GIT binary patch literal 468 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc0wmQNuC@UwmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+ z;1OBOz`%C|gc+x5^GO2**-JcqUD=;-h>G%yK9g&E4iq}>>EaloasKKBL$4zN0>?hy zFJ^YE*^?)IRH=eNnqM)ZN=W~)jj|x-u&F z|Fdr^=7#A=c|Je-?Oa45gW0BV$G1OMPF#L)O~iuDb*E3wDcy4I^;_9LNwGR1S)rn5 zWiEF$`F$veKeF?C!ODqKA1i)Kc3+g!nPj(~oLt`(Xa)3=YKdz^ zNlIc#s#S7PDv)9@GB7gGHL%b%G7K>?v@*7^GBndRFtRc*ICFi^WfTp$`6-!cm1r6a aO|6U!AsV6$_<@1Sz~JfX=d#Wzp$P!a(xv$T literal 0 HcmV?d00001 diff --git a/icons/ruins.png b/icons/ruins.png new file mode 100644 index 0000000000000000000000000000000000000000..85b853a5c2cf0c17c630b133d04848bd8b2cadd9 GIT binary patch literal 642 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBDDgbNC&U#<1Hr1*%hs-4 zxn#+bO`F!QU%z(uo}HUFZ(O%-bzx!ty7eoYnwwi%nwy&18XB5gnmgLsy6Wm%;^X7i zty?>5*3=0TCN(v86cm&f7gshnxApf=3=a=qx^!u6ZF7BnX*`v&yC+PXIJvpGqoJX7@7|p?HH~$3jZMw%jZJNZMdeLRK=HPu zK^@r$?uy zMa0AeB_)N%#YIF%MgRZ*KU8H-0nnd}B|(0{3=Yq3q=7g|-tI0w-}41N138=p9+AZi z419+`m{C;2s{ts;UgGKN%Kn5yR7^oZ6~f(b4RDvoMtVqs=w ze13W_!$`N#CQ~a zlgl0eU7%Xx8c~vxSdwa$T$Bo=7>o>z40H`Fbd3x{jEt;|jjT*ev<-}`3=DQU#Lq<0 ckei>9nO2Eg!wK2vsz414p00i_>zopr05G8C_W%F@ literal 0 HcmV?d00001 diff --git a/icons/ruins_d.png b/icons/ruins_d.png new file mode 100644 index 0000000000000000000000000000000000000000..a4d9b47d0433308b2fb0aae520790dab7d3d915e GIT binary patch literal 417 zcmeAS@N?(olHy`uVBq!ia0vp^A|T8GBp6maa=HklSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+ueoXe|!I#{XiaPfk$L90|Vb75M~tB@M-`GvX^-Jy0Smv5EWBoX?-u402FHTba4!k zxV&|OA>Sbb0hZ@E?kD_I90gm>*>`N(7?#pM+6=Ekyu03Ub8+jGjc>^^$%y(&Zd zS$+n#hQ%zi@5r3Dl@1YC%oIH)6rP%rKU3~v<*Ft7b$f#oOJ5thZTK~F#p~D;S^LuL z9$35-iB{d%w(RhhO-q*Dv$=a%ulnyg@f-gihTng|`bbMH-$HB00icUiOI#yLQW8s2 zt&)pUffR$0fsui(frYM-VTh5Dm9epvfr++(k(GhLr4v@MC>nC}Q!>*kacfvSwdo;H O1B0ilpUXO@geCyCG=?|; literal 0 HcmV?d00001 diff --git a/icons/shipwreck.png b/icons/shipwreck.png new file mode 100644 index 0000000000000000000000000000000000000000..0f4d73a77158830aa7bad91388f2c142dd0a08b7 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc0wmQNuC@UwmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+ z;1OBOz`%D1gc(IOyc&Rl>?NMQuIx`ZM8)LwGp2ox1qu~-x;TbtoIe_9D0D!9gF9~i z$I+swr<^jxqnH@-e0Y)_v8cJ_ugt(-mbgZgq$HN4S|t~y0x1R~10w@n0}EXv!w@4QD`P_|6LW0?BP#=g~`Y- literal 0 HcmV?d00001 diff --git a/icons/shipwreck_d.png b/icons/shipwreck_d.png new file mode 100644 index 0000000000000000000000000000000000000000..46f547439e71b9dfb30c08b2398087e324b410fb GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^A|T8GBp6maa=HklSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+ueoXe|!I#{XiaPfk$L90|Vb75M~tB@M-`GvX^-Jy0Smv5EWD4eB>H42Pl-`>Eak7 zae3*4gS-ri94yDXTXPwwcr#8Z<99fJCs=M)&xX@n;ij+580;4+b}aL^?Q*caH$mZt zCx45bDP46hOx7_4S6Fo@?*ia+WGRI3}{6XN>+|NlRKTY)?X z*bh`EFaMN}@8k7Op!l!fYiqjy19eZdW&o-@XCmAHRL4>hY zzWlug{y+|Afk$L90|Vb75M~tB@M-`GvX^-Jy0SlEViRGKJG*D~QJ|2Ar;B3<$Ms}` zgoFY{CN_=+W7gOhr^_)hEP*o}!YVB%tZg{MviU%ch}sO+q{Rl-X-x;UOrE`)nf~tF z9bcJZwGK<+UQTuoABNC)zxUdl7FU60s+PD$l%yn}eUL&Fe5 zODkhDD^pW#10yQ~10fevQxpxk`6-!cl~4^vx&~$;Mg~?urN#!@28KWlojyl_;mW|^ M>FVdQ&MBb@0BSOJ@&Et; literal 0 HcmV?d00001 diff --git a/icons/spawn_d.png b/icons/spawn_d.png new file mode 100644 index 0000000000000000000000000000000000000000..35716c5fa54849474ac73c2b8734668f7d3cab80 GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VTavfC3&Vd9T(EcfWS|IVfk$L9 z0|Vb75M~tB@M-`GvX^-Jy0Smv6cdq^-02-w1{7N8>Eak-ar*3KTdpGt0&Ne&V+B~W zcgv_xyJorI36EUhfh1k+b#{^Ah7Uz9*$c~+|O?=+ewZ$rX{VT%4CYy=xd**!qJA=kjJ<|=> z{&`xcKezd-vV4D14}(N9gG5_m;P#tOuN``Be&zopr0K%bUumAu6 literal 0 HcmV?d00001 diff --git a/icons/stronghold.png b/icons/stronghold.png new file mode 100644 index 0000000000000000000000000000000000000000..ec08bc6debfa715eaa5be6126beeacb22ae345f2 GIT binary patch literal 753 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3-oLy1%&rDVB6cUq=RpjeRx011AId3dtTp zz6=cQ{0s~|K>Qb|snLOfp|+BN;bk`igUwS02FWwUHm&7AwXXtvLR^9Lj2Sa(dg7|O zVl(R;v+JF+>Yb-dnKEnEtlr+<-0G0rs*r?)gpQ7m)FPka;^O-H`pU}6xB}~#Oy`37 zpyUG2phWY)c+>P!zua2?#O#2?Joo4fr?_2nq@R_WFD`T6-Ng-Mt4=*YB(ssA6t7A=~KD(Z12V zi{s8(){Yr6TlaEu-U#v%T=+(&e6`%fpa8coR+`n_F$-&YSmp)PKhTw4RyptW#TQ?4 z=A=zE{9O6_(WkD*o)vfJR_)%IdnT>?*}sPye}>f03pW-!*uUS!jn=OHDz>i2 z(B|oV&mF&#@BNwlpK(i~RM#?Q0WqN0RZCnWN>UO_QmvAUQh^kMk%5tcu7QQFp<#%j zrIoRnm8q$=fsvJgfsl)-DT;>N{FKbJN~i`ST?4ZaBLgd-Qey*c14E#OPM@Q|w93HX M>FVdQ&MBb@01i7E-2eap literal 0 HcmV?d00001 diff --git a/icons/stronghold_d.png b/icons/stronghold_d.png new file mode 100644 index 0000000000000000000000000000000000000000..1d1ff8a6d40bdf3101e73d8eb473013d274c5fcb GIT binary patch literal 645 zcmV;00($+4P)w78D)^A8rUh0006cNkljf*H~|OfNqWcLgCznaKtMzwrj5smyBMkO1-(@l&l@{VexA&H-?;bo_6ENi z41PN}P4DmT`~BWpd%a$Jy&eEer_-O0T-Uwd@4Ma3T8oGuA0K~6rBocp08p0Y%gYM@ ze0_bDWhtcu07P_MH%-&cX7d()2!EP+8K>TRaF5%9LMMLIZ2YH zX@n43>ke6#?T6ov2mRBCJOqZ5oVi0C+u@B5zTMNuTBTr3tsh}mrR z`uZBj@o+fIvTQz|cgWFbgosVkc%ByoL6Ria+N!F?^PKUY=77-NiSt%t)QB5pRD?RNVQ-;&%x5Rjx%6j^II=R%0j&(D6pKbcIT zC?ZRANJJ!IPNx$h8e^=rB#e|&N{NVRnjQ{^N78lOIF5}mQp)S~N@r0PLI_#_K%VE1 z9wKK5A@28k7>17HR8_TFtvKht?>9|D+L9SNq}KX&yG2n%U1F_m+m@VE*R{2lTuG5T fB(>asx)Z(uOmsi;j;QL;00000NkvXXu0mjfU^E_- literal 0 HcmV?d00001 diff --git a/icons/village.png b/icons/village.png new file mode 100644 index 0000000000000000000000000000000000000000..a4da8bb9d21d5e00b76af36b4e0cea2b37e0cbb3 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^B0wy~!VDx`*CzS_DVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C-DT@CkAK|NlRb2?Tq) zi?&v$8>4lj*;?#y5fLm`Z~Df*BafCZDwc@)(o6 z-CYq1NW)XRGe+K7ZzoL;AhD zdw=Vx6+e4-*ZAU0hYi){ca8_B2{dFlI@yS97-QY`6?zK#qG8~eHcB(ehe3dtTp zz6=aiY77hwEes65fIM;}!-VRJP#e>su$cFj&?{ktHfipyqV7qJI58mLQX6*C^f9 zfrsjrU3mP*w9qD!RX+6TnIFHCB=vvIe6&CP%|&04SvpCr!hI3#z0!O;QUvE`Y~)l? z++L9?=ze!{O#1%EE2R20jz{_ET#DbpTX$CN*nwE5_g($`FH_YrE10h^N){X3I9dpF zrfP|6L`h0wNvc(HQ7VvPFfuSQ&^55oH8Kn_GPE)@vobKzHZZa>Fu3ThB8H+NH$Npa YtrE9}1yXZX0W~mqy85}Sb4q9e0A&e>TL1t6 literal 0 HcmV?d00001 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..950a743 --- /dev/null +++ b/main.cpp @@ -0,0 +1,25 @@ +#include "mainwindow.h" +#include + +#include "quad.h" + +#include "cubiomes/generator.h" +#include "cubiomes/util.h" + +MainWindow *gMainWindowInstance; + +int main(int argc, char *argv[]) +{ + initBiomes(); + initBiomeColours(biomeColors); + initBiomeTypeColours(tempsColors); + + QApplication a(argc, argv); + MainWindow mw; + gMainWindowInstance = &mw; + mw.show(); + int ret = a.exec(); + gMainWindowInstance = NULL; + + return ret; +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..d61b855 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,649 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "filterdialog.h" +#include "gotodialog.h" +#include "quadlistdialog.h" +#include "aboutdialog.h" +#include "quad.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MAXRESULTS 65535 + + +QDataStream& operator<<(QDataStream& out, const Condition& v) +{ + out.writeRawData((const char*)&v, sizeof(Condition)); + return out; +} + +QDataStream& operator>>(QDataStream& in, Condition& v) +{ + in.readRawData((char*)&v, sizeof(Condition)); + return in; +} + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + id_counter(1), + sthread(this), + stimer(this) +{ + ui->setupUi(this); + ui->frameMap->layout()->addWidget(ui->toolBar); + ui->toolBar->setContentsMargins(0, 0, 0, 0); + + QFont mono = QFont("Monospace", 9); + mono.setStyleHint(QFont::TypeWriter); + ui->listConditions48->setFont(mono); + ui->listConditionsFull->setFont(mono); + ui->listResults->setFont(mono); + + qRegisterMetaType< int64_t >("int64_t"); + qRegisterMetaType< QVector >("QVector"); + qRegisterMetaType< Condition >("Condition"); + qRegisterMetaTypeStreamOperators< Condition >("Condition"); + + protodialog = new ProtoBaseDialog(this); + + connect(&sthread, &SearchThread::results, this, &MainWindow::searchResultsAdd); + connect(&sthread, &SearchThread::baseDone, this, &MainWindow::searchBaseDone); + connect(&sthread, &SearchThread::finish, this, &MainWindow::searchFinish); + connect(ui->checkStop, &QAbstractButton::toggled, &sthread, &SearchThread::setStopOnResult); + sthread.setStopOnResult(ui->checkStop->isChecked()); + + connect(&stimer, &QTimer::timeout, this, QOverload<>::of(&MainWindow::resultTimeout)); + stimer.start(500); + + updateSensitivity(); +} + +MainWindow::~MainWindow() +{ + stimer.stop(); + sthread.stop(); // tell search to stop at next convenience + sthread.quit(); // tell the event loop to exit + sthread.wait(); // wait for search to finish + delete ui; +} + +QVector MainWindow::getConditions() const +{ + QVector conds; + + for (int i = 0, ie = ui->listConditions48->count(); i < ie; i++) + conds.push_back(qvariant_cast(ui->listConditions48->item(i)->data(Qt::UserRole))); + + for (int i = 0, ie = ui->listConditionsFull->count(); i < ie; i++) + conds.push_back(qvariant_cast(ui->listConditionsFull->item(i)->data(Qt::UserRole))); + + return conds; +} + +MapView* MainWindow::getMapView() +{ + return ui->mapView; +} + +bool MainWindow::getSeed(int *mc, int64_t *seed, bool applyrand) +{ + bool ok = true; + if (mc) + { + const std::string& mcs = ui->comboBoxMC->currentText().toStdString(); + *mc = str2mc(mcs.c_str()); + if (*mc < 0) + { + *mc = MC_1_16; + qDebug() << "Unknown MC version: " << *mc; + ok = false; + } + } + + if (seed) + { + const QByteArray& ba = ui->seedEdit->text().toLocal8Bit(); + int v = str2seed(ba.data(), seed); + if (applyrand && v == S_RANDOM) + ui->seedEdit->setText(QString::asprintf("%" PRId64, *seed)); + } + + return ok; +} + +bool MainWindow::setSeed(int mc, int64_t seed) +{ + const char *mcstr = mc2str(mc); + if (!mcstr) + { + qDebug() << "Unknown MC version: " << mc; + return false; + } + + ui->comboBoxMC->setCurrentText(mcstr); + ui->seedEdit->setText(QString::asprintf("%" PRId64, seed)); + ui->mapView->setSeed(mc, seed); + return true; +} + +// [ID] Condition Cnt Rel Area +void MainWindow::setItemCondition(QListWidgetItem *item, Condition cond) const +{ + item->setData(Qt::UserRole, QVariant::fromValue(cond)); + + const FilterInfo& ft = g_filterinfo.list[cond.type]; + QString s = QString::asprintf("[%02d] %-28sx%-3d", cond.save, ft.name, cond.count); + + if (cond.relative) + s += QString::asprintf("[%02d]+", cond.relative); + else + s += " "; + + if (ft.coord) + s += QString::asprintf("(%d,%d)", cond.x1, cond.z1); + if (ft.area) + s += QString::asprintf(",(%d,%d)", cond.x2, cond.z2); + + if (ft.cat == CAT_48) + item->setBackground(QColor(Qt::yellow)); + else if (ft.cat == CAT_FULL) + item->setBackground(QColor(Qt::green)); + + item->setText(s); +} + +void MainWindow::editCondition(QListWidgetItem *item) +{ + FilterDialog *dialog = new FilterDialog(this, (Condition*)item->data(Qt::UserRole).data()); + dialog->exec(); + if (dialog->result() == QDialog::Accepted) + { + setItemCondition(item, dialog->cond); + } +} + +void MainWindow::updateMapSeed() +{ + int mc; + int64_t seed; + if (getSeed(&mc, &seed)) + ui->mapView->setSeed(mc, seed); +} + +void MainWindow::updateSensitivity() +{ + int selectcnt = 0; + selectcnt += ui->listConditions48->selectedItems().size(); + selectcnt += ui->listConditionsFull->selectedItems().size(); + + if (selectcnt == 0) + { + ui->buttonRemove->setEnabled(false); + ui->buttonEdit->setEnabled(false); + } + else if (selectcnt == 1) + { + ui->buttonRemove->setEnabled(true); + ui->buttonEdit->setEnabled(true); + } + else + { + ui->buttonRemove->setEnabled(true); + ui->buttonEdit->setEnabled(false); + } +} + +void MainWindow::warning(QString title, QString text) +{ + QMessageBox::warning(this, title, text, QMessageBox::Ok); +} + +void MainWindow::mapGoto(qreal x, qreal z) +{ + ui->mapView->setView(x, z); +} + +void MainWindow::openProtobaseMsg(QString path) +{ + protodialog->setPath(path); + protodialog->show(); +} + +void MainWindow::closeProtobaseMsg() +{ + if (protodialog->closeOnDone()) + protodialog->close(); +} + +void MainWindow::on_comboBoxMC_currentIndexChanged(int) +{ + updateMapSeed(); + update(); +} + +void MainWindow::on_seedEdit_editingFinished() +{ + updateMapSeed(); + update(); +} + +void MainWindow::on_seedEdit_textChanged(const QString &a) +{ + int64_t s; + int v = str2seed(a.toLocal8Bit().data(), &s); + switch (v) + { + case 0: ui->labelSeedType->setText("(text)"); break; + case 1: ui->labelSeedType->setText("(numeric)"); break; + case 2: ui->labelSeedType->setText("(random)"); break; + } +} + +void MainWindow::on_actionDesert_toggled(bool show) +{ ui->mapView->setShow(D_DESERT, show); } + +void MainWindow::on_actionJungle_toggled(bool show) +{ ui->mapView->setShow(D_JUNGLE, show); } + +void MainWindow::on_actionIgloo_toggled(bool show) +{ ui->mapView->setShow(D_IGLOO, show); } + +void MainWindow::on_actionHut_toggled(bool show) +{ ui->mapView->setShow(D_HUT, show); } + +void MainWindow::on_actionMonument_toggled(bool show) +{ ui->mapView->setShow(D_MONUMENT, show); } + +void MainWindow::on_actionVillage_toggled(bool show) +{ ui->mapView->setShow(D_VILLAGE, show); } + +void MainWindow::on_actionRuin_toggled(bool show) +{ ui->mapView->setShow(D_RUINS, show); } + +void MainWindow::on_actionShipwreck_toggled(bool show) +{ ui->mapView->setShow(D_SHIPWRECK, show); } + +void MainWindow::on_actionMansion_toggled(bool show) +{ ui->mapView->setShow(D_MANSION, show); } + +void MainWindow::on_actionOutpost_toggled(bool show) +{ ui->mapView->setShow(D_OUTPOST, show); } + +void MainWindow::on_actionPortal_toggled(bool show) +{ ui->mapView->setShow(D_PORTAL, show); } + +void MainWindow::on_actionSpawn_toggled(bool show) +{ ui->mapView->setShow(D_SPAWN, show); } + +void MainWindow::on_actionStronghold_toggled(bool show) +{ ui->mapView->setShow(D_STRONGHOLD, show); } + + +static void remove_selected(QListWidget *list) +{ + QList selected = list->selectedItems(); + for (QListWidgetItem *item : selected) + { + list->takeItem(list->row(item)); + delete item; + } +} + +void MainWindow::on_buttonRemove_clicked() +{ + remove_selected(ui->listConditions48); + remove_selected(ui->listConditionsFull); +} + +void MainWindow::on_buttonEdit_clicked() +{ + QList sel; + sel = ui->listConditions48->selectedItems(); + if (!sel.empty()) + editCondition(sel.first()); + sel = ui->listConditionsFull->selectedItems(); + if (!sel.empty()) + editCondition(sel.first()); +} + +void MainWindow::on_buttonAddFilter_clicked() +{ + FilterDialog *dialog = new FilterDialog(this); + dialog->exec(); + if (dialog->result() == QDialog::Accepted) + { + Condition cond = dialog->cond; + cond.save = id_counter++; + + const FilterInfo& ft = g_filterinfo.list[cond.type]; + if (ft.cat == CAT_FULL) + { + QListWidgetItem *item = new QListWidgetItem(ui->listConditionsFull, QListWidgetItem::UserType); + setItemCondition(item, cond); + ui->listConditionsFull->addItem(item); + } + else if (ft.cat == CAT_48) + { + QListWidgetItem *item = new QListWidgetItem(ui->listConditions48, QListWidgetItem::UserType); + setItemCondition(item, cond); + ui->listConditions48->addItem(item); + + if (cond.type >= F_QH_IDEAL && cond.type <= F_QH_BARELY) + { + Condition cq = cond; + cq.type = F_HUT; + cq.x1 = -128; cq.z1 = -128; + cq.x2 = +128; cq.z2 = +128; + cq.relative = cond.save; + cq.save = id_counter++; + cq.count = 4; + QListWidgetItem *item = new QListWidgetItem(ui->listConditionsFull, QListWidgetItem::UserType); + setItemCondition(item, cq); + ui->listConditionsFull->addItem(item); + } + else if (cond.type == F_QM_90 || cond.type == F_QM_95) + { + Condition cq = cond; + cq.type = F_MONUMENT; + cq.x1 = -160; cq.z1 = -160; + cq.x2 = +160; cq.z2 = +160; + cq.relative = cond.save; + cq.save = id_counter++; + cq.count = 4; + QListWidgetItem *item = new QListWidgetItem(ui->listConditionsFull, QListWidgetItem::UserType); + setItemCondition(item, cq); + ui->listConditionsFull->addItem(item); + } + } + } +} + + +void MainWindow::on_listConditions48_itemDoubleClicked(QListWidgetItem *item) +{ + editCondition(item); +} + +void MainWindow::on_listConditionsFull_itemDoubleClicked(QListWidgetItem *item) +{ + editCondition(item); +} + +void MainWindow::on_listConditions48_itemSelectionChanged() +{ + updateSensitivity(); +} + +void MainWindow::on_listConditionsFull_itemSelectionChanged() +{ + updateSensitivity(); +} + +void MainWindow::on_buttonClear_clicked() +{ + ui->listResults->clearContents(); + ui->listResults->setRowCount(0); + ui->lineStart48->setText("0"); + ui->progressBar->setValue(0); + ui->progressBar->setFormat("0.00%"); +} + +void MainWindow::on_buttonStart_clicked() +{ + if (ui->buttonStart->isChecked()) + { + int mc = MC_1_16; + getSeed(&mc, NULL); + QVector condvec = getConditions(); + int64_t sstart = (int64_t)ui->lineStart48->text().toLongLong() & MASK48; + + ui->lineStart48->setText(QString::asprintf("%" PRId64, sstart)); + if (!sthread.isRunning() && sthread.set(sstart, mc, condvec)) + { + ui->buttonStart->setText("Abort search"); + ui->buttonStart->setIcon(QIcon::fromTheme("process-stop")); + sthread.start(); + } + else + { + ui->buttonStart->setChecked(false); + } + } + else + { + ui->buttonStart->setEnabled(false); + ui->buttonStart->setText("Stopping..."); + update(); + sthread.stop(); // tell search to stop at next convenience + sthread.quit(); // tell the event loop to exit + sthread.wait(); // wait for search to finish + ui->buttonStart->setEnabled(true); + ui->buttonStart->setText("Start search"); + ui->buttonStart->setIcon(QIcon::fromTheme("system-search")); + } + + update(); +} + + +void MainWindow::on_listResults_itemSelectionChanged() +{ + int row = ui->listResults->currentRow(); + if (row >= 0 && row < ui->listResults->rowCount()) + { + int64_t s = ui->listResults->item(row, 0)->data(Qt::UserRole).toLongLong(); + ui->seedEdit->setText(QString::asprintf("%" PRId64, s)); + on_seedEdit_editingFinished(); + } +} + +void MainWindow::on_listResults_customContextMenuRequested(const QPoint &pos) +{ + QMenu menu(this); + + QAction *actremove = menu.addAction(QIcon::fromTheme("list-remove"), "Remove selected seed", this, &MainWindow::removeCurrent); + actremove->setEnabled(!ui->listResults->selectedItems().empty()); + + QAction *actcopy = menu.addAction(QIcon::fromTheme("edit-copy"), "Copy list to clipboard", this, &MainWindow::copyResults); + actcopy->setEnabled(ui->listResults->rowCount() > 0); + + int n = pasteList(true); + QAction *actpaste = menu.addAction(QIcon::fromTheme("edit-paste"), QString::asprintf("Paste %d seeds from clipboard", n), this, &MainWindow::pasteResults); + actpaste->setEnabled(n > 0); + menu.exec(ui->listResults->mapToGlobal(pos)); +} + +void MainWindow::on_buttonInfo_clicked() +{ + const char* msg = + "The constraints are separated into two sections. " + "The first (top) list is used to select a 48-bit generator, " + "while the second (bottom) list contains conditions that have a biome dependency." + "\n\n" + "Conditions can reference each other for relative positions " + "(indicated with the ID in square brackets [XY]). " + "The conditions will be checked in the same order they are listed, " + "so make sure that references are not broken." + "\n\n" + "You can edit existing conditions by double-clicking, and use drag to reorder them. " + ; + QMessageBox::information(this, "Help: search conditions", msg, QMessageBox::Ok); +} + +void MainWindow::on_actionGo_to_triggered() +{ + GotoDialog *dialog = new GotoDialog(this, ui->mapView->getX(), ui->mapView->getZ()); + dialog->show(); +} + +void MainWindow::on_actionScan_seed_for_Quad_Huts_triggered() +{ + QuadListDialog *dialog = new QuadListDialog(this); + dialog->show(); +} + +void MainWindow::on_actionAbout_triggered() +{ + AboutDialog *dialog = new AboutDialog(this); + dialog->show(); +} + +void MainWindow::on_mapView_customContextMenuRequested(const QPoint &pos) +{ + QMenu menu(this); + menu.addAction("Copy coordinates", this, &MainWindow::copyCoord); + menu.addAction("Go to coordinates...", this, &MainWindow::on_actionGo_to_triggered); + menu.exec(ui->mapView->mapToGlobal(pos)); +} + + +int MainWindow::searchResultsAdd(QVector seeds, bool countonly) +{ + int ns = ui->listResults->rowCount(); + int n = ns; + if (n >= MAXRESULTS) + return 0; + if (seeds.size() + n > MAXRESULTS) + seeds.resize(MAXRESULTS - n); + if (seeds.empty()) + return 0; + + QSet current; + current.reserve(n + seeds.size()); + for (int i = 0; i < n; i++) + { + int64_t seed = ui->listResults->item(i, 0)->data(Qt::UserRole).toLongLong(); + current.insert(seed); + } + + ui->listResults->setSortingEnabled(false); + for (int64_t s : seeds) + { + if (current.contains(s)) + continue; + if (countonly) + { + n++; + continue; + } + current.insert(s); + QTableWidgetItem* s48item = new QTableWidgetItem(); + QTableWidgetItem* seeditem = new QTableWidgetItem(); + s48item->setData(Qt::UserRole, QVariant::fromValue(s)); + s48item->setText(QString::asprintf("%012llx|%04x", + (qulonglong)(s & MASK48), (uint)(s >> 48) & ((1 << 16) - 1))); + seeditem->setData(Qt::DisplayRole, QVariant::fromValue(s)); + ui->listResults->insertRow(n); + ui->listResults->setItem(n, 0, s48item); + ui->listResults->setItem(n, 1, seeditem); + n++; + } + ui->listResults->setSortingEnabled(true); + + if (countonly == false && n >= MAXRESULTS) + { + sthread.stop(); + warning("Warning", QString::asprintf("Maximum number of results reached (%d).", MAXRESULTS)); + } + + if (ui->checkStop->isChecked()) + sthread.stop(); + + return n - ns; +} + +void MainWindow::searchBaseDone(int64_t s48) +{ + ui->lineStart48->setText(QString::asprintf("%" PRId64, s48 + 1)); + int v = (s48 * 10000) >> 48; + if (ui->progressBar->value() != v) + { + ui->progressBar->setValue(v); + ui->progressBar->setFormat(QString::asprintf("%d.%02d%%", v / 100, v % 100)); + } +} + +void MainWindow::searchFinish(int64_t s48) +{ + if (s48 >= MASK48) + { + ui->lineStart48->setText(QString::asprintf("%" PRId64, MASK48)); + ui->progressBar->setValue(10000); + ui->progressBar->setFormat(QString::asprintf("Done")); + } + ui->buttonStart->setChecked(false); + on_buttonStart_clicked(); +} + +void MainWindow::resultTimeout() +{ + update(); +} + +void MainWindow::removeCurrent() +{ + int row = ui->listResults->currentRow(); + if (row >= 0) + ui->listResults->removeRow(row); +} + +void MainWindow::copyResults() +{ + QString text; + int n = ui->listResults->rowCount(); + for (int i = 0; i < n; i++) + { + int64_t seed = ui->listResults->item(i, 0)->data(Qt::UserRole).toLongLong(); + text += QString::asprintf("%" PRId64 "\n", seed); + } + + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); +} + +void MainWindow::pasteResults() +{ + pasteList(false); +} + +int MainWindow::pasteList(bool dummy) +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + QStringList slist = clipboard->text().split('\n'); + QVector seeds; + + for (QString s : slist) + { + s = s.trimmed(); + if (s.isEmpty()) + continue; + bool ok = true; + int64_t seed = s.toLongLong(&ok); + if (!ok) + return 0; + seeds.push_back(seed); + } + + if (!seeds.empty()) + { + return searchResultsAdd(seeds, dummy); + } + return 0; +} + +void MainWindow::copyCoord() +{ + Pos p = ui->mapView->getActivePos(); + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(QString::asprintf("%d, %d", p.x, p.z)); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..22d66a8 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,119 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "searchthread.h" +#include "protobasedialog.h" + + +namespace Ui { +class MainWindow; +} + +Q_DECLARE_METATYPE(int64_t) +Q_DECLARE_METATYPE(Pos) +Q_DECLARE_METATYPE(Condition) + +class MapView; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + + bool getSeed(int *mc, int64_t *seed, bool applyrand = true); + bool setSeed(int mc, int64_t seed); + QVector getConditions() const; + MapView *getMapView(); + +protected: + void setItemCondition(QListWidgetItem *item, Condition cond) const; + void editCondition(QListWidgetItem *item); + + void updateMapSeed(); + void updateSensitivity(); + +public slots: + void warning(QString title, QString text); + void mapGoto(qreal x, qreal z); + void openProtobaseMsg(QString path); + void closeProtobaseMsg(); + +private slots: + void on_comboBoxMC_currentIndexChanged(int a); + void on_seedEdit_editingFinished(); + void on_seedEdit_textChanged(const QString &arg1); + + void on_actionDesert_toggled(bool arg1); + void on_actionJungle_toggled(bool arg1); + void on_actionIgloo_toggled(bool arg1); + void on_actionHut_toggled(bool arg1); + void on_actionMonument_toggled(bool arg1); + void on_actionVillage_toggled(bool arg1); + void on_actionRuin_toggled(bool arg1); + void on_actionShipwreck_toggled(bool arg1); + void on_actionMansion_toggled(bool arg1); + void on_actionOutpost_toggled(bool arg1); + void on_actionPortal_toggled(bool arg1); + void on_actionSpawn_toggled(bool arg1); + void on_actionStronghold_toggled(bool arg1); + + void on_buttonRemove_clicked(); + void on_buttonEdit_clicked(); + void on_buttonAddFilter_clicked(); + + void on_listConditions48_itemDoubleClicked(QListWidgetItem *item); + void on_listConditionsFull_itemDoubleClicked(QListWidgetItem *item); + void on_listConditions48_itemSelectionChanged(); + void on_listConditionsFull_itemSelectionChanged(); + + void on_buttonClear_clicked(); + void on_buttonStart_clicked(); + + void on_listResults_itemSelectionChanged(); + void on_listResults_customContextMenuRequested(const QPoint &pos); + + void on_buttonInfo_clicked(); + + void on_actionGo_to_triggered(); + void on_actionScan_seed_for_Quad_Huts_triggered(); + void on_actionAbout_triggered(); + + void on_mapView_customContextMenuRequested(const QPoint &pos); + + // internal events + int searchResultsAdd(QVector seeds, bool countonly); + void searchBaseDone(int64_t s48); + void searchFinish(int64_t s48); + void resultTimeout(); + void removeCurrent(); + void copyResults(); + void pasteResults(); + int pasteList(bool dummy = false); + void copyCoord(); + + +public: + Ui::MainWindow *ui; + int id_counter; + SearchThread sthread; + QTimer stimer; + ProtoBaseDialog *protodialog; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..c733d81 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,701 @@ + + + MainWindow + + + + 0 + 0 + 1280 + 720 + + + + Cubiomes Viewer + + + + :/icons/426997539678997085.png:/icons/426997539678997085.png + + + + + + + Qt::Horizontal + + + + + + + + + Minecraft version + + + + 1.16 + + + + + 1.15 + + + + + 1.14 + + + + + 1.13 + + + + + 1.12 + + + + + 1.11 + + + + + 1.10 + + + + + 1.9 + + + + + 1.8 + + + + + 1.7 + + + + + + + + Seed can be an integer or text. Leave empty for a random seed. + + + seed: + + + + + + + press enter to accept + + + + + + + Minecraft version + + + MC + + + + + + + + 61 + 25 + + + + Seed can be an integer or text. Leave empty for a random seed. + + + (random) + + + + + + + + + 0 + + + + Search + + + + + + QFrame::NoFrame + + + Qt::Vertical + + + true + + + + + 0 + 0 + + + + Contraints + + + + + + Edit condition + + + + + + + 3 + + + Qt::Vertical + + + + + Monospace + + + + Conditions for the 48-bit seed generator. + + + QAbstractItemView::InternalMove + + + QAbstractItemView::ExtendedSelection + + + QListView::Snap + + + + + + Monospace + + + + Biome dependent conditions. + + + QAbstractItemView::InternalMove + + + QAbstractItemView::ExtendedSelection + + + QListView::Snap + + + + + + + + Remove selection + + + + + + + Add condition + + + + + + + + 20 + 16777215 + + + + ? + + + + + + + + + 0 + 0 + + + + Matching seeds + + + + + + + Monospace + + + + Qt::CustomContextMenu + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + 160 + + + false + + + true + + + 20 + + + + Hex (Low-48 | Top-16) + + + AlignLeading|AlignVCenter + + + + + Seed + + + AlignLeading|AlignVCenter + + + + + + + + + + Starting 48-bit seed: + + + + + + + Stop on results + + + true + + + + + + + Progress within the set of all 48-bit seeds. + + + 10000 + + + 0 + + + 0.00% + + + + + + + 0 + + + + + + + Clear results + + + + + + + Start search + + + true + + + + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + + + 0 + 0 + 1280 + 22 + + + + + Map + + + + + + + Help + + + + + + + + + true + + + + :/icons/desert_d.png + :/icons/desert.png:/icons/desert_d.png + + + desert + + + Show desert pyramids + + + + + true + + + + :/icons/jungle_d.png + :/icons/jungle.png:/icons/jungle_d.png + + + jungle + + + Show jungle temples + + + + + true + + + + :/icons/igloo_d.png + :/icons/igloo.png:/icons/igloo_d.png + + + igloo + + + Show igloos + + + + + true + + + + :/icons/hut_d.png + :/icons/hut.png:/icons/hut_d.png + + + hut + + + Show swamp huts + + + + + true + + + + :/icons/monument_d.png + :/icons/monument.png:/icons/monument_d.png + + + monument + + + Show ocean monuments + + + + + true + + + + :/icons/village_d.png + :/icons/village.png:/icons/village_d.png + + + village + + + Show villages + + + + + true + + + + :/icons/ruins_d.png + :/icons/ruins.png:/icons/ruins_d.png + + + ruin + + + Show ocean ruins + + + + + true + + + + :/icons/shipwreck_d.png + :/icons/shipwreck.png:/icons/shipwreck_d.png + + + shipwrecks + + + Show shipwrecks + + + + + true + + + + :/icons/mansion_d.png + :/icons/mansion.png:/icons/mansion_d.png + + + mansion + + + Show woodland mansions + + + + + true + + + + :/icons/outpost_d.png + :/icons/outpost.png:/icons/outpost_d.png + + + outpost + + + Show pillager outposts + + + + + true + + + + :/icons/portal_d.png + :/icons/portal.png:/icons/portal_d.png + + + portal + + + Show ruined portals + + + + + true + + + + :/icons/spawn_d.png + :/icons/spawn.png:/icons/spawn_d.png + + + spawn + + + Show spawn + + + + + true + + + + :/icons/stronghold_d.png + :/icons/stronghold.png:/icons/stronghold_d.png + + + stronghold + + + Show strongholds + + + + + Go to... + + + + + Scan seed for Quad-Huts... + + + + + About + + + + + + + MapView + QWidget +
mapview.h
+ 1 +
+
+ + + + +
diff --git a/mapview.cpp b/mapview.cpp new file mode 100644 index 0000000..6744375 --- /dev/null +++ b/mapview.cpp @@ -0,0 +1,353 @@ +#include "mapview.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +static const char *biome2str(int id) +{ + switch (id) + { + case ocean: return "ocean"; + case plains: return "plains"; + case desert: return "desert"; + case mountains: return "mountains"; + case forest: return "forest"; + case taiga: return "taiga"; + case swamp: return "swamp"; + case river: return "river"; + case nether_wastes: return "nether_wastes"; + case the_end: return "the_end"; + // 10 + case frozen_ocean: return "frozen_ocean"; + case frozen_river: return "frozen_river"; + case snowy_tundra: return "snowy_tundra"; + case snowy_mountains: return "snowy_mountains"; + case mushroom_fields: return "mushroom_fields"; + case mushroom_field_shore: return "mushroom_field_shore"; + case beach: return "beach"; + case desert_hills: return "desert_hills"; + case wooded_hills: return "wooded_hills"; + case taiga_hills: return "taiga_hills"; + // 20 + case mountain_edge: return "mountain_edge"; + case jungle: return "jungle"; + case jungle_hills: return "jungle_hills"; + case jungle_edge: return "jungle_edge"; + case deep_ocean: return "deep_ocean"; + case stone_shore: return "stone_shore"; + case snowy_beach: return "snowy_beach"; + case birch_forest: return "birch_forest"; + case birch_forest_hills: return "birch_forest_hills"; + case dark_forest: return "dark_forest"; + // 30 + case snowy_taiga: return "snowy_taiga"; + case snowy_taiga_hills: return "snowy_taiga_hills"; + case giant_tree_taiga: return "giant_tree_taiga"; + case giant_tree_taiga_hills: return "giant_tree_taiga_hills"; + case wooded_mountains: return "wooded_mountains"; + case savanna: return "savanna"; + case savanna_plateau: return "savanna_plateau"; + case badlands: return "badlands"; + case wooded_badlands_plateau: return "wooded_badlands_plateau"; + case badlands_plateau: return "badlands_plateau"; + // 40 -- 1.13 + case small_end_islands: return "small_end_islands"; + case end_midlands: return "end_midlands"; + case end_highlands: return "end_highlands"; + case end_barrens: return "end_barrens"; + case warm_ocean: return "warm_ocean"; + case lukewarm_ocean: return "lukewarm_ocean"; + case cold_ocean: return "cold_ocean"; + case deep_warm_ocean: return "deep_warm_ocean"; + case deep_lukewarm_ocean: return "deep_lukewarm_ocean"; + case deep_cold_ocean: return "deep_cold_ocean"; + // 50 + case deep_frozen_ocean: return "deep_frozen_ocean"; + + case the_void: return "the_void"; + + // mutated variants + case sunflower_plains: return "sunflower_plains"; + case desert_lakes: return "desert_lakes"; + case gravelly_mountains: return "gravelly_mountains"; + case flower_forest: return "flower_forest"; + case taiga_mountains: return "taiga_mountains"; + case swamp_hills: return "swamp_hills"; + case ice_spikes: return "ice_spikes"; + case modified_jungle: return "modified_jungle"; + case modified_jungle_edge: return "modified_jungle_edge"; + case tall_birch_forest: return "tall_birch_forest"; + case tall_birch_hills: return "tall_birch_hills"; + case dark_forest_hills: return "dark_forest_hills"; + case snowy_taiga_mountains: return "snowy_taiga_mountains"; + case giant_spruce_taiga: return "giant_spruce_taiga"; + case giant_spruce_taiga_hills: return "giant_spruce_taiga_hills"; + case modified_gravelly_mountains: return "modified_gravelly_mountains"; + case shattered_savanna: return "shattered_savanna"; + case shattered_savanna_plateau: return "shattered_savanna_plateau"; + case eroded_badlands: return "eroded_badlands"; + case modified_wooded_badlands_plateau: return "modified_wooded_badlands_plateau"; + case modified_badlands_plateau: return "modified_badlands_plateau"; + // 1.14 + case bamboo_jungle: return "bamboo_jungle"; + case bamboo_jungle_hills: return "bamboo_jungle_hills"; + // 1.16 + case soul_sand_valley: return "soul_sand_valley"; + case crimson_forest: return "crimson_forest"; + case warped_forest: return "warped_forest"; + case basalt_deltas: return "basalt_deltas"; + } + return NULL; +} + +bool MapOverlay::event(QEvent *e) +{ + if (e->type() == QEvent::MouseMove) + update(); + return QWidget::event(e); +} + +void MapOverlay::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + const char *bname = biome2str(id); + if (bname) + { + QString s = QString::asprintf("%s [%d,%d]", bname, pos.x, pos.z); + QRect r = painter.fontMetrics() + .boundingRect(0, 0, width(), height(), Qt::AlignRight | Qt::AlignTop, s); + + painter.fillRect(r, QBrush(QColor(0, 0, 0, 128), Qt::SolidPattern)); + painter.setPen(Qt::white); + painter.drawText(r, s); + } +} + +MapView::MapView(QWidget *parent) +: QWidget(parent) +, world() +, blocks2pix(1.0/12) +, focusx(),focusz() +, prevx(),prevz() +, velx(), velz() +, holding() +, mstart(),mprev() +, updatecounter() +, sshow() +{ + memset(sshow, 0, sizeof(sshow)); + + QPalette pal = palette(); + pal.setColor(QPalette::Background, Qt::black); + setAutoFillBackground(true); + setPalette(pal); + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, QOverload<>::of(&MapView::timeout)); + timer->start(50); + + elapsed1.start(); + frameelapsed.start(); + + overlay = new MapOverlay(this); + overlay->setMouseTracking(true); + + setContextMenuPolicy(Qt::CustomContextMenu); +} + +MapView::~MapView() +{ + delete world; + delete overlay; +} + +void MapView::setSeed(int mc, int64_t s) +{ + prevx = focusx; + prevz = focusz; + velx = velz = 0; + if (world == NULL || world->mc != mc || world->seed != s) + { + delete world; + world = new QWorld(mc, s); + } + for (int i = 0; i < STRUCT_NUM; i++) + world->sshow[i] = sshow[i]; + update(2); +} + +void MapView::setShow(int stype, bool v) +{ + sshow[stype] = v; + if (world) + for (int s = 0; s < STRUCT_NUM; s++) + world->sshow[s] = sshow[s]; + update(2); +} + +void MapView::setView(qreal x, qreal z) +{ + prevx = focusx = x; + prevz = focusz = z; + velx = velz = 0; + update(2); +} + +void MapView::timeout() +{ + qreal dt = 1e-3 * timer->interval(); //elapsed1.nsecsElapsed() * 1e-9; + //elapsed1.start(); + + frameelapsed.start(); + + if (!holding) + { + qreal m = 1.0 - 3*dt; if (m < 0) m = 0; + velx *= m; + velz *= m; + focusx += velx * dt; + focusz += velz * dt; + } + else + { + velx = (focusx - prevx) / dt; + velz = (focusz - prevz) / dt; + prevx = focusx; + prevz = focusz; + } + + if ((velx*velx + velz*velz) * (blocks2pix*blocks2pix) < 100) + { + velx = 0; + velz = 0; + } +} + +void MapView::update(int cnt) +{ + updatecounter = cnt; + QWidget::update(); +} + +Pos MapView::getActivePos() +{ + Pos p = overlay->pos; + if (world && world->seltype != D_NONE) + p = world->selpos; + return p; +} + +void MapView::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + painter.setRenderHint(QPainter::Antialiasing); + painter.setRenderHint(QPainter::HighQualityAntialiasing); + + qreal dt = frameelapsed.nsecsElapsed() * 1e-9; + qreal fx = focusx; + qreal fz = focusz; + + if (!holding) + { + fx += velx * dt; + fz += velz * dt; + } + + if (world) + { + world->draw(painter, width(), height(), fx, fz, blocks2pix); + + QPoint cur = mapFromGlobal(QCursor::pos()); + qreal bx = (cur.x() - width()/2) / blocks2pix + fx; + qreal bz = (cur.y() - height()/2) / blocks2pix + fz; + Pos p = {(int)bx, (int)bz}; + overlay->pos = p; + overlay->id = getBiomeAtPos(&world->g, p); + + if (QThreadPool::globalInstance()->activeThreadCount() > 0 || velx || velz) + updatecounter = 2; + if (updatecounter > 0) + { + updatecounter--; + QWidget::update(); + } + } +} + +void MapView::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + overlay->resize(width(), height()); +} + +void MapView::wheelEvent(QWheelEvent *e) +{ + const qreal ang = e->angleDelta().y() / 8; // e->delta() / 8; + blocks2pix *= pow(2, ang/100); + update();//repaint(); +} + +void MapView::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) + { + mprev = mstart = e->pos(); + holding = true; + prevx = focusx; + prevz = focusz; + velx = 0; + velz = 0; + + if (world) + { + world->selx = mstart.x(); + world->selz = mstart.y(); + world->seldo = true; + update(); + } + } +} + +void MapView::mouseMoveEvent(QMouseEvent *e) +{ + if ((e->buttons() & Qt::LeftButton) && holding) + { + QPoint d = e->pos() - mprev; + focusx = focusx - d.x() / blocks2pix; + focusz = focusz - d.y() / blocks2pix; + mprev = e->pos(); + update();//repaint(); + } +} + +void MapView::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton && holding) + { + holding = false; + mprev = e->pos(); + + if (world && e->pos() == mstart) + { + world->selx = mstart.x(); + world->selz = mstart.y(); + world->seldo = true; + world->seltype = D_NONE; + } + } +} + +void MapView::keyPressEvent(QKeyEvent *) +{ +} diff --git a/mapview.h b/mapview.h new file mode 100644 index 0000000..eddeafe --- /dev/null +++ b/mapview.h @@ -0,0 +1,85 @@ +#ifndef MAPVIEW_H +#define MAPVIEW_H + +#include "quad.h" + +#include +#include +#include + + +class MapOverlay : public QWidget +{ + Q_OBJECT + +public: + explicit MapOverlay(QWidget *parent = nullptr) + : QWidget(parent),pos{},id(-1) {} + ~MapOverlay() {} + +public slots: + bool event(QEvent *) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; + +public: + Pos pos; + int id; +}; + +class MapView : public QWidget +{ + Q_OBJECT + +public: + explicit MapView(QWidget *parent = nullptr); + ~MapView(); + + qreal getX() const { return focusx; } + qreal getZ() const { return focusz; } + + void setSeed(int mc, int64_t s); + void setShow(int stype, bool v); + void setView(qreal x, qreal z); + + void timeout(); + + void update(int cnt = 1); + + Pos getActivePos(); + +signals: + +public slots: + void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; + void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; + + void wheelEvent(QWheelEvent *) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; + + void keyPressEvent(QKeyEvent *) Q_DECL_OVERRIDE; + +public: + QWorld *world; + + QTimer *timer; + QElapsedTimer elapsed1; + QElapsedTimer frameelapsed; + + MapOverlay *overlay; + +private: + qreal blocks2pix; + qreal focusx, focusz; + qreal prevx, prevz; + qreal velx, velz; + + bool holding; + QPoint mstart, mprev; + int updatecounter; + + bool sshow[STRUCT_NUM]; +}; + +#endif // MAPVIEW_H diff --git a/protobasedialog.cpp b/protobasedialog.cpp new file mode 100644 index 0000000..5a2ab6e --- /dev/null +++ b/protobasedialog.cpp @@ -0,0 +1,26 @@ +#include "protobasedialog.h" +#include "ui_protobasedialog.h" + +ProtoBaseDialog::ProtoBaseDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ProtoBaseDialog) +{ + ui->setupUi(this); +} + +ProtoBaseDialog::~ProtoBaseDialog() +{ + delete ui; +} + +bool ProtoBaseDialog::closeOnDone() +{ + return ui->checkBox->isChecked(); +} + +void ProtoBaseDialog::setPath(QString path) +{ + ui->label->setText( + "This take a moment.\n" + "Results will be saved to \"" + path + "\" so subsequent searches will start faster."); +} diff --git a/protobasedialog.h b/protobasedialog.h new file mode 100644 index 0000000..237594f --- /dev/null +++ b/protobasedialog.h @@ -0,0 +1,25 @@ +#ifndef PROTOBASEDIALOG_H +#define PROTOBASEDIALOG_H + +#include + +namespace Ui { +class ProtoBaseDialog; +} + +class ProtoBaseDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ProtoBaseDialog(QWidget *parent = nullptr); + ~ProtoBaseDialog(); + + bool closeOnDone(); + void setPath(QString path); + +private: + Ui::ProtoBaseDialog *ui; +}; + +#endif // PROTOBASEDIALOG_H diff --git a/protobasedialog.ui b/protobasedialog.ui new file mode 100644 index 0000000..b7d49ec --- /dev/null +++ b/protobasedialog.ui @@ -0,0 +1,80 @@ + + + ProtoBaseDialog + + + Generating Protobases + + + + + + <html><head/><body><p><span style=" font-size:12pt; font-weight:600;">Please wait: generating protobases.</span></p></body></html> + + + + + + + This take a moment. Results will be saved to _PATH_ so subsequent searches will start faster. + + + + + + + Automatically close dialog when processing is done. + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + ProtoBaseDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ProtoBaseDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/quad.cpp b/quad.cpp new file mode 100644 index 0000000..49abe2c --- /dev/null +++ b/quad.cpp @@ -0,0 +1,705 @@ +#include "quad.h" + +#include + +#include +#include + +unsigned char biomeColors[256][3]; +unsigned char tempsColors[256][3]; + + +Quad::Quad(const Level* l, int i, int j) + : mc(l->mc),entry(l->entry),seed(l->seed) + , ti(i),tj(j),blocks(l->blocks),pixs(l->pixs),stype(l->stype) + , rgb(),img(),spos() + , done() + , prio(),stopped() +{ + setAutoDelete(false); +} + +Quad::~Quad() +{ + delete [] rgb; + delete img; + delete spos; +} + +std::vector *Quad::addStruct(const StructureConfig sconf, LayerStack *g) +{ + int x0 = ti*blocks, x1 = (ti+1)*blocks; + int z0 = tj*blocks, z1 = (tj+1)*blocks; + int si0 = (int)floor(x0 / (qreal)(sconf.regionSize * 16)); + int sj0 = (int)floor(z0 / (qreal)(sconf.regionSize * 16)); + int si1 = (int)floor((x1-1) / (qreal)(sconf.regionSize * 16)); + int sj1 = (int)floor((z1-1) / (qreal)(sconf.regionSize * 16)); + + std::vector* st = new std::vector(); + + for (int i = si0; i <= si1; i++) + { + for (int j = sj0; j <= sj1; j++) + { + int valid; + Pos p = getStructurePos(sconf, seed, i, j, &valid); + + if (valid && p.x >= x0 && p.x < x1 && p.z >= z0 && p.z < z1) + { + if (isViableStructurePos(sconf.structType, mc, g, seed, p.x, p.z)) + st->push_back(p); + } + } + } + + return st; +} + +void Quad::run() +{ + if (done) + return; + + if (pixs > 0) + { + int *b = allocCache(entry, pixs, pixs); + + genArea(entry, b, ti*pixs, tj*pixs, pixs, pixs); + + rgb = new uchar[pixs*pixs * 3]; + biomesToImage(rgb, biomeColors, b, pixs, pixs, 1, 1); + img = new QImage(rgb, pixs, pixs, QImage::Format_RGB888); + free(b); + } + else + { + LayerStack g; + setupGenerator(&g, mc); + + switch (stype) + { + case D_DESERT: + spos = addStruct(mc <= MC_1_12 ? DESERT_PYRAMID_CONFIG_112 : DESERT_PYRAMID_CONFIG, &g); + break; + case D_JUNGLE: + spos = addStruct(mc <= MC_1_12 ? JUNGLE_PYRAMID_CONFIG_112 : JUNGLE_PYRAMID_CONFIG, &g); + break; + case D_IGLOO: + spos = addStruct(mc <= MC_1_12 ? IGLOO_CONFIG_112 : IGLOO_CONFIG, &g); + break; + case D_HUT: + spos = addStruct(mc <= MC_1_12 ? SWAMP_HUT_CONFIG_112 : SWAMP_HUT_CONFIG, &g); + break; + case D_VILLAGE: + spos = addStruct(VILLAGE_CONFIG, &g); + break; + case D_MANSION: + spos = addStruct(MANSION_CONFIG, &g); + break; + case D_MONUMENT: + spos = addStruct(MONUMENT_CONFIG, &g); + break; + case D_RUINS: + spos = addStruct(mc <= MC_1_15 ? OCEAN_RUIN_CONFIG_115 : OCEAN_RUIN_CONFIG, &g); + break; + case D_SHIPWRECK: + spos = addStruct(mc <= MC_1_15 ? SHIPWRECK_CONFIG_115 : SHIPWRECK_CONFIG, &g); + break; + case D_OUTPOST: + spos = addStruct(OUTPOST_CONFIG, &g); + break; + case D_PORTAL: + spos = addStruct(RUINED_PORTAL_CONFIG, &g); + break; + } + } + done = true; +} + + +Level::Level() + : cells(),g(),entry(),seed(),mc() + , tx(),tz(),tw(),th() + , scale(),blocks(),pixs() + , stype() +{ +} + +Level::~Level() +{ + QThreadPool::globalInstance()->waitForDone(); + for (Quad *q : cells) + delete q; +} + +int mapOceanMixMod(const Layer * l, int * out, int x, int z, int w, int h) +{ + int *otyp; + int i, j; + l->p2->getMap(l->p2, out, x, z, w, h); + + otyp = (int *) malloc(w*h*sizeof(int)); + memcpy(otyp, out, w*h*sizeof(int)); + + l->p->getMap(l->p, out, x, z, w, h); + + + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + int landID, oceanID; + + landID = out[i + j*w]; + + if (!isOceanic(landID)) + continue; + + oceanID = otyp[i + j*w]; + + if (landID == deep_ocean) + { + switch (oceanID) + { + case lukewarm_ocean: + oceanID = deep_lukewarm_ocean; + break; + case ocean: + oceanID = deep_ocean; + break; + case cold_ocean: + oceanID = deep_cold_ocean; + break; + case frozen_ocean: + oceanID = deep_frozen_ocean; + break; + } + } + + out[i + j*w] = oceanID; + } + } + + free(otyp); + + return 0; +} + +void Level::init4map(int mcversion, int64_t ws, int pix, int layerscale) +{ + mc = mcversion; + seed = ws; + setupGenerator(&g, mc); + + tx = tz = tw = th = 0; + scale = layerscale; + pixs = pix; + blocks = pix * layerscale; + stype = D_NONE; + + switch (scale) + { + case 1: + entry = g.entry_1; + break; + case 4: + entry = g.entry_4; + break; + case 16: + if (mc >= MC_1_13) { + setupMultiLayer(g.entry_1, + &g.layers[L_SHORE_16], + &g.layers[L13_ZOOM_16], + 0, mapOceanMixMod); + entry = g.entry_1; + } else { + entry = &g.layers[L_SHORE_16]; + } + break; + case 64: + if (mc >= MC_1_13) { + setupMultiLayer(g.entry_1, + &g.layers[L_RARE_BIOME_64], + &g.layers[L13_ZOOM_64], + 0, mapOceanMixMod); + entry = g.entry_1; + } else { + entry = &g.layers[L_RARE_BIOME_64]; + } + break; + case 256: + if (mc >= MC_1_13) { + setupMultiLayer(g.entry_1, + &g.layers[L_BIOME_256], + &g.layers[L13_OCEAN_TEMP_256], + 0, mapOceanMixMod); + entry = g.entry_1; + } else { + entry = &g.layers[L_BIOME_256]; + } + break; + default: + printf("Bad scale (%d) for level\n", scale); + exit(1); + } + + setWorldSeed(entry, seed); +} + +void Level::init4struct(int mcversion, int64_t ws, int b, int structtype) +{ + mc = mcversion; + seed = ws; + blocks = b; + pixs = -1; + scale = -1; + stype = structtype; +} + +static int sqdist(int x, int z) { return x*x + z*z; } + +void Level::resizeLevel(std::vector& cache, int x, int z, int w, int h) +{ + // move the cells from the old grid to the new grid + // or to the cached queue if they are not inside the new grid + std::vector grid(w*h); + std::vector togen; + + for (Quad *q : cells) + { + int gx = q->ti - x; + int gz = q->tj - z; + if (gx >= 0 && gx < w && gz >= 0 && gz < h) + grid[gz*w + gx] = q; + else + cache.push_back(q); + } + + // look through the cached queue for reusable quads + std::vector newcache; + for (Quad *c : cache) + { + int gx = c->ti - x; + int gz = c->tj - z; + + if (c->blocks == blocks && c->stype == stype) + { + // remove outside quads from schedule + if (QThreadPool::globalInstance()->tryTake(c)) + { + c->stopped = true; + } + + if (gx >= 0 && gx < w && gz >= 0 && gz < h) + { + Quad *& g = grid[gz*w + gx]; + if (g == NULL) + { + g = c; + continue; + } + } + } + newcache.push_back(c); + } + cache.swap(newcache); + + // collect which quads need generation and add any that are missing + for (int j = 0; j < h; j++) + { + for (int i = 0; i < w; i++) + { + Quad *& g = grid[j*w + i]; + if (g == NULL) + { + g = new Quad(this, x+i, z+j); + g->prio = sqdist(i-w/2, j-h/2); + togen.push_back(g); + } + else if (g->stopped || QThreadPool::globalInstance()->tryTake(g)) + { + if (!g->done) + { + g->stopped = false; + g->prio = sqdist(i-w/2, j-h/2); + togen.push_back(g); + } + } + } + } + + // start the quad processing + std::sort(togen.begin(), togen.end(), + [](Quad* a, Quad* b) { return a->prio < b->prio; }); + for (Quad *q : togen) + QThreadPool::globalInstance()->start(q, scale); + + cells.swap(grid); + tx = x; + tz = z; + tw = w; + th = h; +} + +void Level::update(std::vector& cache, qreal bx0, qreal bz0, qreal bx1, qreal bz1) +{ + int nti0 = (int) std::floor(bx0 / blocks); + int ntj0 = (int) std::floor(bz0 / blocks); + int nti1 = (int) std::floor(bx1 / blocks) + 1; + int ntj1 = (int) std::floor(bz1 / blocks) + 1; + + // resize if the new area is much smaller or in an unprocessed range + if ((nti1-nti0)*2 < tw || nti0 < tx || nti1 > tx+tw || ntj0 < tz || ntj1 > tz+th) + { + qreal padf = 0.2 * (bx1 - bx0); + nti0 = (int) std::floor((bx0-padf) / blocks); + ntj0 = (int) std::floor((bz0-padf) / blocks); + nti1 = (int) std::floor((bx1+padf) / blocks) + 1; + ntj1 = (int) std::floor((bz1+padf) / blocks) + 1; + + resizeLevel(cache, nti0, ntj0, nti1-nti0, ntj1-ntj0); + } +} + + +QWorld::QWorld(int mc, int64_t seed) + : mc(mc) + , seed(seed) + , lv() + , lvs() + , activelv() + , structlv() + , cached() + , cachedstruct() + , cachesize() + , spawn() + , strongholds() + , isdel() + , seldo() + , selx() + , selz() + , seltype(-1) + , selpos() + , qual() +{ + setupGenerator(&g, mc); + applySeed(&g, seed); + + activelv = 0; + structlv = 3; + int pixs = 512; + lvs.resize(D_SPAWN); + for (int stype = 0; stype < D_SPAWN; stype++) + lvs[stype].init4struct(mc, seed, 2048, stype); + lv.resize(5); + lv[0].init4map(mc, seed, pixs, 1); + lv[1].init4map(mc, seed, pixs, 4); + lv[2].init4map(mc, seed, pixs, 16); + lv[3].init4map(mc, seed, pixs, 64); + lv[4].init4map(mc, seed, pixs, 256); + cachesize = 100; + qual = 1.0; + + memset(sshow, 0, sizeof(sshow)); + + icons[D_DESERT] = QPixmap(":/icons/desert.png"); + icons[D_JUNGLE] = QPixmap(":/icons/jungle.png"); + icons[D_IGLOO] = QPixmap(":/icons/igloo.png"); + icons[D_HUT] = QPixmap(":/icons/hut.png"); + icons[D_VILLAGE] = QPixmap(":/icons/village.png"); + icons[D_MANSION] = QPixmap(":/icons/mansion.png"); + icons[D_MONUMENT] = QPixmap(":/icons/monument.png"); + icons[D_RUINS] = QPixmap(":/icons/ruins.png"); + icons[D_SHIPWRECK] = QPixmap(":/icons/shipwreck.png"); + icons[D_OUTPOST] = QPixmap(":/icons/outpost.png"); + icons[D_PORTAL] = QPixmap(":/icons/portal.png"); + icons[D_SPAWN] = QPixmap(":/icons/spawn.png"); + icons[D_STRONGHOLD] = QPixmap(":/icons/stronghold.png"); +} + +QWorld::~QWorld() +{ + isdel = true; + QThreadPool::globalInstance()->clear(); + QThreadPool::globalInstance()->waitForDone(); + for (Quad *q : cached) + delete q; + for (Quad *q : cachedstruct) + delete q; + if (spawn && spawn != (Pos*)-1) + { + delete spawn; + delete strongholds; + } +} + +void QWorld::cleancache(std::vector& cache, unsigned int maxsize) +{ + // try to delete the oldest entries in the cache + if (cache.size() > maxsize) + { + std::vector newcache; + int i; + for (i = cache.size()-1; i >= 0; --i) + { + Quad *q = cache[i]; + if (newcache.size() + i < maxsize * 0.8) + { + newcache.push_back(q); + } + else + { + if (q->done || q->stopped || QThreadPool::globalInstance()->tryTake(q)) + delete q; + else + newcache.push_back(q); + } + } + + cache.resize(newcache.size()); + std::copy(newcache.rbegin(), newcache.rend(), cache.begin()); + } +} + + +struct SpawnStronghold : public QRunnable +{ + QWorld *world; + int mc; + int64_t seed; + + SpawnStronghold(QWorld *world, int mc, int64_t seed) : + world(world),mc(mc),seed(seed) {} + + void run() + { + LayerStack g; + setupGenerator(&g, mc); + applySeed(&g, seed); + + Pos *p = new Pos; + *p = getSpawn(mc, &g, NULL, seed); + world->spawn = p; + if (world->isdel) return; + + StrongholdIter sh; + initFirstStronghold(&sh, mc, seed); + + std::vector *shp = new std::vector; + shp->reserve(mc >= MC_1_9 ? 128 : 3); + + while (nextStronghold(&sh, &g, NULL) > 0) + { + if (world->isdel) + { + delete shp; + return; + } + shp->push_back(sh.pos); + } + + world->strongholds = shp; + } +}; + +void QWorld::draw(QPainter& painter, int vw, int vh, qreal focusx, qreal focusz, qreal blocks2pix) +{ + qreal uiw = vw / blocks2pix; + qreal uih = vh / blocks2pix; + + qreal bx0 = focusx - uiw/2; + qreal bz0 = focusz - uih/2; + qreal bx1 = focusx + uiw/2; + qreal bz1 = focusz + uih/2; + + if (blocks2pix >= qual) activelv = -1; + else if (blocks2pix >= qual/4) activelv = 0; + else if (blocks2pix >= qual/16) activelv = 1; + else if (blocks2pix >= qual/64) activelv = 2; + else if (blocks2pix >= qual/256) activelv = 3; + else activelv = lv.size()-1; + + for (int li = activelv+1; li >= activelv; --li) + { + if (li < 0 || li >= (int)lv.size()) + continue; + Level& l = lv[li]; + for (Quad *q : l.cells) + { + if (q->img) // q was processed in another thread and is now done + { + qreal ps = q->blocks * blocks2pix; + qreal px = vw/2 + (q->ti) * ps - focusx * blocks2pix; + qreal pz = vh/2 + (q->tj) * ps - focusz * blocks2pix; + + QRect rec(px,pz,ps,ps); + painter.drawImage(rec, *q->img); + + QString s = QString::asprintf("%d,%d", q->ti*q->blocks, q->tj*q->blocks); + QRect textrec = painter.fontMetrics() + .boundingRect(rec, Qt::AlignLeft | Qt::AlignTop, s); + + painter.fillRect(textrec, QBrush(QColor(0, 0, 0, 128), Qt::SolidPattern)); + + painter.setPen(QColor(255, 255, 255)); + painter.drawText(textrec, s); + + painter.setPen(QPen(QColor(0, 0, 0, 96), 1)); + painter.drawRect(rec); + } + } + } + + if (activelv < structlv) + { + for (int stype = 0; stype < D_SPAWN; stype++) + { + Level& l = lvs[stype]; + if (!sshow[stype]) + continue; + + std::vector frags; + + for (Quad *q : l.cells) + { + if (q->spos) // q was processed in another thread and is now done + { + for (Pos& p : *q->spos) + { + qreal x = vw/2 + (p.x - focusx) * blocks2pix; + qreal y = vh/2 + (p.z - focusz) * blocks2pix; + if (x >= 0 && x < vw && y >= 0 && y < vh) + { + QPointF d = QPointF(x, y); + QRectF r = icons[stype].rect(); + frags.push_back(QPainter::PixmapFragment::create(d, r)); + + if (seldo) + { + r.moveCenter(d); + if (r.contains(selx, selz)) + { + seltype = stype; + selpos = p; + } + } + } + } + } + } + + painter.drawPixmapFragments(frags.data(), frags.size(), icons[stype]); + } + } + + Pos* sp = spawn; // atomic fetch + if (sp && sp != (Pos*)-1 && sshow[D_SPAWN]) + { + qreal x = vw/2 + (sp->x - focusx) * blocks2pix; + qreal y = vh/2 + (sp->z - focusz) * blocks2pix; + + QPointF d = QPointF(x, y); + QRectF r = icons[D_SPAWN].rect(); + painter.drawPixmap(d, icons[D_SPAWN]); + + if (seldo) + { + r.moveCenter(d); + if (r.contains(selx, selz)) + { + seltype = D_SPAWN; + selpos = *sp; + } + } + } + + std::vector* shs = strongholds; // atomic fetch + if (shs && sshow[D_STRONGHOLD]) + { + std::vector frags; + frags.reserve(shs->size()); + for (Pos p : *shs) + { + qreal x = vw/2 + (p.x - focusx) * blocks2pix; + qreal y = vh/2 + (p.z - focusz) * blocks2pix; + QPointF d = QPointF(x, y); + QRectF r = icons[D_STRONGHOLD].rect(); + frags.push_back(QPainter::PixmapFragment::create(d, r)); + + if (seldo) + { + r.moveCenter(d); + if (r.contains(selx, selz)) + { + seltype = D_STRONGHOLD; + selpos = p; + } + } + } + painter.drawPixmapFragments(frags.data(), frags.size(), icons[D_STRONGHOLD]); + } + + for (int li = lv.size()-1; li >= 0; --li) + { + if (li == activelv || li == activelv+1) + lv[li].update(cached, bx0, bz0, bx1, bz1); + else + lv[li].update(cached, 0, 0, 0, 0); + } + for (int stype = 0; stype < D_SPAWN; stype++) + { + if (activelv < structlv && sshow[stype]) + lvs[stype].update(cachedstruct, bx0, bz0, bx1, bz1); + else + lvs[stype].update(cachedstruct, 0, 0, 0, 0); + } + + // start the spawn and stronghold worker thread if this is the first run + if (spawn == NULL && (sshow[D_SPAWN] || sshow[D_STRONGHOLD])) + { + spawn = (Pos*) -1; + QThreadPool::globalInstance()->start(new SpawnStronghold(this, mc, seed)); + } + + if (seldo) + { + seldo = false; + } + + if (seltype != D_NONE) + { + qreal x = vw/2 + (selpos.x - focusx) * blocks2pix; + qreal y = vh/2 + (selpos.z - focusz) * blocks2pix; + QRect iconrec = icons[seltype].rect(); + qreal w = iconrec.width() * 1.5; + qreal h = iconrec.height() * 1.5; + painter.drawPixmap(x-w/2, y-h/2, w, h, icons[seltype]); + + QFont f = QFont(); + f.setBold(true); + painter.setFont(f); + + QString s = QString::asprintf(" %d,%d", selpos.x, selpos.z); + int pad = 5; + QRect textrec = painter.fontMetrics() + .boundingRect(0, 0, vw, vh, Qt::AlignLeft | Qt::AlignTop, s); + + if (textrec.height() < iconrec.height()) + textrec.setHeight(iconrec.height()); + + textrec.translate(pad+iconrec.width(), pad); + + painter.fillRect(textrec.marginsAdded(QMargins(pad+iconrec.width(),pad,pad,pad)), + QBrush(QColor(0, 0, 0, 128), Qt::SolidPattern)); + + painter.setPen(QPen(QColor(255, 255, 255), 2)); + painter.drawText(textrec, s, QTextOption(Qt::AlignLeft | Qt::AlignVCenter)); + + painter.drawPixmap(iconrec.translated(pad,pad), icons[seltype]); + } + + cleancache(cached, cachesize); + cleancache(cachedstruct, cachesize); +} + + diff --git a/quad.h b/quad.h new file mode 100644 index 0000000..c4580f2 --- /dev/null +++ b/quad.h @@ -0,0 +1,227 @@ +#ifndef QUAD_H +#define QUAD_H + +#include +#include +#include +#include +#include + +#include + +#include "cubiomes/finders.h" +#include "cubiomes/util.h" + +extern unsigned char biomeColors[256][3]; +extern unsigned char tempsColors[256][3]; + +enum { + D_NONE = -1, + D_DESERT, + D_JUNGLE, + D_IGLOO, + D_HUT, + D_VILLAGE, + D_MANSION, + D_MONUMENT, + D_RUINS, + D_SHIPWRECK, + D_OUTPOST, + D_PORTAL, + D_SPAWN, + D_STRONGHOLD, + STRUCT_NUM +}; + +inline const char* mc2str(int mc) +{ + switch (mc) + { + case MC_1_7: return "1.7"; break; + case MC_1_8: return "1.8"; break; + case MC_1_9: return "1.9"; break; + case MC_1_10: return "1.10"; break; + case MC_1_11: return "1.11"; break; + case MC_1_12: return "1.12"; break; + case MC_1_13: return "1.13"; break; + case MC_1_14: return "1.14"; break; + case MC_1_15: return "1.15"; break; + case MC_1_16: return "1.16"; break; + default: return NULL; + } +} + +inline int str2mc(const char *s) +{ + if (!strcmp(s, "1.16")) return MC_1_16; + if (!strcmp(s, "1.15")) return MC_1_15; + if (!strcmp(s, "1.14")) return MC_1_14; + if (!strcmp(s, "1.13")) return MC_1_13; + if (!strcmp(s, "1.12")) return MC_1_12; + if (!strcmp(s, "1.11")) return MC_1_11; + if (!strcmp(s, "1.10")) return MC_1_10; + if (!strcmp(s, "1.9")) return MC_1_9; + if (!strcmp(s, "1.8")) return MC_1_8; + if (!strcmp(s, "1.7")) return MC_1_7; + return -1; +} + +// get a random 64-bit integer +static inline int64_t getRnd64() +{ + static QMutex mutex; + static std::random_device rd; + static std::mt19937_64 mt(rd()); + static uint64_t x = (uint64_t) time(0); + int64_t ret = 0; + mutex.lock(); + if (rd.entropy()) + { + std::uniform_int_distribution d; + ret = d(mt); + } + else + { + const uint64_t c = 0xd6e8feb86659fd93ULL; + x ^= x >> 32; + x *= c; + x ^= x >> 32; + x *= c; + x ^= x >> 32; + ret = (int64_t) x; + } + mutex.unlock(); + return ret; +} + +enum { S_TEXT, S_NUMERIC, S_RANDOM }; +inline int str2seed(const char *str, int64_t *out) +{ + int slen = strlen(str); + char *p; + if (slen == 0) + { + *out = getRnd64(); + return S_RANDOM; + } + + *out = strtoll(str, &p, 10); + if (str + slen == p) + return S_NUMERIC; + + // String.hashCode(); + *out = 0; + for (int i = 0; i < slen; i++) + *out = 31*(*out) + str[i]; + *out &= (uint32_t)-1; + return S_TEXT; +} + + +struct Level; + +class Quad : public QRunnable +{ +public: + Quad(const Level* l, int i, int j); + ~Quad(); + + + std::vector *addStruct(const StructureConfig sconf, LayerStack *g); + void run(); + + int mc; + const Layer *entry; + int64_t seed; + int ti, tj; + int blocks; + int pixs; + int stype; + + uchar *rgb; + + // img and spos act as an atomic gate (with NULL or non-NULL indicating available results) + QAtomicPointer img; + QAtomicPointer> spos; + + std::atomic_bool done; // indicates that no further processing will occur + +public: + // externally managed (read/write in controller thread only) + int prio; + int stopped; // not done, and also not in processing queue +}; + + +struct Level +{ + Level(); + ~Level(); + + void init4map(int mc, int64_t ws, int pix, int layerscale); + void init4struct(int mc, int64_t ws, int blocks, int stype); + + void resizeLevel(std::vector& cache, int x, int z, int w, int h); + void update(std::vector& cache, qreal bx0, qreal bz0, qreal bx1, qreal bz1); + + std::vector cells; + LayerStack g; + Layer *entry; + int64_t seed; + int mc; + int tx, tz, tw, th; + int scale; + int blocks; + int pixs; + int stype; +}; + + +struct QWorld +{ + QWorld(int mc, int64_t seed); + ~QWorld(); + + void cleancache(std::vector& cache, unsigned int maxsize); + + void draw(QPainter& painter, int vw, int vh, qreal focusx, qreal focusz, qreal blocks2pix); + + + int mc; + int64_t seed; + LayerStack g; + + // the visible area is managed in Quads of different scales (for biomes and structures), + // which are managed in rectangular sections as levels + std::vector lv; // levels for biomes + std::vector lvs; // levels for structures + int activelv; // currently visible level + int structlv; // currently visible structure level + + // processed Quads are cached until they are too far out of view + std::vector cached; + std::vector cachedstruct; + unsigned int cachesize; + + bool sshow[STRUCT_NUM]; + + // spawn and strongholds will be filled by a designated worker thread once results are done + QAtomicPointer spawn; + QAtomicPointer> strongholds; + // isdel is a flag for the worker thread to stop + std::atomic_bool isdel; + + // structure selection from mouse position + bool seldo; + qreal selx, selz; + int seltype; + Pos selpos; + + qreal qual; // quality, i.e. maximum pixels per 'block' at the current layer + + QPixmap icons[STRUCT_NUM]; +}; + + + +#endif // QUAD_H diff --git a/quadlistdialog.cpp b/quadlistdialog.cpp new file mode 100644 index 0000000..70b89c2 --- /dev/null +++ b/quadlistdialog.cpp @@ -0,0 +1,189 @@ +#include "quadlistdialog.h" +#include "ui_quadlistdialog.h" + +#include "mapview.h" + +#include "search.h" + +#include +#include +#include + + +QuadListDialog::QuadListDialog(MainWindow *mainwindow) + : QDialog(mainwindow) + , ui(new Ui::QuadListDialog) + , mainwindow(mainwindow) +{ + ui->setupUi(this); + + QFont mono = QFont("Monospace", 9); + mono.setStyleHint(QFont::TypeWriter); + ui->listQuadHuts->setFont(mono); + ui->listQuadHuts->setColumnWidth(0, 80); + ui->listQuadHuts->setColumnWidth(1, 160); + + loadSeed(); + refresh(); +} + +QuadListDialog::~QuadListDialog() +{ + delete ui; +} + + +void QuadListDialog::loadSeed() +{ + ui->comboBoxMC->setCurrentText("1.16"); + ui->lineSeed->clear(); + + int mc; + int64_t seed; + mainwindow->getSeed(&mc, &seed, false); + + const char *mcstr = mc2str(mc); + if (!mcstr) + { + qDebug() << "Unknown MC version: " << mc; + return; + } + + ui->comboBoxMC->setCurrentText(mcstr); + ui->lineSeed->setText(QString::asprintf("%" PRId64, seed)); +} + +bool QuadListDialog::getSeed(int *mc, int64_t *seed) +{ + const std::string& mcs = ui->comboBoxMC->currentText().toStdString(); + *mc = str2mc(mcs.c_str()); + if (*mc < 0) + { + qDebug() << "Unknown MC version: " << *mc; + return false; + } + + const QByteArray& ba = ui->lineSeed->text().toLocal8Bit(); + int v = str2seed(ba.data(), seed); + if (v == S_RANDOM) + ui->lineSeed->setText(QString::asprintf("%" PRId64, *seed)); + + return true; +} + +void QuadListDialog::refresh() +{ + ui->listQuadHuts->setRowCount(0); + ui->labelMsg->clear(); + + int mc; + int64_t seed; + if (!getSeed(&mc, &seed)) + return; + + LayerStack g; + setupGenerator(&g, mc); + + StructureConfig sconf = mc >= MC_1_13 ? SWAMP_HUT_CONFIG : SWAMP_HUT_CONFIG_112; + const int maxqh = 1000; + Pos *qhlist = new Pos[maxqh]; + const int64_t *lbits = low20QuadHutBarely; + int lbitcnt = sizeof(low20QuadHutBarely) / sizeof(int64_t); + int r = 3e7 / 512; + int qhcnt = 0; + + for (int i = 0; i < lbitcnt; i++) + { + int64_t l20 = lbits[i]; + qhcnt += scanForQuads(sconf, (seed + sconf.salt) & MASK48, l20, -r, -r, 2*r, 2*r, qhlist+qhcnt, maxqh-qhcnt); + } + if (qhcnt >= maxqh) + QMessageBox::warning(this, "Warning", "Quad-hut scanning buffer exhausted, results will be incomplete."); + + ui->listQuadHuts->setSortingEnabled(false); + int qhn = 0; + for (int i = 0; i < qhcnt; i++) + { + Pos qh[4] = { + getStructurePos(sconf, seed, qhlist[i].x+0, qhlist[i].z+0, 0), + getStructurePos(sconf, seed, qhlist[i].x+0, qhlist[i].z+1, 0), + getStructurePos(sconf, seed, qhlist[i].x+1, qhlist[i].z+0, 0), + getStructurePos(sconf, seed, qhlist[i].x+1, qhlist[i].z+1, 0), + }; + if (isViableStructurePos(sconf.structType, mc, &g, seed, qh[0].x, qh[0].z) && + isViableStructurePos(sconf.structType, mc, &g, seed, qh[1].x, qh[1].z) && + isViableStructurePos(sconf.structType, mc, &g, seed, qh[2].x, qh[2].z) && + isViableStructurePos(sconf.structType, mc, &g, seed, qh[3].x, qh[3].z)) + { + ui->listQuadHuts->insertRow(qhn); + Pos afk; + afk = getOptimalAfk(qh, 7,7,9, 0); + float rad = isQuadBaseFeature(sconf, moveStructure(seed, -qhlist[i].x, -qhlist[i].z), 7,7,9, 128); + int dist = (int) round(sqrt(afk.x * (qreal)afk.x + afk.z * (qreal)afk.z)); + QVariant var = QVariant::fromValue(afk); + + QTableWidgetItem* distitem = new QTableWidgetItem(); + distitem->setData(Qt::UserRole, var); + distitem->setData(Qt::DisplayRole, dist); + ui->listQuadHuts->setItem(qhn, 0, distitem); + + QTableWidgetItem* afkitem = new QTableWidgetItem(); + afkitem->setData(Qt::UserRole, var); + afkitem->setText(QString::asprintf("(%d,%d)", afk.x, afk.z)); + ui->listQuadHuts->setItem(qhn, 1, afkitem); + + QTableWidgetItem* raditem = new QTableWidgetItem(); + raditem->setData(Qt::UserRole, var); + raditem->setText(QString::asprintf("%.1f", rad)); + ui->listQuadHuts->setItem(qhn, 2, raditem); + qhn++; + } + } + ui->listQuadHuts->setSortingEnabled(true); + ui->listQuadHuts->sortByColumn(0, Qt::AscendingOrder); + + if (qhn == 0) + ui->labelMsg->setText("World contains no quad-huts."); + else + ui->labelMsg->setText(QString::asprintf("World contains %d quad-hut%s.", qhn, qhn==1?"":"s")); + + delete[] qhlist; +} + +void QuadListDialog::on_buttonGo_clicked() +{ + refresh(); +} + +void QuadListDialog::on_listQuadHuts_customContextMenuRequested(const QPoint &pos) +{ + QMenu menu(this); + menu.addAction("Show in map viewer", this, &QuadListDialog::gotoSwampHut); + menu.exec(ui->listQuadHuts->mapToGlobal(pos)); +} + +void QuadListDialog::gotoSwampHut() +{ + MapView *mapView = mainwindow->getMapView(); + QTableWidgetItem *item = ui->listQuadHuts->currentItem(); + if (!item) + return; + + int mc; + int64_t seed; + if (!getSeed(&mc, &seed)) + return; + + QVariant dat = item->data(Qt::UserRole); + if (dat.isValid()) + { + Pos p = qvariant_cast(dat); + mainwindow->setSeed(mc, seed); + mapView->setView(p.x+0.5, p.z+0.5); + } +} + +void QuadListDialog::on_buttonClose_clicked() +{ + close(); +} diff --git a/quadlistdialog.h b/quadlistdialog.h new file mode 100644 index 0000000..412dc5c --- /dev/null +++ b/quadlistdialog.h @@ -0,0 +1,38 @@ +#ifndef QUADLISTDIALOG_H +#define QUADLISTDIALOG_H + +#include +#include "mainwindow.h" + +namespace Ui { +class QuadListDialog; +} + +class QuadListDialog : public QDialog +{ + Q_OBJECT + +public: + explicit QuadListDialog(MainWindow *mainwindow); + ~QuadListDialog(); + + void loadSeed(); + void refresh(); + + bool getSeed(int *mc, int64_t *seed); + +private slots: + void on_buttonGo_clicked(); + + void on_listQuadHuts_customContextMenuRequested(const QPoint &pos); + + void gotoSwampHut(); + + void on_buttonClose_clicked(); + +private: + Ui::QuadListDialog *ui; + MainWindow *mainwindow; +}; + +#endif // QUADLISTDIALOG_H diff --git a/quadlistdialog.ui b/quadlistdialog.ui new file mode 100644 index 0000000..0f03e14 --- /dev/null +++ b/quadlistdialog.ui @@ -0,0 +1,205 @@ + + + QuadListDialog + + + + 0 + 0 + 480 + 333 + + + + Quad-Hut Scan + + + + + + Quad-Huts + + + + + + + Monospace + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + + Distance + + + distance to origin + + + AlignLeading|AlignVCenter + + + + + Optimal centre + + + optimal AFK location + + + AlignLeading|AlignVCenter + + + + + Spawn range + + + distance to furthest spawning space from optimal AFK position + + + AlignLeading|AlignVCenter + + + + + + + + + + + + + + + + + + + + Seed can be an integer or text. Leave empty for a random seed. + + + seed: + + + + + + + Minecraft version + + + + 1.16 + + + + + 1.15 + + + + + 1.14 + + + + + 1.13 + + + + + 1.12 + + + + + 1.11 + + + + + 1.10 + + + + + 1.9 + + + + + 1.8 + + + + + 1.7 + + + + + + + + press enter to accept + + + + + + + Minecraft version + + + MC + + + + + + + Go + + + + + + + + + Close + + + + + + + + + + + diff --git a/search.cpp b/search.cpp new file mode 100644 index 0000000..93b8060 --- /dev/null +++ b/search.cpp @@ -0,0 +1,894 @@ +#include "search.h" +#include "mainwindow.h" + +#include + +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#endif + +extern MainWindow *gMainWindowInstance; + +// Quad monument bases are too expensive to generate on the fly and there are +// so few of them that they can be hard coded, rather than loading from a file. +const int64_t g_qm_90[] = { + 35624347962, + 775379617447, + 3752024106001, + 6745614706047, + 8462955635640, + 9735132392160, + 10800300288310, + 11692770796600, + 15412118464919, + 17507532114595, + 20824644731942, + 22102701941684, + 23762458057008, + 25706119531719, + 30282993829760, + 35236447787959, + 36751564646809, + 36982453953202, + 40642997855160, + 40847762196737, + 42617133245824, + 43070154706705, + 45369094039004, + 46388611271005, + 49815551084927, + 55209383814200, + 60038067905464, + 62013253870977, + 64801897210897, + 64967864749064, + 65164403480601, + 69458416339619, + 69968827844832, + 73925647436179, + 75345272448242, + 75897465569912, + 75947388701440, + 77139057518714, + 80473739688725, + 80869452536592, + 85241154759688, + 85336458606471, + 85712023677536, + 88230710669198, + 89435894990993, + 91999529042303, + 96363285020895, + 96666161703135, + 97326727082256, + 108818298610594, + 110070513256351, + 110929712933903, + 113209235869070, + 117558655912178, + 121197807897998, + 141209504516769, + 141849611674552, + 143737598577672, + 152637000035722, + 157050650953070, + 170156303860785, + 177801550039713, + 183906212826120, + 184103110528026, + 185417005583496, + 195760072598584, + 197672667717871, + 201305960334702, + 206145718978215, + 208212272645282, + 210644031421853, + 211691056285867, + 211760277005049, + 214621983270272, + 215210457001278, + 215223265529230, + 218746494888768, + 220178916199595, + 220411714922367, + 222407756997991, + 222506979025848, + 223366717375839, + 226043527056401, + 226089475358070, + 226837059463777, + 228023673284850, + 230531729507551, + 233072888622088, + 233864988591288, + 235857097051144, + 236329863308326, + 240806176474748, + 241664440380224, + 244715397179172, + 248444967740433, + 249746457285392, + 252133682596189, + 254891649599679, + 256867214419776, + 257374503348631, + 257985200458337, + 258999802520935, + 260070444629216, + 260286378141952, + 261039947696903, + 264768533253187, + 265956688913983, +}; + +const int64_t g_qm_95[] = { + 775379617447, + 40642997855160, + 75345272448242, + 85241154759688, + 143737598577672, + 201305960334702, + 206145718978215, + 220178916199595, + 226043527056401, +}; + + +__attribute__((const, used)) +static int qhutQual(int low20) +{ + switch (low20) + { + case 0x1272d: return F_QH_BARELY; + case 0x17908: return F_QH_BARELY; + case 0x367b9: return F_QH_BARELY; + case 0x43f18: return F_QH_IDEAL; + case 0x487c9: return F_QH_BARELY; + case 0x487ce: return F_QH_BARELY; + case 0x50aa7: return F_QH_BARELY; + case 0x647b5: return F_QH_NORMAL; + + case 0x65118: return F_QH_BARELY; + case 0x75618: return F_QH_NORMAL; + case 0x79a0a: return F_QH_IDEAL; + case 0x89718: return F_QH_NORMAL; + case 0x9371a: return F_QH_NORMAL; + case 0x967ec: return F_QH_BARELY; + case 0xa3d0a: return F_QH_BARELY; + case 0xa5918: return F_QH_BARELY; + + case 0xa591d: return F_QH_BARELY; + case 0xa5a08: return F_QH_NORMAL; + case 0xb5e18: return F_QH_NORMAL; + case 0xc6749: return F_QH_BARELY; + case 0xc6d9a: return F_QH_BARELY; + case 0xc751a: return F_QH_CLASSIC; + case 0xd7108: return F_QH_BARELY; + case 0xd717a: return F_QH_BARELY; + + case 0xe2739: return F_QH_BARELY; + case 0xe9918: return F_QH_BARELY; + case 0xee1c4: return F_QH_BARELY; + case 0xf520a: return F_QH_IDEAL; + + default: return 0; + } +} + +// returns for a >90% quadmonument the number of blocks, by area, in spawn range +__attribute__((const, used)) +static int qmonumentQual(int64_t s48) +{ + switch ((s48) & ((1LL<<48)-1)) + { + case 35624347962LL: return 12409; + case 775379617447LL: return 12796; + case 3752024106001LL: return 12583; + case 6745614706047LL: return 12470; + case 8462955635640LL: return 12190; + case 9735132392160LL: return 12234; + case 10800300288310LL: return 12443; + case 11692770796600LL: return 12748; + case 15412118464919LL: return 12463; + case 17507532114595LL: return 12272; + case 20824644731942LL: return 12470; + case 22102701941684LL: return 12227; + case 23762458057008LL: return 12165; + case 25706119531719LL: return 12163; + case 30282993829760LL: return 12236; + case 35236447787959LL: return 12338; + case 36751564646809LL: return 12459; + case 36982453953202LL: return 12499; + case 40642997855160LL: return 12983; + case 40847762196737LL: return 12296; + case 42617133245824LL: return 12234; + case 43070154706705LL: return 12627; + case 45369094039004LL: return 12190; + case 46388611271005LL: return 12299; + case 49815551084927LL: return 12296; + case 55209383814200LL: return 12632; + case 60038067905464LL: return 12227; + case 62013253870977LL: return 12470; + case 64801897210897LL: return 12780; + case 64967864749064LL: return 12376; + case 65164403480601LL: return 12125; + case 69458416339619LL: return 12610; + case 69968827844832LL: return 12236; + case 73925647436179LL: return 12168; + case 75345272448242LL: return 12836; + case 75897465569912LL: return 12343; + case 75947388701440LL: return 12234; + case 77139057518714LL: return 12155; + case 80473739688725LL: return 12155; + case 80869452536592LL: return 12165; + case 85241154759688LL: return 12799; + case 85336458606471LL: return 12651; + case 85712023677536LL: return 12212; + case 88230710669198LL: return 12499; + case 89435894990993LL: return 12115; + case 91999529042303LL: return 12253; + case 96363285020895LL: return 12253; + case 96666161703135LL: return 12470; + case 97326727082256LL: return 12165; + case 108818298610594LL: return 12150; + case 110070513256351LL: return 12400; + case 110929712933903LL: return 12348; + case 113209235869070LL: return 12130; + case 117558655912178LL: return 12687; + case 121197807897998LL: return 12130; + case 141209504516769LL: return 12147; + case 141849611674552LL: return 12630; + case 143737598577672LL: return 12938; + case 152637000035722LL: return 12130; + case 157050650953070LL: return 12588; + case 170156303860785LL: return 12348; + case 177801550039713LL: return 12389; + case 183906212826120LL: return 12358; + case 184103110528026LL: return 12630; + case 185417005583496LL: return 12186; + case 195760072598584LL: return 12118; + case 197672667717871LL: return 12553; + case 201305960334702LL: return 12948; + case 206145718978215LL: return 12796; + case 208212272645282LL: return 12317; + case 210644031421853LL: return 12261; + case 211691056285867LL: return 12478; + case 211760277005049LL: return 12539; + case 214621983270272LL: return 12236; + case 215210457001278LL: return 12372; + case 215223265529230LL: return 12499; + case 218746494888768LL: return 12234; + case 220178916199595LL: return 12848; + case 220411714922367LL: return 12470; + case 222407756997991LL: return 12458; + case 222506979025848LL: return 12632; + case 223366717375839LL: return 12296; + case 226043527056401LL: return 13028; // best + case 226089475358070LL: return 12285; + case 226837059463777LL: return 12305; + case 228023673284850LL: return 12742; + case 230531729507551LL: return 12296; + case 233072888622088LL: return 12376; + case 233864988591288LL: return 12376; + case 235857097051144LL: return 12632; + case 236329863308326LL: return 12396; + case 240806176474748LL: return 12190; + case 241664440380224LL: return 12118; + case 244715397179172LL: return 12300; + case 248444967740433LL: return 12780; + case 249746457285392LL: return 12391; + case 252133682596189LL: return 12299; + case 254891649599679LL: return 12296; + case 256867214419776LL: return 12234; + case 257374503348631LL: return 12391; + case 257985200458337LL: return 12118; + case 258999802520935LL: return 12290; + case 260070444629216LL: return 12168; + case 260286378141952LL: return 12234; + case 261039947696903LL: return 12168; + case 264768533253187LL: return 12242; + case 265956688913983LL: return 12118; + + default: return 0; + } +} + +int check(int64_t s48, void *data) +{ + const StructureConfig sconf = *(const StructureConfig*) data; + return isQuadBase(sconf, s48 - sconf.salt, 128); +} + +/* Loads a seed list for a filter type from disk, or generates it if neccessary. + * @mc mincreaft version + * @ftyp filter type + * @qb output seed base list + * @qbn output length of seed base list + * @dyn list was dynamically allocated and requires a free + * @sconf structure configuration used for the bases + */ +static void genSeedBases(int mc, int ftyp, const int64_t **qb, int64_t *qbn, + int *dyn, StructureConfig *sconf) +{ + char fnam[128]; + + const char *lbstr = NULL; + const int64_t *lbset = NULL; + int64_t lbcnt = 0; + int64_t *dqb = NULL; + + *qb = NULL; + *qbn = 0; + *dyn = 0; + + switch (ftyp) + { + case F_QH_IDEAL: + lbstr = "ideal"; + lbset = low20QuadIdeal; + lbcnt = sizeof(low20QuadIdeal) / sizeof(int64_t); + goto L_QH_ANY; + case F_QH_CLASSIC: + lbstr = "cassic"; + lbset = low20QuadClassic; + lbcnt = sizeof(low20QuadClassic) / sizeof(int64_t); + goto L_QH_ANY; + case F_QH_NORMAL: + lbstr = "normal"; + lbset = low20QuadHutNormal; + lbcnt = sizeof(low20QuadHutNormal) / sizeof(int64_t); + goto L_QH_ANY; + case F_QH_BARELY: + lbstr = "barely"; + lbset = low20QuadHutBarely; + lbcnt = sizeof(low20QuadHutBarely) / sizeof(int64_t); + goto L_QH_ANY; +L_QH_ANY: + snprintf(fnam, sizeof(fnam), "protobases/quad_%s.txt", lbstr); + *sconf = mc <= MC_1_12 ? SWAMP_HUT_CONFIG_112 : SWAMP_HUT_CONFIG; + + if ((dqb = loadSavedSeeds(fnam, qbn)) == NULL) + { + QMetaObject::invokeMethod(gMainWindowInstance, "openProtobaseMsg", Qt::QueuedConnection, Q_ARG(QString, QString(fnam))); + + int threads = QThread::idealThreadCount(); + int err = searchAll48(&dqb, qbn, fnam, threads, lbset, lbcnt, 20, check, sconf); + + QMetaObject::invokeMethod(gMainWindowInstance, "closeProtobaseMsg", Qt::BlockingQueuedConnection); + + if (err) + { + QMetaObject::invokeMethod( + gMainWindowInstance, "warning", Qt::BlockingQueuedConnection, + Q_ARG(QString, QString("Warning")), + Q_ARG(QString, QString("Failed to generate protobases."))); + return; + } + } + if (dqb) + { + // convert protobases to proper bases by subtracting the salt + for (int64_t i = 0; i < (*qbn); i++) + dqb[i] -= sconf->salt; + *qb = (const int64_t*)dqb; + *dyn = 1; + } + break; + + case F_QM_95: + *qb = g_qm_95; + *qbn = sizeof(g_qm_95) / sizeof(int64_t); + *sconf = MONUMENT_CONFIG; + *dyn = 0; + break; + case F_QM_90: + *qb = g_qm_90; + *qbn = sizeof(g_qm_90) / sizeof(int64_t); + *sconf = MONUMENT_CONFIG; + *dyn = 0; + break; + } +} + +static int cmp_baseitem(const void *a, const void *b) +{ + return *(int64_t*)a > *(int64_t*)b; +} + +/* Produces a list of seed bases from precomputed lists, provided all candidates + * fit into a buffer. + * + * @param mc mincraft version + * @param cond conditions + * @param ccnt number of conditions + * @param bufmax maximum allowed buffer size + */ +CandidateList getCandidates(int mc, const Condition *cond, int ccnt, int64_t bufmax) +{ + int ci; + CandidateList clist = {}; + + for (ci = 0; ci < ccnt; ci++) + { + int64_t qbn = 0; + const int64_t *qb = NULL; + StructureConfig sconf; + int dyn; + + if (cond[ci].relative == 0) + { + genSeedBases(mc, cond[ci].type, &qb, &qbn, &dyn, &sconf); + + if (qb) + { + int x = (cond[ci].x1 >> 9); + int z = (cond[ci].z1 >> 9); + int w = (cond[ci].x2 >> 9) - x; + int h = (cond[ci].z2 >> 9) - z; + + // does the set of candidates for this condition fit in memory? + if (qbn * w*h < bufmax * 4 * (int64_t)sizeof(*clist.items->spos)) + { + if (clist.items == NULL) + { + clist.bcnt = qbn * w*h; + clist.scnt = 4; + clist.isiz = sizeof(*clist.items) + clist.scnt * sizeof(*clist.items->spos); + clist.mem = (char*) calloc(clist.bcnt, clist.isiz); + + Candidate *item = (Candidate*)(clist.mem); + + int i, j; + int64_t q; + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + for (q = 0; q < qbn; q++) + { + item->seed = moveStructure(qb[q], x+i, z+j); + item->spos[0].sconf = sconf; + item->spos[0].x = x+i; + item->spos[0].z = z+j; + item->spos[1].sconf = sconf; + item->spos[1].x = x+i+1; + item->spos[1].z = z+j; + item->spos[2].sconf = sconf; + item->spos[2].x = x+i; + item->spos[2].z = z+j+1; + item->spos[3].sconf = sconf; + item->spos[3].x = x+i+1; + item->spos[3].z = z+j+1; + item = (Candidate*)((char*)item + clist.isiz); + } + } + } + } + else + { + // TODO: + // merge, i.e. filter out seeds and add new structure condition to item->spos + } + } + + if (dyn) + free((void*)qb); + } + } + } + + if (clist.items) + { + qsort(clist.items, clist.bcnt, clist.isiz, cmp_baseitem); + } + + return clist; +} + + +static bool intersectLineLine(double ax1, double az1, double ax2, double az2, double bx1, double bz1, double bx2, double bz2) +{ + double ax = ax2 - ax1, az = az2 - az1; + double bx = bx2 - bx1, bz = bz2 - bz1; + double adotb = ax * bz - az * bx; + if (adotb == 0) + return false; // parallel + + double cx = bx1 - ax1, cz = bz1 - az1; + double t; + t = (cx * az - cz * ax) / adotb; + if (t < 0 || t > 1) + return false; + t = (cx * bz - cz * bx) / adotb; + if (t < 0 || t > 1) + return false; + + return true; +} + +// does the line segment l1->l2 intersect the rectangle r1->r2 +static bool intersectRectLine(double rx1, double rz1, double rx2, double rz2, double lx1, double lz1, double lx2, double lz2) +{ + if (lx1 >= rx1 && lx1 <= rx2 && lz1 >= rz1 && lz1 <= rz2) return true; + if (lx2 >= rx1 && lx2 <= rx2 && lz2 >= rz1 && lz2 <= rz2) return true; + if (intersectLineLine(lx1, lz1, lx2, lz2, rx1, rz1, rx1, rz2)) return true; + if (intersectLineLine(lx1, lz1, lx2, lz2, rx1, rz2, rx2, rz2)) return true; + if (intersectLineLine(lx1, lz1, lx2, lz2, rx2, rz2, rx2, rz1)) return true; + if (intersectLineLine(lx1, lz1, lx2, lz2, rx2, rz1, rx1, rz1)) return true; + return false; +} + +static bool isInnerRingOk(int mc, int64_t seed, int x1, int z1, int x2, int z2, int r1, int r2) +{ + StrongholdIter sh; + Pos p = initFirstStronghold(&sh, mc, seed); + + if (p.x >= x1 && p.x <= x2 && p.z >= z1 && p.z <= z2) + return true; + // Do a ray cast analysis, checking if any of the generation angles intersect the area. + double c, s; + c = cos(sh.angle + M_PI*2/3); + s = sin(sh.angle + M_PI*2/3); + if (intersectRectLine(x1-112, z1-112, x2+112, z2+112, c*r1, s*r1, c*r2, s*r2)) + return true; + c = cos(sh.angle + M_PI*4/3); + s = sin(sh.angle + M_PI*4/3); + if (intersectRectLine(x1-112, z1-112, x2+112, z2+112, c*r1, s*r1, c*r2, s*r2)) + return true; + + return false; +} + +int testCond(StructPos *spos, int64_t seed, const Condition *cond, int mc, LayerStack *g, volatile bool *abort) +{ + int x1, x2, z1, z2; + int rx1, rx2, rz1, rz2, rx, rz; + Pos pc; + StructureConfig sconf; + int qual, valid; + int xt, zt; + int64_t s, r, rmin, rmax; + Pos p[128]; + + StructPos *sout = spos + cond->save; + + switch (cond->type) + { + case F_QH_IDEAL: + case F_QH_CLASSIC: + case F_QH_NORMAL: + case F_QH_BARELY: + sconf = mc <= MC_1_12 ? SWAMP_HUT_CONFIG_112 : SWAMP_HUT_CONFIG; + qual = cond->type; + x1 = cond->x1; + z1 = cond->z1; + x2 = cond->x2; + z2 = cond->z2; + + if (cond->relative) + { + x1 += spos[cond->relative].cx; + z1 += spos[cond->relative].cz; + x2 += spos[cond->relative].cx; + z2 += spos[cond->relative].cz; + } + + rx1 = x1 >> 9; + rz1 = z1 >> 9; + rx2 = x2 >> 9; + rz2 = z2 >> 9; + + for (rz = rz1; rz < rz2 && !*abort; rz++) + { + for (rx = rx1; rx < rx2; rx++) + { + s = moveStructure(seed, -rx, -rz); + if ( U(qhutQual((s + sconf.salt) & 0xfffff) >= qual) && + U(isQuadBaseFeature24(sconf, s, 7,7,9)) ) + { + sout->sconf = sconf; + p[0] = getStructurePos(sconf, seed, rx+0, rz+0, 0); + p[1] = getStructurePos(sconf, seed, rx+0, rz+1, 0); + p[2] = getStructurePos(sconf, seed, rx+1, rz+0, 0); + p[3] = getStructurePos(sconf, seed, rx+1, rz+1, 0); + pc = getOptimalAfk(p, 7,7,9, 0); + sout->cx = pc.x; + sout->cz = pc.z; + return 1; + } + } + } + return 0; + + case F_QM_95: qual = 58*58*4 * 95 / 100; goto L_QM_ANY; + case F_QM_90: qual = 58*58*4 * 90 / 100; +L_QM_ANY: + sconf = MONUMENT_CONFIG; + + x1 = cond->x1; + z1 = cond->z1; + x2 = cond->x2; + z2 = cond->z2; + + if (cond->relative) + { + x1 += spos[cond->relative].cx; + z1 += spos[cond->relative].cz; + x2 += spos[cond->relative].cx; + z2 += spos[cond->relative].cz; + } + + rx1 = x1 >> 9; + rz1 = z1 >> 9; + rx2 = x2 >> 9; + rz2 = z2 >> 9; + + + for (rz = rz1; rz < rz2 && !*abort; rz++) + { + for (rx = rx1; rx < rx2; rx++) + { + s = moveStructure(seed, -rx, -rz); + if (qmonumentQual(s) >= qual) + { + sout->sconf = sconf; + p[0] = getStructurePos(sconf, seed, rx+0, rz+0, 0); + p[1] = getStructurePos(sconf, seed, rx+0, rz+1, 0); + p[2] = getStructurePos(sconf, seed, rx+1, rz+0, 0); + p[3] = getStructurePos(sconf, seed, rx+1, rz+1, 0); + pc = getOptimalAfk(p, 58,23,58, 0); + sout->cx = pc.x; + sout->cz = pc.z; + return 1; + } + } + } + return 0; + + + case F_DESERT: + sconf = mc <= MC_1_12 ? DESERT_PYRAMID_CONFIG_112 : DESERT_PYRAMID_CONFIG; + goto L_STRUCT_ANY; + case F_HUT: + sconf = mc <= MC_1_12 ? SWAMP_HUT_CONFIG_112 : SWAMP_HUT_CONFIG; + goto L_STRUCT_ANY; + case F_JUNGLE: + sconf = mc <= MC_1_12 ? JUNGLE_PYRAMID_CONFIG_112 : JUNGLE_PYRAMID_CONFIG; + goto L_STRUCT_ANY; + case F_IGLOO: + sconf = mc <= MC_1_12 ? IGLOO_CONFIG_112 : IGLOO_CONFIG; + goto L_STRUCT_ANY; + case F_MONUMENT: sconf = MONUMENT_CONFIG; goto L_STRUCT_ANY; + case F_VILLAGE: sconf = VILLAGE_CONFIG; goto L_STRUCT_ANY; + case F_OUTPOST: sconf = OUTPOST_CONFIG; goto L_STRUCT_ANY; + +L_STRUCT_ANY: + x1 = cond->x1; + z1 = cond->z1; + x2 = cond->x2; + z2 = cond->z2; + + if (cond->relative) + { + x1 += spos[cond->relative].cx; + z1 += spos[cond->relative].cz; + x2 += spos[cond->relative].cx; + z2 += spos[cond->relative].cz; + } + + rx1 = x1 >> 9; + rz1 = z1 >> 9; + rx2 = x2 >> 9; + rz2 = z2 >> 9; + + + // TODO: warn if multistructure clusters are used as a positional + // dependency (the centre can change based on biomes) + + xt = 0; + zt = 0; + qual = 0; + + // Note "<=" + for (rz = rz1; rz <= rz2 && !*abort; rz++) + { + for (rx = rx1; rx <= rx2; rx++) + { + pc = getStructurePos(sconf, seed, rx, rz, &valid); + if (valid && pc.x >= x1 && pc.x < x2 && pc.z >= z1 && pc.z < z2) + { + if (g && !isViableStructurePos(sconf.structType, mc, g, seed, pc.x, pc.z)) + continue; + + xt += pc.x; + zt += pc.z; + + if (++qual >= cond->count) + { + sout->sconf = sconf; + sout->cx = xt / qual; + sout->cz = zt / qual; + return 1; + } + } + } + } + return 0; + + case F_SPAWN: + if (!g) return 1; + x1 = cond->x1; + z1 = cond->z1; + x2 = cond->x2; + z2 = cond->z2; + if (cond->relative) + { + x1 += spos[cond->relative].cx; + z1 += spos[cond->relative].cz; + x2 += spos[cond->relative].cx; + z2 += spos[cond->relative].cz; + } + applySeed(g, seed); + if (*abort) return 0; + pc = getSpawn(mc, g, NULL, seed); + return (pc.x >= x1 && pc.x < x2 && pc.z >= z1 && pc.z < z2); + + case F_STRONGHOLD: + x1 = cond->x1; + z1 = cond->z1; + x2 = cond->x2; + z2 = cond->z2; + if (cond->relative) + { + x1 += spos[cond->relative].cx; + z1 += spos[cond->relative].cz; + x2 += spos[cond->relative].cx; + z2 += spos[cond->relative].cz; + } + // TODO: add option for looking for the first stronghold only (which would be faster) + + rx1 = abs(x1); rx2 = abs(x2); + rz1 = abs(z1); rz2 = abs(z2); + if (x1 <= 112 && x2 >= -112 && z1 <= 112 && z2 >= -112) + { + rmin = 0; + } + else + { + xt = (rx1 < rx2 ? rx1 : rx2) - 112; + zt = (rz1 < rz2 ? rz1 : rz2) - 112; + rmin = xt*xt + zt*zt; + } + xt = (rx1 > rx2 ? rx1 : rx2) + 112; + zt = (rz1 > rz2 ? rz1 : rz2) + 112; + rmax = xt*xt + zt*zt; + + // -MC_1_8 formula: + // r = 640 + [0,1]*512 (+/-112) + // MC_1_9+ formula: + // r = 1408 + 3072*n + 1280*[0,1] (+/-112) + + if (mc < MC_1_9) + { + if (rmax < 640*640 || rmin > 1152*1152) + return 0; + r = 0; + rmin = 640; + rmax = 1152; + } + else + { // check if the area is entirely outside the radii ranges in which strongholds can generate + if (rmax < 1408*1408) + return 0; + rmin = sqrt(rmin); + rmax = sqrt(rmax); + r = (rmax - 1408) / 3072; // maximum relevant ring number + if (rmax - rmin < 3072-1280) // area does not span more than one ring + { + if (rmin > 1408+1280+3072*r) + return 0; // area is between rings + } + rmin = 1408; + rmax = 1408+1280; + } + // if we are only looking at the inner ring, we can check if the generation angles are suitable + if (r == 0 && !isInnerRingOk(mc, seed, x1, z1, x2, z2, rmin, rmax)) + return 0; + + // pre-biome-checks complete, the area appears to line up with possible generation positions + if (!g) + return 1; + else + { + StrongholdIter sh; + initFirstStronghold(&sh, mc, seed); + applySeed(g, seed); + qual = 0; + while (nextStronghold(&sh, g, NULL) > 0) + { + if (*abort || sh.ringnum > r) + break; + + if (sh.pos.x >= x1 && sh.pos.x < x2 && sh.pos.z >= z1 && sh.pos.z < z2) + if (++qual >= cond->count) + return 1; + + if (sh.ringnum == r && sh.ringidx+1 == sh.ringmax) + break; + } + } + return 0; + + // TODO: burried treasure + + case F_TEMP: + if (!g) return 1; + x1 = cond->x1; + z1 = cond->z1; + if (cond->relative) + { + x1 += spos[cond->relative].cx; + z1 += spos[cond->relative].cz; + } + return hasAllTemps(g, seed, x1 >> 10, z1 >> 10); + + case F_BIOME: + if (!g) return 1; + x1 = cond->x1; + z1 = cond->z1; + x2 = cond->x2 - cond->x1; // width + z2 = cond->z2 - cond->z1; // height + if (cond->relative) + { + x1 += spos[cond->relative].cx; + z1 += spos[cond->relative].cz; + } + if (*abort) return 0; + return checkForBiomes(g, L_VORONOI_ZOOM_1, NULL, seed, x1, z1, x2, z2, cond->bfilter, 0) > 0; + + default: + break; + } + + return 1; +} + + + +size_t searchFamily(int64_t seedbuf[], int64_t s, int scnt, int mc, + LayerStack *g, const Condition cond[], int ccnt, StructPos *spos, volatile bool *abort) +{ + const Condition *c, *ce = cond + ccnt; + + if (*abort) + return 0; + + for (c = cond; c != ce; c++) + if (!testCond(spos, s, c, mc, NULL, abort)) + return 0; + + for (c = cond; c != ce; c++) + if (g_filterinfo.list[c->type].cat != CAT_48) + break; + + int n = 0; + while (scnt--) + { + s += (1LL << 48); + for (const Condition *ct = c; ct != ce; ct++) + if (!testCond(spos, s, ct, mc, g, abort)) + goto L_NEXT_SEED; + + seedbuf[n++] = s; +L_NEXT_SEED:; + if (*abort) + break; + } + + return n; +} diff --git a/search.h b/search.h new file mode 100644 index 0000000..0186f6e --- /dev/null +++ b/search.h @@ -0,0 +1,263 @@ +#ifndef SEARCH_H +#define SEARCH_H + +#include "cubiomes/finders.h" + + +enum +{ + CAT_NONE, + CAT_48, + CAT_FULL, +}; + +struct FilterInfo +{ + int cat; + bool coord; + bool area; + bool biomes; + int step; + int count; + const char *icon; + const char *name; + const char *desription; +}; + +enum +{ + F_SELECT, + F_QH_IDEAL, + F_QH_CLASSIC, + F_QH_NORMAL, + F_QH_BARELY, + F_QM_95, + F_QM_90, + F_TEMP, + F_BIOME, + F_SPAWN, + F_STRONGHOLD, + F_DESERT, + F_JUNGLE, + F_HUT, + F_IGLOO, + F_MONUMENT, + F_VILLAGE, + F_OUTPOST, + FILTER_MAX, +}; + +// global table of filter data (as constants with enum indexing) +static const struct FilterList +{ + FilterInfo list[FILTER_MAX]; + + FilterList() : list{} + { + list[F_SELECT] = FilterInfo{ + CAT_NONE, 0, 0, 0, 0, 0, + NULL, + "", + "" + }; + + list[F_QH_IDEAL] = FilterInfo{ + CAT_48, 1, 1, 0, 512, 0, + NULL, + "Quad-hut (ideal)", + "The lower 48-bits provide potential for four swamp huts in " + "spawning range, in one of the best configurations that exist." + }; + + list[F_QH_CLASSIC] = FilterInfo{ + CAT_48, 1, 1, 0, 512, 0, + NULL, + "Quad-hut (classic)", + "The lower 48-bits provide potential for four swamp huts in " + "spawning range, in one of the \"classic\" configurations. " + "(Checks for huts in the nearest 2x2 chunk corners of each " + "region.)" + }; + + list[F_QH_NORMAL] = FilterInfo{ + CAT_48, 1, 1, 0, 512, 0, + NULL, + "Quad-hut (normal)", + "The lower 48-bits provide potential for four swamp huts in " + "spawning range, such that all of them are within 128 blocks " + "of a single AFK location, including a vertical tollerance " + "for a fall damage chute." + }; + + list[F_QH_BARELY] = FilterInfo{ + CAT_48, 1, 1, 0, 512, 0, + NULL, + "Quad-hut (barely)", + "The lower 48-bits provide potential for four swamp huts in " + "spawning range, in any configuration, such that the bounding " + "boxes are within 128 blocks of a single AFK location." + }; + + list[F_QM_95] = FilterInfo{ + CAT_48, 1, 1, 0, 512, 0, + NULL, + "Quad-ocean-monument (>95%)", + "The lower 48-bits provide potential for 95% of the area of " + "four ocean monuments to be within 128 blocks of an AFK " + "location." + }; + + list[F_QM_90] = FilterInfo{ + CAT_48, 1, 1, 0, 512, 0, + NULL, + "Quad-ocean-monument (>90%)", + "The lower 48-bits provide potential for 90% of the area of " + "four ocean monuments to be within 128 blocks of an AFK " + "location." + }; + + list[F_TEMP] = FilterInfo{ + CAT_FULL, 1, 0, 0, 1024, 0, + NULL, + "All temperatures cluster", + "Checks that the seed has all temperature categories " + "(Oceanic, Warm, Lush, Cold, Freezing, and special variants) " + "in a 3x3 cluster at layer scale 1024. (Input position " + "represents the center of diversity.)" + }; + + list[F_BIOME] = FilterInfo{ + CAT_FULL, 1, 1, 1, 1, 0, + NULL, + "Biome filter", + "Discard seeds that do not have the required biomes inside " + "the specified area." + }; + + list[F_SPAWN] = FilterInfo{ + CAT_FULL, 1, 1, 0, 1, 0, + ":icons/spawn.png", + "Spawn", + "" + }; + + list[F_STRONGHOLD] = FilterInfo{ + CAT_FULL, 1, 1, 0, 1, 1, + ":icons/stronghold.png", + "Stronghold", + "" + }; + + list[F_DESERT] = FilterInfo{ + CAT_FULL, 1, 1, 0, 512, 1, + ":icons/desert.png", + "Desert pyramid", + "" + }; + + list[F_JUNGLE] = FilterInfo{ + CAT_FULL, 1, 1, 0, 512, 1, + ":icons/jungle.png", + "Jungle temple", + "" + }; + + list[F_HUT] = FilterInfo{ + CAT_FULL, 1, 1, 0, 512, 1, + ":icons/hut.png", + "Swamp hut", + "" + }; + + list[F_IGLOO] = FilterInfo{ + CAT_FULL, 1, 1, 0, 512, 1, + ":icons/igloo.png", + "Igloo", + "" + }; + + list[F_MONUMENT] = FilterInfo{ + CAT_FULL, 1, 1, 0, 512, 1, + ":icons/monument.png", + "Ocean monument", + "" + }; + + list[F_VILLAGE] = FilterInfo{ + CAT_FULL, 1, 1, 0, 512, 1, + ":icons/village.png", + "Village", + "" + }; + + list[F_OUTPOST] = FilterInfo{ + CAT_FULL, 1, 1, 0, 512, 1, + ":icons/outpost.png", + "Pillager outpost", + "" + }; + } +} +g_filterinfo; + + +struct Condition +{ + int type; + int x1, z1, x2, z2; + int rx, rz, rw, rh; + int save; + int relative; + BiomeFilter bfilter; + int count; +}; + + +struct Candidate +{ + struct __attribute__((packed)) SPos + { + StructureConfig sconf; + int x, z; + }; + + // a candidate is a seed base and the structure positions it encompases + int64_t seed; + SPos spos[]; +}; + +struct CandidateList +{ + union { + Candidate *items; // list of candidate items + char *mem; + }; + int64_t isiz; // sizeof Candidate, each with scnt structures + int64_t scnt; // structures in each base item + int64_t bcnt; // number of base items +}; + + + +/* Attempts to construct a list of 48-bit bases that should be further checked. + * Any conditions that would result in a list larger than a buffer size will + * not be preloaded in this way. + */ +CandidateList getCandidates(int mc, const Condition *cond, int ccnt, int64_t bufmax); + + +struct StructPos +{ + StructureConfig sconf; + int cx, cz; // effective center position +}; + + +int testCond(StructPos *spos, int64_t seed, const Condition *cond, int mc, LayerStack *g, volatile bool *abort); + +size_t searchFamily(int64_t seedbuf[], int64_t s, int scnt, int mc, + LayerStack *g, const Condition cond[], int ccnt, StructPos *spos, volatile bool *abort); + + + +#endif // SEARCH_H diff --git a/searchthread.cpp b/searchthread.cpp new file mode 100644 index 0000000..cdce394 --- /dev/null +++ b/searchthread.cpp @@ -0,0 +1,166 @@ +#include "searchthread.h" +#include + +class FamilyBlock: public QRunnable +{ + // This class is a threadpool item for a range of upper 16-bit values to + // look through, following a full 64-bit seed. +public: + SearchThread *master; // master thread for results + int64_t sstart; // starting seed + int scnt; // number of upper 16-bit combinations to check + int mc; // mincraft version + const Condition* cond; // conditions to be met + int ccnt; // number of conditions + + FamilyBlock(SearchThread *t, int64_t sstart, int scnt, int mc, + const Condition* cond, int ccnt) + : master(t),sstart(sstart),scnt(scnt),mc(mc),cond(cond),ccnt(ccnt) + { + setAutoDelete(true); + } + + void run() + { + LayerStack g; + setupGenerator(&g, mc); + StructPos spos[100] = {}; + int64_t seedbuf[scnt]; + + int n = searchFamily(seedbuf, sstart, scnt, mc, &g, cond, ccnt, spos, &master->abortsearch); + if (n && !master->abortsearch) + { + master->mutex.lock(); + for (int i = 0; i < n; i++) + { + master->seeds.push_back(seedbuf[i]); + //printf("%ld\n", seedbuf[i]); fflush(stdout); + } + master->mutex.unlock(); + } + } +}; + +// called from main GUI thread +bool SearchThread::set(int64_t start48, int mc, const QVector& cv) +{ + this->sstart = start48; + this->mc = mc; + this->condvec = cv; + char refbuf[100] = {}; + + for (const Condition& c : cv) + { + if (c.save < 1 || c.save > 99) + { + QMessageBox::warning(NULL, "Warning", QString::asprintf("Condition with invalid ID [%02d].", c.save)); + return false; + } + if (c.relative && refbuf[c.relative] == 0) + { + QMessageBox::warning(NULL, "Warning", QString::asprintf( + "Condition with ID [%02d] has a broken reference position:\n" + "condition missing or out of order.", c.save)); + return false; + } + if (++refbuf[c.save] > 1) + { + QMessageBox::warning(NULL, "Warning", QString::asprintf("More than one condition with ID [%02d].", c.save)); + return false; + } + if (c.type != F_TEMP) + { + int w = c.x2 - c.x1; + int h = c.z2 - c.z1; + if (w * h < 1) + { + QMessageBox::warning(NULL, "Warning", QString::asprintf( + "Condition with ID [%02d] does not specify a valid area: (%d, %d) - (%d, %d).", + c.save, c.x1, c.z1, c.x2, c.z2)); + return false; + } + } + } + + return true; +} + +// does the 48-bit seed meet the conditions c..ce? +static bool isCandidate(int64_t s48, int mc, const Condition *c, const Condition *ce, volatile bool *abort) +{ + StructPos spos[100] = {}; + for (; c != ce; c++) + if (!testCond(spos, s48, c, mc, NULL, abort)) + return false; + return true; +} + +void SearchThread::run() +{ + abortsearch = false; + + const Condition *cond = condvec.data(); + int64_t ccnt = condvec.size(); + + CandidateList cl = getCandidates(mc, cond, ccnt, PRECOMPUTE48_BUFSIZ); + int64_t ci; + char *sp; + int64_t s48 = sstart; + + if (cl.mem) + { + // a pre-computed list of candidates exists, skip forwards to starting point + for (ci = 0, sp = cl.mem; ci < cl.bcnt; ci++, sp += cl.isiz) + if ((s48 = *(int64_t*)sp) >= sstart) + break; + + for (; ci < cl.bcnt && !abortsearch; ci++, sp += cl.isiz) + { + s48 = *(int64_t*)sp; + if (isCandidate(s48, mc, cond, cond+ccnt, &abortsearch)) + { + if (!abortsearch && runSearch48(s48, cond, ccnt) && stoponres) + break; + } + } + if (ci == cl.bcnt) + s48 = MASK48+1; + + free(cl.mem); + } + else + { + // go through all 48-bit seeds + for (; s48 <= MASK48 && !abortsearch; s48++) + { + if (isCandidate(s48, mc, cond, cond+ccnt, &abortsearch)) + { + if (!abortsearch && runSearch48(s48, cond, ccnt) && stoponres) + break; + } + } + } + + emit finish(s48); +} + +bool SearchThread::runSearch48(int64_t s48, const Condition* cond, int ccnt) +{ + // found a 48-bit seed: now distibute the search for the upper 16-bits onto a thread pool + seeds.clear(); + const int blocksize = 0x200; + const int blockcnt = 0x10000 / blocksize; + for (int i = 0; i < blockcnt; i++) + { + pool.start(new FamilyBlock(this, s48, blocksize, mc, cond, ccnt)); + s48 += (int64_t)blocksize << 48; + } + pool.waitForDone(); + emit baseDone(s48); + if (!seeds.empty()) + { + emit results(seeds, false); + return true; + } + return false; +} diff --git a/searchthread.h b/searchthread.h new file mode 100644 index 0000000..89de47f --- /dev/null +++ b/searchthread.h @@ -0,0 +1,53 @@ +#ifndef SEARCHTHREAD_H +#define SEARCHTHREAD_H + +#include +#include +#include +#include +#include + +#include "search.h" + +#define PRECOMPUTE48_BUFSIZ ((int64_t)1 << 30) + + +class SearchThread : public QThread +{ + Q_OBJECT + +public: + SearchThread(QObject *parent) : + QThread(parent),mc(),sstart(),condvec(),pool(this),stoponres(),seeds(),mutex() + { + } + + bool set(int64_t start48, int mc, const QVector& cv); + + void stop() { abortsearch = true; } + + void run() override; + bool runSearch48(int64_t s48, const Condition* cond, int ccnt); + +signals: + int results(QVector seeds, bool countonly); + void baseDone(int64_t s48); + void finish(int64_t s48); + +public slots: + void setStopOnResult(bool a) { stoponres = a; } + +protected: + int mc; + int64_t sstart; + QVector condvec; + QThreadPool pool; + bool stoponres; + +public: + QVector seeds; + QMutex mutex; + volatile bool abortsearch; +}; + +#endif // SEARCHTHREAD_H