Trial chambers etc

* added trial chambers (no icon yet)
* added sampling filter for climate noise (#287)
* fixed a crash from testing code for stack allocation (#290)
This commit is contained in:
Cubitect 2024-06-02 22:34:37 +02:00
parent 0683518389
commit 8d54874324
24 changed files with 613 additions and 335 deletions

@ -1 +1 @@
Subproject commit ae58fc49833db481ae493015ddffce317f1ace9b
Subproject commit 0af31b4e7eeb14a58c2bd9a4c4c68b97b4a7d6e8

View File

@ -13,7 +13,7 @@ QT += core widgets
CHARSET = -finput-charset=UTF-8 -fexec-charset=UTF-8
QMAKE_CFLAGS = $$CHARSET -fwrapv -DSTRUCT_CONFIG_OVERRIDE=1
QMAKE_CXXFLAGS = $$QMAKE_CFLAGS
QMAKE_CXXFLAGS_RELEASE *= -O3
QMAKE_CXXFLAGS_RELEASE *= -O3 -g3
greaterThan(QT_MAJOR_VERSION, 5) {
QMAKE_CXXFLAGS += -std=gnu++17

View File

@ -60,6 +60,11 @@
</screenshot>
</screenshots>
<recommends>
<display_length side="shortest">900</display_length>
<display_length side="longest">1600</display_length>
</recommends>
<url type="homepage">https://github.com/cubitect/cubiomes-viewer</url>
<url type="bugtracker">https://github.com/cubitect/cubiomes-viewer/issues</url>

View File

@ -83,5 +83,7 @@
<file>icons/zoom_in.png</file>
<file>icons/zoom_out.png</file>
<file>icons/zoom.png</file>
<file>icons/chambers.png</file>
<file>icons/chambers_d.png</file>
</qresource>
</RCC>

BIN
rc/icons/chambers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

BIN
rc/icons/chambers_d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

View File

@ -107,13 +107,17 @@ ConditionDialog::ConditionDialog(FormConditions *parent, MapView *mapview, Confi
ui->lineBiomeSize->setValidator(new QIntValidator(1, INT_MAX, this));
ui->lineTolerance->setValidator(new QIntValidator(0, 255, this));
ui->comboY->lineEdit()->setValidator(new QIntValidator(-64, 320, this));
initComboY(ui->comboY1, initcond ? initcond->y : 256);
initComboY(ui->comboY2, initcond ? initcond->y : 256);
ui->lineMin->setValidator(new QDoubleValidator(-1e6, 1e6, 4, this));
ui->lineMax->setValidator(new QDoubleValidator(-1e6, 1e6, 4, this));
QDoubleValidator *paraval = new QDoubleValidator(-1e6, 1e6, 4, this);
ui->lineMin->setValidator(paraval);
ui->lineMax->setValidator(paraval);
ui->lineCoverage->setValidator(new QDoubleValidator(0.0001, 100.0000, 4, this));
ui->lineConfidence->setValidator(new QDoubleValidator(0.0001, 99.9999, 4, this));
ui->lineCoverage2->setValidator(new QDoubleValidator(0.0001, 100.0000, 4, this));
ui->lineCoverage1->setValidator(new QDoubleValidator(0.0001, 100.0000, 4, this));
ui->lineConfidence1->setValidator(new QDoubleValidator(0.0001, 99.9999, 4, this));
ui->lineConfidence2->setValidator(new QDoubleValidator(0.0001, 99.9999, 4, this));
//qobject_cast<QListView*>(ui->comboCat->view())->setSpacing(1);
//qobject_cast<QListView*>(ui->comboType->view())->setSpacing(1);
@ -301,8 +305,10 @@ ConditionDialog::ConditionDialog(FormConditions *parent, MapView *mapview, Confi
ui->checkSkipRef->setChecked(false);
ui->radioSquare->setChecked(true);
ui->checkRadius->setChecked(false);
ui->lineCoverage->setText("50");
ui->lineConfidence->setText("95");
ui->lineCoverage1->setText("50");
ui->lineCoverage2->setText("50");
ui->lineConfidence1->setText("95");
ui->lineConfidence2->setText("95");
ui->comboScale->setCurrentIndex(1);
onCheckStartChanged(false);
on_comboClimatePara_currentIndexChanged(0);
@ -341,8 +347,11 @@ ConditionDialog::ConditionDialog(FormConditions *parent, MapView *mapview, Confi
on_comboClimatePara_currentIndexChanged(cond.para);
ui->comboOctaves->setCurrentIndex(cond.octave);
ui->comboMinMax->setCurrentIndex((cond.minmax & Condition::E_LOCATE_MAX) ? 1 : 0);
ui->lineMin->setText((cond.minmax & Condition::E_TEST_LOWER) ? QString::number(cond.vmin) : "");
ui->lineMax->setText((cond.minmax & Condition::E_TEST_UPPER) ? QString::number(cond.vmax) : "");
QString vmin, vmax;
if (cond.minmax & Condition::E_TEST_LOWER) vmin = QString::number(cond.vmin);
if (cond.minmax & Condition::E_TEST_UPPER) vmax = QString::number(cond.vmax);
ui->lineMin->setText(vmin);
ui->lineMax->setText(vmax);
ui->checkInvertRange->setChecked(cond.flags & Condition::FLG_INVERT);
updateMode();
@ -357,19 +366,10 @@ ConditionDialog::ConditionDialog(FormConditions *parent, MapView *mapview, Confi
ui->checkApprox->setChecked(cond.flags & Condition::FLG_APPROX);
ui->checkMatchAny->setChecked(cond.flags & Condition::FLG_MATCH_ANY);
ui->checkSamplePos->setChecked(cond.count == 1);
ui->lineCoverage->setText(QString::number(cond.converage ? cond.converage * 100 : 50));
ui->lineConfidence->setText(QString::number(cond.confidence ? cond.confidence * 100 : 95));
int i, n = ui->comboY->count();
for (i = 0; i < n; i++)
if (ui->comboY->itemText(i).section(' ', 0, 0).toInt() == cond.y)
break;
if (i >= n)
{
ui->comboY->addItem(QString::number(cond.y));
ui->comboY2->addItem(QString::number(cond.y));
}
ui->comboY->setCurrentIndex(i);
ui->lineCoverage1->setText(QString::number(cond.converage ? cond.converage * 100 : 50));
ui->lineCoverage2->setText(QString::number(cond.converage ? cond.converage * 100 : 50));
ui->lineConfidence1->setText(QString::number(cond.confidence ? cond.confidence * 100 : 95));
ui->lineConfidence2->setText(QString::number(cond.confidence ? cond.confidence * 100 : 95));
if (cond.x1 == cond.z1 && cond.x1 == -cond.x2 && cond.x1 == -cond.z2)
{
@ -486,6 +486,19 @@ void ConditionDialog::addTempCat(int temp, QString name)
}
}
void ConditionDialog::initComboY(QComboBox *cb, int y)
{
if (!cb->lineEdit()->validator())
cb->lineEdit()->setValidator(new QIntValidator(-64, 320, this));
int i, n = cb->count();
for (i = 0; i < n; i++)
if (cb->itemText(i).section(' ', 0, 0).toInt() == y)
break;
if (i >= n)
cb->addItem(QString::number(y));
cb->setCurrentIndex(i);
}
void ConditionDialog::updateMode()
{
int filterindex = ui->comboType->currentData().toInt();
@ -566,7 +579,8 @@ void ConditionDialog::updateMode()
ui->checkSkipRef->setEnabled(cnt);
ui->labelY->setEnabled(ft.hasy);
ui->comboY->setEnabled(ft.hasy);
ui->comboY1->setEnabled(ft.hasy);
ui->comboY2->setEnabled(ft.hasy);
if (filterindex == F_TEMPS)
{
@ -578,7 +592,15 @@ void ConditionDialog::updateMode()
}
else if (filterindex == F_CLIMATE_MINMAX)
{
ui->stackedWidget->setCurrentWidget(ui->pageMinMax);
ui->stackedWidget->setCurrentWidget(ui->pageNoise);
ui->stackedNoise->setCurrentWidget(ui->pageNoiseMinMax);
ui->groupBoxNoise->setTitle("Locate climate minimum/maximum");
}
else if (filterindex == F_NOISE_SAMPLE)
{
ui->stackedWidget->setCurrentWidget(ui->pageNoise);
ui->stackedNoise->setCurrentWidget(ui->pageNoiseSample);
ui->groupBoxNoise->setTitle("Climate noise samples");
}
else if (filterindex == F_BIOME_CENTER || filterindex == F_BIOME_CENTER_256)
{
@ -1035,7 +1057,7 @@ void ConditionDialog::onAccept()
else
c.rmax = 0;
c.y = ui->comboY->currentText().section(' ', 0, 0).toInt();
c.y = ui->comboY1->currentText().section(' ', 0, 0).toInt();
c.flags = 0;
if (ui->checkApprox->isChecked())
@ -1071,8 +1093,8 @@ void ConditionDialog::onAccept()
}
}
c.count = ui->checkSamplePos->isChecked() ? 1 : 0;
c.converage = ui->lineCoverage->text().toFloat() / 100.0;
c.confidence = ui->lineConfidence->text().toFloat() / 100.0;
c.converage = ui->lineCoverage1->text().toFloat() / 100.0;
c.confidence = ui->lineConfidence1->text().toFloat() / 100.0;
c.step = 0;
if (c.type == F_BIOME || c.type == F_BIOME_NETHER || c.type == F_BIOME_END)
c.step = ui->comboScale->currentData().toInt();
@ -1082,15 +1104,15 @@ void ConditionDialog::onAccept()
if (ui->stackedWidget->currentWidget() == ui->pageBiomeCenter)
{
c.biomeId = ui->comboMatchBiome->currentData().toInt();
c.y = ui->comboY2->currentText().section(' ', 0, 0).toInt();
c.biomeSize = ui->lineBiomeSize->text().toInt();
c.tol = ui->lineTolerance->text().toInt();
}
if (ui->stackedWidget->currentWidget() == ui->pageMinMax)
if (ui->stackedWidget->currentWidget() == ui->pageNoise)
{
c.para = ui->comboClimatePara->currentData().toInt();
c.octave = ui->comboOctaves->currentIndex();
c.minmax = ui->comboMinMax->currentIndex() == 0 ?
Condition::E_LOCATE_MIN : Condition::E_LOCATE_MAX;
c.minmax = ui->comboMinMax->currentIndex() == 0 ? Condition::E_LOCATE_MIN : Condition::E_LOCATE_MAX;
bool ok1, ok2;
c.vmin = ui->lineMin->text().toFloat(&ok1);
if (ok1) c.minmax |= Condition::E_TEST_LOWER;
@ -1098,6 +1120,9 @@ void ConditionDialog::onAccept()
if (ok2) c.minmax |= Condition::E_TEST_UPPER;
if (ok1 && ok2 && c.vmin > c.vmax)
std::swap(c.vmin, c.vmax);
c.count = ui->checkSamplePos2->isChecked() ? 1 : 0;
c.converage = ui->lineCoverage2->text().toFloat() / 100.0;
c.confidence = ui->lineConfidence2->text().toFloat() / 100.0;
}
if (ui->stackedWidget->currentWidget() == ui->pageTemps)
{
@ -1653,13 +1678,13 @@ void ConditionDialog::on_comboOctaves_currentIndexChanged(int)
ui->comboOctaves->setToolTip(ui->comboOctaves->currentData(Qt::ToolTipRole).toString());
}
void ConditionDialog::on_comboY_currentTextChanged(const QString &text)
void ConditionDialog::on_comboY1_currentTextChanged(const QString &text)
{
if (ui->comboY2->currentText() != text)
ui->comboY2->setCurrentText(text);
}
void ConditionDialog::on_comboY2_currentTextChanged(const QString &text)
{
if (ui->comboY->currentText() != text)
ui->comboY->setCurrentText(text);
if (ui->comboY1->currentText() != text)
ui->comboY1->setCurrentText(text);
}

View File

@ -56,6 +56,7 @@ public:
virtual ~ConditionDialog();
void addTempCat(int temp, QString name);
void initComboY(QComboBox *cb, int y);
void updateMode();
void updateBiomeSelection();
bool warnIfBad(Condition cond);
@ -112,7 +113,7 @@ private slots:
void on_comboClimatePara_currentIndexChanged(int index);
void on_comboOctaves_currentIndexChanged(int index);
void on_comboY_currentTextChanged(const QString &text);
void on_comboY1_currentTextChanged(const QString &text);
void on_comboY2_currentTextChanged(const QString &text);
private:

View File

@ -392,7 +392,7 @@ QPushButton:hover {
<number>0</number>
</property>
<property name="currentIndex">
<number>2</number>
<number>5</number>
</property>
<widget class="QWidget" name="pageNone">
<property name="enabled">
@ -631,14 +631,14 @@ yield each sampled position individually</string>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineCoverage">
<widget class="QLineEdit" name="lineCoverage1">
<property name="toolTip">
<string>Proportion of the area that has to be of the included biomes</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineConfidence">
<widget class="QLineEdit" name="lineConfidence1">
<property name="toolTip">
<string>Statistical confidence that the coverage has been reached</string>
</property>
@ -649,7 +649,7 @@ yield each sampled position individually</string>
</widget>
</item>
<item row="0" column="4">
<widget class="StyledComboBox" name="comboY">
<widget class="StyledComboBox" name="comboY1">
<property name="editable">
<bool>true</bool>
</property>
@ -1100,7 +1100,7 @@ yield each sampled position individually</string>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageMinMax">
<widget class="QWidget" name="pageNoise">
<layout class="QGridLayout" name="gridLayout_23">
<property name="leftMargin">
<number>0</number>
@ -1115,18 +1115,196 @@ yield each sampled position individually</string>
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_10">
<widget class="QGroupBox" name="groupBoxNoise">
<property name="title">
<string>Locate climate minimum/maximum</string>
</property>
<layout class="QGridLayout" name="gridLayout_24">
<item row="3" column="1">
<widget class="QLineEdit" name="lineMin">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Lower bound (inclusive)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<item row="0" column="1" colspan="4">
<widget class="StyledComboBox" name="comboClimatePara"/>
</item>
<item row="4" column="0" colspan="5">
<widget class="QStackedWidget" name="stackedNoise">
<property name="currentIndex">
<number>0</number>
</property>
<property name="placeholderText">
<string>-Infinity</string>
<widget class="QWidget" name="pageNoiseMinMax">
<layout class="QGridLayout" name="gridLayout_33">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="StyledComboBox" name="comboMinMax">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Minimum</string>
</property>
</item>
<item>
<property name="text">
<string>Maximum</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Yield position of:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>180</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageNoiseSample">
<layout class="QGridLayout" name="gridLayout_32">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="2" column="1">
<widget class="QCheckBox" name="checkSamplePos2">
<property name="toolTip">
<string>Instead of estimating the center of the allowed biome area,
yield each sampled position individually</string>
</property>
<property name="text">
<string>Yield individual samples</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineCoverage2"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_27">
<property name="toolTip">
<string>Statistical confidence that the coverage has been reached</string>
</property>
<property name="text">
<string>Confidence (%):</string>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer_9">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineConfidence2">
<property name="toolTip">
<string>Statistical confidence that the coverage has been reached</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Required coverage (%):</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>180</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>If value is in range:</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_14">
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="4">
<widget class="StyledComboBox" name="comboOctaves">
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
</widget>
</item>
@ -1137,36 +1315,6 @@ yield each sampled position individually</string>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>252</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="4">
<widget class="QCheckBox" name="checkInvertRange">
<property name="toolTip">
<string>Only match if value is outside the given range</string>
</property>
<property name="text">
<string>Invert range</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Yield position of:</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLineEdit" name="lineMax">
<property name="toolTip">
@ -1184,45 +1332,41 @@ yield each sampled position individually</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<item row="3" column="1">
<widget class="QLineEdit" name="lineMin">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Lower bound (inclusive)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="placeholderText">
<string>-Infinity</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QCheckBox" name="checkInvertRange">
<property name="toolTip">
<string>Only match if value is outside the given range</string>
</property>
<property name="text">
<string>If value is in range:</string>
<string>Invert range</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="4">
<widget class="StyledComboBox" name="comboMinMax">
<item>
<property name="text">
<string>Minimum</string>
</property>
</item>
<item>
<property name="text">
<string>Maximum</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1" colspan="4">
<widget class="StyledComboBox" name="comboClimatePara"/>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_14">
<property name="text">
<string>-</string>
<item row="5" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="1" colspan="4">
<widget class="StyledComboBox" name="comboOctaves">
<property name="font">
<font>
<family>Monospace</family>
</font>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
</widget>
<property name="sizeHint" stdset="0">
<size>
<width>180</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>

View File

@ -148,7 +148,7 @@ QString mapopt2display(int opt)
case D_VILLAGE: return QApplication::translate("Map", "Village");
case D_MANSION: return QApplication::translate("Map", "Woodland Mansion");
case D_MONUMENT: return QApplication::translate("Map", "Ocean Monument");
case D_RUINS: return QApplication::translate("Map", "Ocean Ruins");
case D_OCEANRUIN: return QApplication::translate("Map", "Ocean Ruin");
case D_SHIPWRECK: return QApplication::translate("Map", "Shipwreck");
case D_TREASURE: return QApplication::translate("Map", "Buried Treasure");
case D_MINESHAFT: return QApplication::translate("Map", "Mineshaft");
@ -156,7 +156,8 @@ QString mapopt2display(int opt)
case D_GEODE: return QApplication::translate("Map", "Geode");
case D_OUTPOST: return QApplication::translate("Map", "Pillager Outpost");
case D_ANCIENTCITY: return QApplication::translate("Map", "Ancient City");
case D_TRAILS: return QApplication::translate("Map", "Trail Ruins");
case D_TRAILRUINS: return QApplication::translate("Map", "Trail Ruins");
case D_CHAMBERS: return QApplication::translate("Map", "Trial Chambers");
case D_PORTAL: return QApplication::translate("Map", "Ruined Portal");
case D_PORTALN: return QApplication::translate("Map", "Ruined Portal (Nether)");
case D_SPAWN: return QApplication::translate("Map", "Spawn");
@ -182,7 +183,7 @@ const char *mapopt2str(int opt) // to resource string
case D_VILLAGE: return "village";
case D_MANSION: return "mansion";
case D_MONUMENT: return "monument";
case D_RUINS: return "ruins";
case D_OCEANRUIN: return "ruins";
case D_SHIPWRECK: return "shipwreck";
case D_TREASURE: return "treasure";
case D_MINESHAFT: return "mineshaft";
@ -190,7 +191,8 @@ const char *mapopt2str(int opt) // to resource string
case D_GEODE: return "geode";
case D_OUTPOST: return "outpost";
case D_ANCIENTCITY: return "ancient_city";
case D_TRAILS: return "trails";
case D_TRAILRUINS: return "trails";
case D_CHAMBERS: return "chambers";
case D_PORTAL: return "portal";
case D_PORTALN: return "portaln";
case D_SPAWN: return "spawn";
@ -214,7 +216,7 @@ int str2mapopt(const char *s) // from resource string
if (!strcmp(s, "village")) return D_VILLAGE;
if (!strcmp(s, "mansion")) return D_MANSION;
if (!strcmp(s, "monument")) return D_MONUMENT;
if (!strcmp(s, "ruins")) return D_RUINS;
if (!strcmp(s, "ruins")) return D_OCEANRUIN;
if (!strcmp(s, "shipwreck")) return D_SHIPWRECK;
if (!strcmp(s, "treasure")) return D_TREASURE;
if (!strcmp(s, "mineshaft")) return D_MINESHAFT;
@ -222,7 +224,8 @@ int str2mapopt(const char *s) // from resource string
if (!strcmp(s, "geode")) return D_GEODE;
if (!strcmp(s, "outpost")) return D_OUTPOST;
if (!strcmp(s, "ancient_city")) return D_ANCIENTCITY;
if (!strcmp(s, "trails")) return D_TRAILS;
if (!strcmp(s, "trails")) return D_TRAILRUINS;
if (!strcmp(s, "chambers")) return D_CHAMBERS;
if (!strcmp(s, "portal")) return D_PORTAL;
if (!strcmp(s, "portaln")) return D_PORTALN;
if (!strcmp(s, "spawn")) return D_SPAWN;
@ -245,7 +248,7 @@ int mapopt2stype(int opt)
case D_VILLAGE: return Village;
case D_MANSION: return Mansion;
case D_MONUMENT: return Monument;
case D_RUINS: return Ocean_Ruin;
case D_OCEANRUIN: return Ocean_Ruin;
case D_SHIPWRECK: return Shipwreck;
case D_TREASURE: return Treasure;
case D_MINESHAFT: return Mineshaft;
@ -253,7 +256,8 @@ int mapopt2stype(int opt)
case D_GEODE: return Geode;
case D_OUTPOST: return Outpost;
case D_ANCIENTCITY: return Ancient_City;
case D_TRAILS: return Trail_Ruin;
case D_TRAILRUINS: return Trail_Ruins;
case D_CHAMBERS: return Trial_Chambers;
case D_PORTAL: return Ruined_Portal;
case D_PORTALN: return Ruined_Portal_N;
case D_FORTESS: return Fortress;

View File

@ -109,7 +109,7 @@ enum {
D_VILLAGE,
D_MANSION,
D_MONUMENT,
D_RUINS,
D_OCEANRUIN,
D_SHIPWRECK,
D_TREASURE,
D_MINESHAFT,
@ -117,7 +117,8 @@ enum {
D_GEODE,
D_OUTPOST,
D_ANCIENTCITY,
D_TRAILS,
D_TRAILRUINS,
D_CHAMBERS,
D_PORTAL,
D_PORTALN,
D_FORTESS,

View File

@ -155,7 +155,7 @@ FormSearchControl::FormSearchControl(MainWindow *parent)
FormSearchControl::~FormSearchControl()
{
stimer.stop();
sthread.stop(); // tell search to stop at next convenience
sthread.stopSearch();
delete ui;
}
@ -227,9 +227,7 @@ bool FormSearchControl::setSearchConfig(SearchConfig s, bool quiet)
void FormSearchControl::stopSearch()
{
sthread.stop();
//sthread.quit(); // tell the event loop to exit
//sthread.wait(); // wait for search to finish
sthread.stopSearch();
onBufferTimeout();
}
@ -394,7 +392,7 @@ void FormSearchControl::on_buttonStart_clicked()
searchLockUi(true);
nextupdate = 0;
updt = 20;
sthread.start();
sthread.startSearch();
elapsed.start();
stimer.start(250);
}
@ -635,12 +633,12 @@ int FormSearchControl::searchResultsAdd(std::vector<uint64_t> seeds, bool counto
if (n >= config.maxMatching)
{
sthread.stop();
sthread.stopSearch();
discarded = true;
}
if (n + (ssize_t)seeds.size() > config.maxMatching)
{
sthread.stop();
sthread.stopSearch();
discarded = true;
seeds.resize(config.maxMatching - n);
}
@ -671,7 +669,7 @@ int FormSearchControl::searchResultsAdd(std::vector<uint64_t> seeds, bool counto
int addcnt = n - nold;
if (ui->checkStop->isChecked() && addcnt)
sthread.stop();
sthread.stopSearch();
if (!countonly && discarded)
{

View File

@ -149,7 +149,7 @@ void Headless::run()
session.writeHeader(resultstream);
resultstream.flush();
sthread.start();
sthread.startSearch();
elapsed.start();
if (resultfile.isOpen())

View File

@ -73,7 +73,7 @@ bool getLayerOptionInfo(LayerOptInfo *info, int mode, int disp, WorldInfo wi)
if (disp == 0)
{
txt += QString("%1.. x%2").arg(QChar(0x03A3)).arg(amptot, 0, 'f', 6);
txt += QString("%1.. (all) x%2").arg(QChar(0x03A3)).arg(amptot, 0, 'f', 6);
tip += QApplication::translate("LayerDialog", "All octaves");
}
else

View File

@ -174,7 +174,7 @@ MainWindow::MainWindow(QString sessionpath, QString resultspath, QWidget *parent
addMapAction(D_MONUMENT);
addMapAction(D_IGLOO);
addMapAction(D_MANSION);
addMapAction(D_RUINS);
addMapAction(D_OCEANRUIN);
addMapAction(D_SHIPWRECK);
addMapAction(D_TREASURE);
addMapAction(D_WELL);
@ -182,7 +182,8 @@ MainWindow::MainWindow(QString sessionpath, QString resultspath, QWidget *parent
addMapAction(D_OUTPOST);
addMapAction(D_PORTAL);
addMapAction(D_ANCIENTCITY);
addMapAction(D_TRAILS);
addMapAction(D_TRAILRUINS);
addMapAction(D_CHAMBERS);
ui->toolBar->addSeparator();
addMapAction(D_FORTESS);
addMapAction(D_BASTION);

View File

@ -381,6 +381,7 @@ void MapView::showContextMenu(const QPoint &pos)
std::vector<_cpy_dat> cpy_dat;
VarPos vp = getActivePos();
const QPixmap *icon = 0;
if (vp.type != -1)
{ // structure has a known size / location
@ -389,7 +390,7 @@ void MapView::showContextMenu(const QPoint &pos)
{
const Piece& pc = vp.pieces[0];
midx = (pc.bb0.x + pc.bb1.x) >> 1;
midy = (pc.bb0.y);
midy = (pc.bb0.y) + 1;
midz = (pc.bb0.z + pc.bb1.z) >> 1;
}
else
@ -405,6 +406,7 @@ void MapView::showContextMenu(const QPoint &pos)
}
}
cpy_dat.push_back({ tr("Copy tp:"), QString::asprintf("/tp @p %d %d %d", midx, midy, midz) });
icon = &getMapIcon(world ? world->selopt : -1, &vp);
}
cpy_dat.push_back({ tr("Copy tp:"), QString::asprintf("/tp @p %d ~ %d", vp.p.x, vp.p.z) });
cpy_dat.push_back({ tr("Copy coords:"), QString::asprintf("%d %d", vp.p.x, vp.p.z) });
@ -434,7 +436,10 @@ void MapView::showContextMenu(const QPoint &pos)
while (txtWidth(menu->fontMetrics(), txt + " ") < wmax)
txt += " ";
txt += it.cpy;
menu->addAction(txt, [=](){ this->copyText(it.cpy); });
if (icon)
menu->addAction(*icon, txt, [=](){ this->copyText(it.cpy); });
else
menu->addAction(txt, [=](){ this->copyText(it.cpy); });
}
//menu->addAction(tr("Animation"), this, &MapView::runAni);

View File

@ -249,7 +249,15 @@ QString ConditionTree::set(const std::vector<Condition>& cv, int mc)
}
SearchThreadEnv::SearchThreadEnv()
: condtree(),mc(),large(),seed(),surfdim(DIM_UNDEF),octaves(),l_states()
: condtree()
, mc()
, large()
, seed()
, surfdim(DIM_UNDEF)
, octaves()
, searchpass(PASS_FAST_48)
, stop()
, l_states()
{
memset(&g, 0, sizeof(g));
memset(&sn, 0, sizeof(sn));
@ -323,7 +331,7 @@ void SearchThreadEnv::init4Noise(int nptype, int octaves)
{
if (octaves <= 0)
octaves = INT_MAX;
if (g.bn.nptype == nptype && this->octaves >= octaves)
if (g.bn.nptype == nptype && this->octaves == octaves)
return; // already initialized for parameter
if (seed == g.seed && g.bn.nptype == -1)
return; // fully initialized biome noise
@ -340,12 +348,22 @@ void SearchThreadEnv::prepareSurfaceNoise(int dim)
}
}
// The position buffers can exceed the stacksize on some platforms,
// and dynamic heap allocation is too slow. So instead, we assign a
// static memory region on the heap.
static Pos* getPosBuf(uint32_t node)
{
#if DEBUG
if (node >= 100) return NULL; // fatal
#endif
thread_local std::vector<Pos> buf(100 * MAX_INSTANCES);
return &buf[node * MAX_INSTANCES];
}
static
int testTreeAt(
int _testTreeAt(
Pos at, // relative origin
SearchThreadEnv * env, // thread-local environment
int pass, // search pass
std::atomic_bool * abort, // abort signal
Pos * path, // output center position(s)
int node
)
@ -356,8 +374,7 @@ int testTreeAt(
int st, br;
int rx1, rz1, rx2, rz2;
Pos pos;
std::vector<Pos> inst(MAX_INSTANCES);
Pos *inst = getPosBuf(node);
switch (c.type)
{
@ -424,8 +441,8 @@ int testTreeAt(
int sta = COND_OK;
for (int b : branches)
{
int stb = testTreeAt(pos, env, pass, abort, path, b);
if (*abort)
int stb = _testTreeAt(pos, env, path, b);
if (*env->stop)
return COND_FAILED;
if (stb < sta)
sta = stb;
@ -471,8 +488,8 @@ int testTreeAt(
st = COND_OK;
for (int b : branches)
{
int sta = testTreeAt(pos, env, pass, abort, path, b);
if (*abort)
int sta = _testTreeAt(pos, env, path, b);
if (*env->stop)
return COND_FAILED;
if (sta < st)
st = sta;
@ -497,8 +514,8 @@ int testTreeAt(
st = COND_FAILED;
for (int b : branches)
{
int sta = testTreeAt(at, env, pass, abort, path, b);
if (*abort)
int sta = _testTreeAt(at, env, path, b);
if (*env->stop)
return COND_FAILED;
if (sta > st)
st = sta;
@ -528,8 +545,8 @@ int testTreeAt(
st = COND_OK;
for (int b : branches)
{
int sta = testTreeAt(at, env, pass, abort, path, b);
if (*abort)
int sta = _testTreeAt(at, env, path, b);
if (*env->stop)
return COND_FAILED;
if (sta == COND_OK) { st = COND_FAILED; break; }
else if (sta == COND_FAILED) { st = COND_OK; break; }
@ -544,8 +561,8 @@ int testTreeAt(
Pos *buf = path ? path : &inst[0];
for (int b : branches)
{
int sta = testTreeAt(at, env, pass, abort, buf, b);
if (*abort)
int sta = _testTreeAt(at, env, buf, b);
if (*env->stop)
return COND_FAILED;
if (sta < st) {
st = sta;
@ -555,8 +572,8 @@ int testTreeAt(
}
if (st <= COND_MAYBE_POS_INVAL)
return st;
int sta = runCheckScript(L, at, env, pass, buf, &c);
if (*abort)
int sta = runCheckScript(L, at, env, env->searchpass, buf, &c);
if (*env->stop)
return COND_FAILED;
if (sta < st)
st = sta;
@ -568,7 +585,7 @@ int testTreeAt(
if (branches.empty())
{ // this is a leaf node => check only for presence of instances
int icnt = c.count;
st = testCondAt(at, env, pass, abort, &inst[0], &icnt, &c);
st = testCondAt(at, env, &inst[0], &icnt, &c);
if (path && st >= COND_MAYBE_POS_VALID)
{
if (icnt == 1)
@ -593,7 +610,7 @@ int testTreeAt(
}
else
{
st = testCondAt(at, env, pass, abort, &inst[0], NULL, &c);
st = testCondAt(at, env, &inst[0], NULL, &c);
if (st == COND_FAILED || st == COND_MAYBE_POS_INVAL)
return st;
pos = inst[0]; // center point of instances
@ -602,8 +619,8 @@ int testTreeAt(
{
if (st == COND_FAILED)
break;
int sta = testTreeAt(pos, env, pass, abort, path, b);
if (*abort)
int sta = _testTreeAt(pos, env, path, b);
if (*env->stop)
return COND_FAILED;
if (sta < st)
st = sta;
@ -616,7 +633,7 @@ int testTreeAt(
{ // check each instance individually, splitting the instances into
// independent subbranches that are combined via OR
int icnt = MAX_INSTANCES;
st = testCondAt(at, env, pass, abort, &inst[0], &icnt, &c);
st = testCondAt(at, env, &inst[0], &icnt, &c);
if (st == COND_FAILED || st == COND_MAYBE_POS_INVAL)
return st;
int sta = COND_FAILED;
@ -627,8 +644,8 @@ int testTreeAt(
pos = inst[i];
for (int b : branches) // AND dependent conditions
{
int stc = testTreeAt(pos, env, pass, abort, path, b);
if (*abort)
int stc = _testTreeAt(pos, env, path, b);
if (*env->stop)
return COND_FAILED;
// worst branch dictates status for instance
if (stc < stb)
@ -659,17 +676,18 @@ int testTreeAt(
Pos at, // relative origin
SearchThreadEnv * env, // thread-local environment
int pass, // search pass
std::atomic_bool * abort, // abort signal
Pos * path // ok trigger positions
)
{
if (pass != PASS_FAST_48)
{ // do a fast check before continuing with slower checks
int st = testTreeAt(at, env, PASS_FAST_48, abort, NULL, 0);
env->searchpass = PASS_FAST_48;
int st = _testTreeAt(at, env, NULL, 0);
if (st == COND_FAILED)
return st;
}
return testTreeAt(at, env, pass, abort, path, 0);
env->searchpass = pass;
return _testTreeAt(at, env, path, 0);
}
@ -1007,6 +1025,48 @@ static int f_biome_sampler(Generator *g, int scale, int x, int y, int z, void *d
return 0;
}
static int f_noise_sampler(Generator *g, int scale, int x, int y, int z, void *data)
{
(void) y;
sample_boime_t *info = (sample_boime_t*) data;
if (info->stop && *info->stop)
return -2;
if (info->rmaxsq)
{
int dx = (x * scale) - info->at.x;
int dz = (z * scale) - info->at.z;
int64_t rsq = dx*(int64_t)dx + dz*(int64_t)dz;
if (rsq >= info->rmaxsq)
return -1;
}
const Condition *cond = info->cond;
double v = sampleDoublePerlin(&g->bn.climate[cond->para], x, 0, z);
double vmin = cond->minmax & Condition::E_TEST_LOWER ? cond->vmin : -INFINITY;
double vmax = cond->minmax & Condition::E_TEST_UPPER ? cond->vmax : +INFINITY;
v *= 10000;
bool ok = (v > vmin && v < vmax);
if (cond->flags & Condition::FLG_INVERT)
ok = !ok;
if (ok)
{
x *= scale;
z *= scale;
if (info->imax && info->n < MAX_INSTANCES)
info->cent[info->n] = Pos{x, z};
info->xsum += x;
info->zsum += z;
info->n++;
return 1;
}
return 0;
}
/* Tests if a condition is satisfied with 'at' as origin for a search pass.
* If sufficiently satisfied (check return value) then:
* when 'imax' is NULL, the center position is written to 'cent[0]'
@ -1018,8 +1078,6 @@ int
testCondAt(
Pos at, // relative origin
SearchThreadEnv * env, // thread-local environment
int pass, // search pass
std::atomic_bool * abort, // abort signal
Pos * cent, // output center position(s)
int * imax, // max instances (NULL for avg)
const Condition * cond // condition to check
@ -1035,7 +1093,7 @@ testCondAt(
int i, n, icnt;
int64_t s, r, rmin, rmax;
const uint64_t *seeds;
std::vector<Pos> p(MAX_INSTANCES);
Pos *p = getPosBuf(0);
const FilterInfo& finfo = g_filterinfo.list[cond->type];
@ -1251,7 +1309,7 @@ L_qm_any:
icnt = 0;
// Note "<="
for (rz = rz1; rz <= rz2 && !*abort; rz++)
for (rz = rz1; rz <= rz2 && !*env->stop; rz++)
{
for (rx = rx1; rx <= rx2; rx++)
{
@ -1271,9 +1329,10 @@ L_qm_any:
{
continue;
}
if (pass == PASS_FULL_64 || (pass == PASS_FULL_48 && !finfo.dep64))
if ((env->searchpass == PASS_FULL_64) ||
(env->searchpass == PASS_FULL_48 && !finfo.dep64))
{
if (*abort) return COND_FAILED;
if (*env->stop) return COND_FAILED;
if (st == Village && cond->varflags)
{ // we can test for abandoned villages before the
@ -1344,9 +1403,9 @@ L_qm_any:
return COND_OK;
else
{
if (pass == PASS_FULL_64)
if (env->searchpass == PASS_FULL_64)
return COND_FAILED;
if (pass == PASS_FULL_48 && !finfo.dep64)
if (env->searchpass == PASS_FULL_48 && !finfo.dep64)
return COND_FAILED;
return COND_MAYBE_POS_VALID;
}
@ -1363,9 +1422,9 @@ L_qm_any:
cent->z = zt / icnt;
}
if (pass == PASS_FULL_64)
if (env->searchpass == PASS_FULL_64)
return COND_OK;
if (pass == PASS_FULL_48 && !finfo.dep64)
if (env->searchpass == PASS_FULL_48 && !finfo.dep64)
return COND_OK;
// some non-exhaustive structure clusters do not
// have known center positions with 48-bit seeds
@ -1460,10 +1519,10 @@ L_qm_any:
case F_SPAWN:
cent->x = cent->z = 0;
if (pass != PASS_FULL_64)
if (env->searchpass != PASS_FULL_64)
return COND_MAYBE_POS_INVAL;
if (*abort) return COND_FAILED;
if (*env->stop) return COND_FAILED;
env->init4Dim(DIM_OVERWORLD);
pc = getSpawn(&env->g);
if (rmax)
@ -1584,7 +1643,7 @@ L_qm_any:
return cond->count == 0 ? COND_OK : COND_FAILED;
// pre-biome-checks complete, the area appears to line up with possible generation positions
if (pass != PASS_FULL_64)
if (env->searchpass != PASS_FULL_64)
{
return COND_MAYBE_POS_INVAL;
}
@ -1602,7 +1661,7 @@ L_qm_any:
env->init4Dim(DIM_OVERWORLD);
while (nextStronghold(&sh, &env->g) > 0)
{
if (*abort)
if (*env->stop)
break;
bool inside;
if (rmax)
@ -1722,13 +1781,16 @@ L_qm_any:
case F_BIOME_SAMPLE:
case F_NOISE_SAMPLE:
if (pass != PASS_FULL_64)
if (env->searchpass != PASS_FULL_64)
return COND_MAYBE_POS_INVAL;
if (cond->confidence <= 0 || cond->confidence >= 1)
return COND_FAILED;
if (cond->converage <= 0 || cond->converage > 1)
return COND_FAILED;
if (cond->type == F_NOISE_SAMPLE && env->mc <= MC_1_17)
return COND_FAILED;
s = 2;
rx1 = x1 >> s;
@ -1739,7 +1801,6 @@ L_qm_any:
int w = rx2 - rx1 + 1;
int h = rz2 - rz1 + 1;
Range r = {1<<s, rx1, rz1, w, h, s == 0 ? cond->y : cond->y >> 2, 1};
env->init4Dim(DIM_OVERWORLD);
sample_boime_t sample;
sample.cond = cond;
sample.at = at;
@ -1749,14 +1810,24 @@ L_qm_any:
sample.zsum = 0;
sample.imax = imax;
sample.cent = cent;
sample.stop = abort;
sample.stop = env->stop;
uint64_t rng;
setSeed(&rng, env->seed);
int ok = monteCarloBiomes(
&env->g, r, &rng, cond->converage, cond->confidence,
&f_biome_sampler, &sample);
int (*f)(Generator *, int, int, int, int, void *);
if (cond->type == F_NOISE_SAMPLE)
{
env->init4Noise(cond->para, cond->octave);
f = f_noise_sampler;
}
else
{
env->init4Dim(DIM_OVERWORLD);
f = f_biome_sampler;
}
int ok = monteCarloBiomes(&env->g, r, &rng, cond->converage, cond->confidence, f, &sample);
if (imax && cond->count == 1)
{
*imax = sample.n;
@ -1780,7 +1851,7 @@ L_qm_any:
case F_BIOME_4_RIVER:
case F_BIOME_256_OTEMP:
if (env->mc >= MC_1_18)
if (env->mc > MC_1_17)
return COND_FAILED;
s = cond->type == F_BIOME_4_RIVER ? 2 : 8;
@ -1791,15 +1862,15 @@ L_qm_any:
cent->x = (x1 + x2) >> 1;
cent->z = (z1 + z2) >> 1;
if (imax) *imax = 1;
if (pass == PASS_FAST_48)
if (env->searchpass == PASS_FAST_48)
return COND_MAYBE_POS_VALID;
if (pass == PASS_FULL_48)
if (env->searchpass == PASS_FULL_48)
{
if (env->mc < MC_1_13 || cond->type != F_BIOME_256_OTEMP)
return COND_MAYBE_POS_VALID;
}
valid = COND_FAILED;
if (rx2 >= rx1 || rz2 >= rz1 || !*abort)
if (rx2 >= rx1 || rz2 >= rz1 || !*env->stop)
{
int w = rx2-rx1+1;
int h = rz2-rz1+1;
@ -1819,7 +1890,7 @@ L_qm_any:
case F_TEMPS:
if (env->mc >= MC_1_18)
if (env->mc > MC_1_17)
return COND_FAILED;
rx1 = x1 >> 10;
rz1 = z1 >> 10;
@ -1828,7 +1899,7 @@ L_qm_any:
cent->x = (x1 + x2) >> 1;
cent->z = (z1 + z2) >> 1;
if (imax) *imax = 1;
if (pass != PASS_FULL_64)
if (env->searchpass != PASS_FULL_64)
return COND_MAYBE_POS_VALID;
env->init4Dim(DIM_OVERWORLD);
if (checkForTemps(&env->g.ls, env->seed, rx1, rz1, rx2-rx1+1, rz2-rz1+1, cond->temps))
@ -1857,11 +1928,11 @@ L_qm_any:
cent->x = (x1 + x2) >> 1;
cent->z = (z1 + z2) >> 1;
if (imax) *imax = 1;
if (pass == PASS_FAST_48)
if (env->searchpass == PASS_FAST_48)
return COND_MAYBE_POS_VALID;
// the Nether and End require only the 48-bit seed
// (except voronoi uses the full 64-bits)
if (pass != PASS_FULL_64 && (finfo.dep64 || s == 0))
if (env->searchpass != PASS_FULL_64 && (finfo.dep64 || s == 0))
return COND_MAYBE_POS_VALID;
else
{
@ -1870,14 +1941,14 @@ L_qm_any:
int y = (s == 0 ? cond->y : cond->y >> 2);
Range r = {1<<s, rx1, rz1, w, h, y, 1};
valid = checkForBiomes(&env->g, NULL, r, finfo.dim, env->seed,
&cond->bf, (volatile char*)abort) > 0;
&cond->bf, (volatile char*)env->stop) > 0;
}
return valid ? COND_OK : COND_FAILED;
case F_BIOME_CENTER:
case F_BIOME_CENTER_256:
if (pass == PASS_FULL_64)
if (env->searchpass == PASS_FULL_64)
{
s = cond->type == F_BIOME_CENTER ? 2 : 8;
rx1 = x1 >> s;
@ -1893,7 +1964,7 @@ L_qm_any:
{ // exclusion
icnt = getBiomeCenters(
cent, NULL, 1, &env->g, r, cond->biomeId, cond->biomeSize, cond->tol,
(volatile char*)abort
(volatile char*)env->stop
);
if (icnt == 0)
{
@ -1907,7 +1978,7 @@ L_qm_any:
{ // just check there are at least *inst (== cond->count) instances
*imax = icnt = getBiomeCenters(
cent, NULL, cond->count, &env->g, r, cond->biomeId, cond->biomeSize, cond->tol,
(volatile char*)abort
(volatile char*)env->stop
);
if (cond->skipref && icnt > 0)
{ // remove origin instance
@ -1928,7 +1999,7 @@ L_qm_any:
{ // we need the average position of all instances
icnt = getBiomeCenters(
&p[0], NULL, MAX_INSTANCES, &env->g, r, cond->biomeId, cond->biomeSize, cond->tol,
(volatile char*)abort
(volatile char*)env->stop
);
xt = zt = 0;
int j = 0;
@ -1952,13 +2023,13 @@ L_qm_any:
return COND_MAYBE_POS_INVAL;
case F_CLIMATE_MINMAX:
if (env->mc < MC_1_18 || cond->para >= NP_MAX)
if (env->mc <= MC_1_17 || cond->para >= NP_MAX)
return COND_FAILED;
rx1 = x1 >> 2;
rz1 = z1 >> 2;
rx2 = x2 >> 2;
rz2 = z2 >> 2;
if (pass != PASS_FULL_64)
if (env->searchpass != PASS_FULL_64)
return COND_MAYBE_POS_INVAL;
{
track_minmax_t info = {at, at, +INFINITY, -INFINITY};
@ -2000,7 +2071,7 @@ L_qm_any:
}
case F_CLIMATE_NOISE:
if (env->mc < MC_1_18)
if (env->mc <= MC_1_17)
return COND_FAILED;
rx1 = x1 >> 2;
rz1 = z1 >> 2;
@ -2009,8 +2080,9 @@ L_qm_any:
cent->x = (x1 + x2) >> 1;
cent->z = (z1 + z2) >> 1;
if (imax) *imax = 1;
if (pass != PASS_FULL_64)
if (env->searchpass != PASS_FULL_64)
return COND_MAYBE_POS_VALID;
else
{
int w = rx2 - rx1 + 1;
int h = rz2 - rz1 + 1;
@ -2069,7 +2141,7 @@ L_qm_any:
cent->x = x1;
cent->z = z1;
if (imax) *imax = 1;
if (pass != PASS_FULL_64)
if (env->searchpass != PASS_FULL_64)
return COND_MAYBE_POS_VALID;
env->init4Dim(DIM_OVERWORLD);
env->prepareSurfaceNoise(DIM_OVERWORLD);

View File

@ -90,6 +90,7 @@ enum
F_WELL,
F_TRAILS,
F_BIOME_SAMPLE,
F_NOISE_SAMPLE,
// new filters should be added here at the end to keep some downwards compatibility
FILTER_MAX,
};
@ -308,15 +309,23 @@ static const struct FilterList : private FilterInfo
};
list[F_CLIMATE_NOISE] = FilterInfo{
CAT_BIOMES, 0, LOC_REC, 0, 4, BR_NONE, MC_1_18, MC_NEWEST, 0, 0, disp++,
CAT_BIOMES, 1, LOC_REC, 0, 4, BR_NONE, MC_1_18, MC_NEWEST, 0, 0, disp++,
"overworld",
QT_TRANSLATE_NOOP("Filter", "Climate parameters"),
QT_TRANSLATE_NOOP("Filter",
"Custom limits for the required and allowed climate noise parameters that "
"the specified area should cover.")
};
list[F_NOISE_SAMPLE] = FilterInfo{
CAT_BIOMES, 1, LOC_RAD, 0, 4, BR_SPLIT, MC_1_18, MC_NEWEST, 0, 0, disp++,
"overworld",
QT_TRANSLATE_NOOP("Filter", "Climate noise samples"),
QT_TRANSLATE_NOOP("Filter",
"Samples climate noise in a given area to find if a proportion of the "
"biomes match a set of allowed biomes.")
};
list[F_CLIMATE_MINMAX] = FilterInfo{
CAT_BIOMES, 0, LOC_REC, 0, 4, BR_NONE, MC_1_18, MC_NEWEST, 0, 0, disp++,
CAT_BIOMES, 1, LOC_REC, 0, 4, BR_NONE, MC_1_18, MC_NEWEST, 0, 0, disp++,
"overworld",
QT_TRANSLATE_NOOP("Filter", "Locate climate extreme"),
QT_TRANSLATE_NOOP("Filter",
@ -358,7 +367,7 @@ static const struct FilterList : private FilterInfo
""
};
list[F_HEIGHT] = FilterInfo{
CAT_OTHER, 0, LOC_POS, 0, 4, BR_NONE, MC_1_1, MC_NEWEST, 0, 0, disp++,
CAT_OTHER, 1, LOC_POS, 0, 4, BR_NONE, MC_1_1, MC_NEWEST, 0, 0, disp++,
"height",
QT_TRANSLATE_NOOP("Filter", "Surface height"),
QT_TRANSLATE_NOOP("Filter",
@ -446,7 +455,7 @@ static const struct FilterList : private FilterInfo
list[F_RUINS] = FilterInfo{
CAT_STRUCT, 1, LOC_RAD, Ocean_Ruin, 1, BR_CLUST, MC_1_13, MC_NEWEST, 0, 0, disp++,
"ruins",
QT_TRANSLATE_NOOP("Filter", "Ocean ruins"),
QT_TRANSLATE_NOOP("Filter", "Ocean ruin"),
""
};
@ -489,7 +498,7 @@ static const struct FilterList : private FilterInfo
};
list[F_TRAILS] = FilterInfo{
CAT_STRUCT, 1, LOC_RAD, Trail_Ruin, 1, BR_CLUST, MC_1_20, MC_NEWEST, 0, 0, disp++,
CAT_STRUCT, 1, LOC_RAD, Trail_Ruins, 1, BR_CLUST, MC_1_20, MC_NEWEST, 0, 0, disp++,
"trails",
QT_TRANSLATE_NOOP("Filter", "Trail ruins"),
""
@ -637,6 +646,24 @@ static_assert(
"Layout of Condition has changed!"
);
#define MAX_INSTANCES 4096 // should be at least 128
enum
{
COND_FAILED = 0, // seed does not meet the condition
COND_MAYBE_POS_INVAL = 1, // search pass insufficient for result
COND_MAYBE_POS_VALID = 2, // search pass insufficient, but known center
COND_OK = 3, // seed satisfies the condition
};
enum
{
PASS_FAST_48, // only do fast checks that do not require biome gen
PASS_FULL_48, // include possible biome checks for 48-bit seeds
PASS_FULL_64, // run full test on a 64-bit seed
};
struct ConditionTree
{
std::vector<Condition> condvec;
@ -658,6 +685,9 @@ struct SearchThreadEnv
int surfdim;
int octaves;
int searchpass;
std::atomic_bool *stop;
std::map<uint64_t, lua_State*> l_states;
SearchThreadEnv();
@ -671,24 +701,6 @@ struct SearchThreadEnv
void prepareSurfaceNoise(int dim);
};
#define MAX_INSTANCES 4096 // should be at least 128
enum
{
COND_FAILED = 0, // seed does not meet the condition
COND_MAYBE_POS_INVAL = 1, // search pass insufficient for result
COND_MAYBE_POS_VALID = 2, // search pass insufficient, but known center
COND_OK = 3, // seed satisfies the condition
};
enum
{
PASS_FAST_48, // only do fast checks that do not require biome gen
PASS_FULL_48, // include possible biome checks for 48-bit seeds
PASS_FULL_64, // run full test on a 64-bit seed
};
/* Checks if a seed satisfies the conditions tree.
* Returns the lowest condition fulfillment status.
*/
@ -696,15 +708,12 @@ int testTreeAt(
Pos at, // relative origin
SearchThreadEnv * env, // thread-local environment
int pass, // search pass
std::atomic_bool * abort, // abort signal
Pos * path = 0 // ok trigger positions
Pos * path // ok trigger positions
);
int testCondAt(
Pos at, // relative origin
SearchThreadEnv * env, // thread-local environment
int pass, // search pass
std::atomic_bool * abort, // abort signal
Pos * cent, // output center position(s)
int * imax, // max instances (NULL for avg)
const Condition * cond // condition to check

View File

@ -129,7 +129,7 @@ bool Session::load(QWidget *widget, QTextStream& stream, bool quiet)
SearchMaster::SearchMaster(QWidget *parent)
: QObject(parent)
, mutex()
, abort()
, stop()
, proghist()
, progtimer()
, itemtimer()
@ -151,11 +151,12 @@ SearchMaster::SearchMaster(QWidget *parent)
, smax()
, isdone()
{
env.stop = &stop;
}
SearchMaster::~SearchMaster()
{
stop();
stopSearch();
}
bool SearchMaster::set(QWidget *widget, const Session& s)
@ -313,7 +314,7 @@ bool SearchMaster::set(QWidget *widget, const Session& s)
this->smin = s.sc.smin;
this->smax = s.sc.smax;
this->isdone = false;
this->abort = false;
this->stop = false;
return true;
}
@ -393,7 +394,7 @@ static bool applyTranspose(std::vector<uint64_t>& slist,
return !slist.empty();
}
void SearchMaster::presearch()
void SearchMaster::preSearch()
{
uint64_t sstart = seed;
@ -567,13 +568,13 @@ void SearchMaster::presearch()
}
}
void SearchMaster::start()
void SearchMaster::startSearch()
{
stop();
abort = false;
presearch();
stopSearch();
stop = false;
preSearch();
if (abort)
if (stop)
{
emit searchFinish(false);
return;
@ -607,9 +608,9 @@ void SearchMaster::start()
}
}
void SearchMaster::stop()
void SearchMaster::stopSearch()
{
abort = true;
stop = true;
if (workers.empty())
return;
@ -871,10 +872,10 @@ bool SearchMaster::requestItem(SearchWorker *item)
high = 0;
low++;
for (; low <= MASK48 && !abort; low++)
for (; low <= MASK48 && !stop; low++)
{
env.setSeed(low);
if (testTreeAt(origin, &env, PASS_FAST_48, &abort)
if (testTreeAt(origin, &env, PASS_FAST_48, nullptr)
!= COND_FAILED)
{
break;
@ -909,7 +910,7 @@ void SearchMaster::onWorkerFinished()
for (SearchWorker *worker: workers)
delete worker;
workers.clear();
emit searchFinish(isdone && !abort);
emit searchFinish(isdone && !stop);
}
@ -919,7 +920,6 @@ SearchWorker::SearchWorker(SearchMaster *master)
{
this->slist = master->slist.empty() ? NULL : master->slist.data();
this->len = master->slist.size();
this->abort = &master->abort;
this->prog = master->prog;
this->idx = master->idx;
@ -927,12 +927,11 @@ SearchWorker::SearchWorker(SearchMaster *master)
this->scnt = 0;
this->seed = master->seed;
this->env = new SearchThreadEnv();
this->env.stop = &master->stop;
}
SearchWorker::~SearchWorker()
{
delete env;
}
bool SearchWorker::getNextItem()
@ -944,25 +943,21 @@ bool SearchWorker::getNextItem()
void SearchWorker::run()
{
Pos origin = {0,0};
env->init(master->mc, master->large, master->condtree);
volatile char b[1024*1024];
for (size_t i = 0; i < sizeof(b); i++)
b[i] = (char)i + b[i % 256];
env.init(master->mc, master->large, master->condtree);
switch (master->searchtype)
{
case SEARCH_LIST:
while (!*abort && getNextItem())
while (!*env.stop && getNextItem())
{ // seed = slist[..]
uint64_t ie = idx+scnt < len ? idx+scnt : len;
for (uint64_t i = idx; i < ie; i++)
{
seed = slist[i];
env->setSeed(seed);
if (testTreeAt(origin, env, PASS_FULL_64, abort) == COND_OK)
env.setSeed(seed);
if (testTreeAt(origin, &env, PASS_FULL_64, nullptr) == COND_OK)
{
if (!*abort)
if (!*env.stop)
emit result(seed);
}
}
@ -972,7 +967,7 @@ void SearchWorker::run()
break;
case SEARCH_48ONLY:
while (!*abort && getNextItem())
while (!*env.stop && getNextItem())
{
if (slist)
{
@ -980,10 +975,10 @@ void SearchWorker::run()
for (uint64_t i = idx; i < ie; i++)
{
seed = slist[i];
env->setSeed(seed);
if (testTreeAt(origin, env, PASS_FULL_48, abort) != COND_FAILED)
env.setSeed(seed);
if (testTreeAt(origin, &env, PASS_FULL_48, nullptr) != COND_FAILED)
{
if (!*abort)
if (!*env.stop)
emit result(seed);
}
}
@ -993,10 +988,10 @@ void SearchWorker::run()
seed = sstart;
for (int i = 0; i < scnt; i++)
{
env->setSeed(seed);
if (testTreeAt(origin, env, PASS_FULL_48, abort) != COND_FAILED)
env.setSeed(seed);
if (testTreeAt(origin, &env, PASS_FULL_48, nullptr) != COND_FAILED)
{
if (!*abort)
if (!*env.stop)
emit result(seed);
}
@ -1011,7 +1006,7 @@ void SearchWorker::run()
break;
case SEARCH_INC:
while (!*abort && getNextItem())
while (!*env.stop && getNextItem())
{
if (slist)
{ // seed = (high << 48) | slist[..]
@ -1022,10 +1017,10 @@ void SearchWorker::run()
{
seed = (high << 48) | slist[lowidx];
env->setSeed(seed);
if (testTreeAt(origin, env, PASS_FULL_64, abort) == COND_OK)
env.setSeed(seed);
if (testTreeAt(origin, &env, PASS_FULL_64, nullptr) == COND_OK)
{
if (!*abort)
if (!*env.stop)
emit result(seed);
}
@ -1044,10 +1039,10 @@ void SearchWorker::run()
seed = sstart;
for (int i = 0; i < scnt; i++)
{
env->setSeed(seed);
if (testTreeAt(origin, env, PASS_FULL_64, abort) == COND_OK)
env.setSeed(seed);
if (testTreeAt(origin, &env, PASS_FULL_64, nullptr) == COND_OK)
{
if (!*abort)
if (!*env.stop)
emit result(seed);
}
@ -1062,7 +1057,7 @@ void SearchWorker::run()
break;
case SEARCH_BLOCKS:
while (!*abort && getNextItem())
while (!*env.stop && getNextItem())
{ // seed = ([..] << 48) | low
if (slist && idx >= len)
{
@ -1076,8 +1071,8 @@ void SearchWorker::run()
else
low = sstart & MASK48;
env->setSeed(low);
if (testTreeAt(origin, env, PASS_FULL_48, abort) == COND_FAILED)
env.setSeed(low);
if (testTreeAt(origin, &env, PASS_FULL_48, nullptr) == COND_FAILED)
{
continue;
}
@ -1086,10 +1081,10 @@ void SearchWorker::run()
{
seed = (high << 48) | low;
env->setSeed(seed);
if (testTreeAt(origin, env, PASS_FULL_64, abort) == COND_OK)
env.setSeed(seed);
if (testTreeAt(origin, &env, PASS_FULL_64, nullptr) == COND_OK)
{
if (!*abort)
if (!*env.stop)
emit result(seed);
}

View File

@ -37,10 +37,10 @@ public:
bool set(QWidget *widget, const Session& s);
void presearch();
void preSearch();
void start();
void stop();
void startSearch();
void stopSearch();
// Get search progress:
// status : progress status summary
@ -69,7 +69,7 @@ public:
std::vector<SearchWorker*> workers;
QMutex mutex;
std::atomic_bool abort;
std::atomic_bool stop;
std::deque<TProg> proghist;
QElapsedTimer progtimer;
@ -114,7 +114,6 @@ public:
const uint64_t * slist; // candidate list
uint64_t len; // number of candidates
std::atomic_bool * abort;
/// current work item
uint64_t prog; // search space progress
@ -126,7 +125,7 @@ public:
// (or the last entry in the seed list)
private:
SearchThreadEnv * env;
SearchThreadEnv env;
};

View File

@ -61,6 +61,12 @@ QTreeWidgetItem *setConditionTreeItems(ConditionTree& ctree, int node, int64_t s
return item;
}
AnalysisLocations::AnalysisLocations(QObject *parent)
: QThread(parent), wi(),stop(),sidx(),pidx()
{
env.stop = &stop;
}
QString AnalysisLocations::set(WorldInfo wi, const std::vector<Condition>& conds)
{
this->wi = wi;
@ -85,7 +91,7 @@ void AnalysisLocations::run()
Pos at = pos[pidx.load()];
Pos cpos[MAX_INSTANCES] = {};
if (testTreeAt(at, &env, PASS_FULL_64, &stop, cpos)
if (testTreeAt(at, &env, PASS_FULL_64, cpos)
!= COND_OK)
{
continue;

View File

@ -16,8 +16,7 @@ class AnalysisLocations : public QThread
{
Q_OBJECT
public:
explicit AnalysisLocations(QObject *parent = nullptr)
: QThread(parent), wi(),stop(),sidx(),pidx() {}
explicit AnalysisLocations(QObject *parent = nullptr);
QString set(WorldInfo wi, const std::vector<Condition>& conds);

View File

@ -80,7 +80,6 @@ struct RandGen
// get a random 64-bit integer
uint64_t getRnd64();
enum { S_TEXT, S_NUMERIC, S_RANDOM };
int str2seed(const QString &str, uint64_t *out);

View File

@ -201,11 +201,15 @@ void getStructs(std::vector<VarPos> *out, const StructureConfig sconf,
{
SurfaceNoise sn;
initSurfaceNoise(&sn, DIM_END, wi.seed);
id = isViableEndCityTerrain(&g, &sn, p.x, p.z);
if (!id)
int y = isViableEndCityTerrain(&g, &sn, p.x, p.z);
if (!y)
continue;
int n = getEndCityPieces(pieces, wi.seed, p.x >> 4, p.z >> 4);
vp.pieces.assign(pieces, pieces+n);
if (n)
{
vp.pieces.assign(pieces, pieces+n);
vp.pieces[0].bb0.y = y; // height of end city pieces are relative to surface
}
}
else if (sconf.structType == Ruined_Portal || sconf.structType == Ruined_Portal_N)
{
@ -235,12 +239,12 @@ void getStructs(std::vector<VarPos> *out, const StructureConfig sconf,
static QMutex g_mutex;
static qreal cubic_hermite(qreal p[4], qreal u)
static float cubic_hermite(float p[4], float u)
{
qreal a = p[1];
qreal b = 0.5 * (-p[0] + p[2]);
qreal c = p[0] - 2.5*p[1] + 2*p[2] - 0.5*p[3];
qreal d = 0.5 * (-p[0] + 3*p[1] - 3*p[2] + p[3]);
float a = p[1];
float b = 0.5 * (-p[0] + p[2]);
float c = p[0] - 2.5*p[1] + 2*p[2] - 0.5*p[3];
float d = 0.5 * (-p[0] + 3*p[1] - 3*p[2] + p[3]);
return a + b*u + c*u*u + d*u*u*u;
}
@ -262,21 +266,28 @@ void applyHeightShading(unsigned char *rgb, Range r,
px -= bd; pz -= bd;
pw += 2*bd; ph += 2*bd;
}
std::vector<qreal> buf(pw * ph);
for (int j = 0; j < ph; j++)
std::vector<float> buf(pw * ph);
if (ps == 0)
{
for (int i = 0; i < pw; i++)
mapApproxHeight(&buf[0], 0, g, sn, px, pz, pw, ph);
}
else
{
for (int j = 0; j < ph; j++)
{
if (abort && *abort) return;
int samplex = (((px + i) << ps)) * r.scale / 4;
int samplez = (((pz + j) << ps)) * r.scale / 4;
float y = 0;
mapApproxHeight(&y, 0, g, sn, samplex, samplez, 1, 1);
buf[j*pw+i] = y;
for (int i = 0; i < pw; i++)
{
if (abort && *abort) return;
int samplex = (((px + i) << ps)) * r.scale / 4;
int samplez = (((pz + j) << ps)) * r.scale / 4;
float y = 0;
mapApproxHeight(&y, 0, g, sn, samplex, samplez, 1, 1);
buf[j*pw+i] = y;
}
}
}
// interpolate height
std::vector<qreal> height((w+2) * (h+2));
std::vector<float> height((w+2) * (h+2));
for (int j = 0; j < h+2; j++)
{
for (int i = 0; i < w+2; i++)
@ -284,12 +295,12 @@ void applyHeightShading(unsigned char *rgb, Range r,
if (abort && *abort) return;
int pi = ((x + i - 1) >> ps) - px - bd;
int pj = ((z + j - 1) >> ps) - pz - bd;
qreal di = ((x + i - 1) & st) / (qreal)(st + 1);
qreal dj = ((z + j - 1) & st) / (qreal)(st + 1);
qreal v = 0;
float di = ((x + i - 1) & st) / (float)(st + 1);
float dj = ((z + j - 1) & st) / (float)(st + 1);
float v = 0;
if (bicubic)
{
qreal p[] = {
float p[] = {
cubic_hermite(&buf.at((pj+0)*pw + pi), di),
cubic_hermite(&buf.at((pj+1)*pw + pi), di),
cubic_hermite(&buf.at((pj+2)*pw + pi), di),
@ -299,10 +310,10 @@ void applyHeightShading(unsigned char *rgb, Range r,
}
else // bilinear
{
qreal v00 = buf.at((pj+0)*pw + pi+0);
qreal v01 = buf.at((pj+0)*pw + pi+1);
qreal v10 = buf.at((pj+1)*pw + pi+0);
qreal v11 = buf.at((pj+1)*pw + pi+1);
float v00 = buf.at((pj+0)*pw + pi+0);
float v01 = buf.at((pj+0)*pw + pi+1);
float v10 = buf.at((pj+1)*pw + pi+0);
float v11 = buf.at((pj+1)*pw + pi+1);
v = lerp2(di, dj, v00, v01, v10, v11);
}
height[j*(w+2)+i] = v;
@ -310,24 +321,24 @@ void applyHeightShading(unsigned char *rgb, Range r,
}
if (abort && *abort) return;
// apply shading based on height changes
qreal mul = 0.25 / r.scale;
qreal lout = 0.65;
qreal lmin = 0.5;
qreal lmax = 1.5;
qreal ymax = r.scale == 1 ? r.y : r.y << 2;
float mul = 0.25 / r.scale;
float lout = 0.65;
float lmin = 0.5;
float lmax = 1.5;
float ymax = r.scale == 1 ? r.y : r.y << 2;
for (int j = 0; j < h; j++)
{
for (int i = 0; i < w; i++)
{
int tw = w+2;
qreal t01 = height[(j+0)*tw + i+1];
qreal t10 = height[(j+1)*tw + i+0];
qreal t11 = height[(j+1)*tw + i+1];
qreal t12 = height[(j+1)*tw + i+2];
qreal t21 = height[(j+2)*tw + i+1];
qreal d0 = t01 + t10;
qreal d1 = t12 + t21;
qreal light = 1.0;
float t01 = height[(j+0)*tw + i+1];
float t10 = height[(j+1)*tw + i+0];
float t11 = height[(j+1)*tw + i+1];
float t12 = height[(j+1)*tw + i+2];
float t21 = height[(j+2)*tw + i+1];
float d0 = t01 + t10;
float d1 = t12 + t21;
float light = 1.0;
uchar *col = rgb + 3*(j*w + i);
if (mode == HV_GRAYSCALE)
{
@ -336,7 +347,7 @@ void applyHeightShading(unsigned char *rgb, Range r,
col[0] = 0xff; col[1] = col[2] = 0;
continue;
}
qreal v = t11;
float v = t11;
uchar c = (v <= 0) ? 0 : (v > 0xff) ? 0xff : (uchar)(v);
col[0] = col[1] = col[2] = c;
continue;
@ -348,14 +359,14 @@ void applyHeightShading(unsigned char *rgb, Range r,
if (light > lmax) light = lmax;
if (mode == HV_CONTOURS || mode == HV_CONTOURS_SHADING)
{
qreal spacing = 16.0;
qreal tmin = std::min({t01, t10, t12, t21});
float spacing = 16.0;
float tmin = std::min({t01, t10, t12, t21});
if (floor(tmin / spacing) != floor(t11 / spacing))
light *= 0.5;
}
for (int k = 0; k < 3; k++)
{
qreal c = col[k] * light;
float c = col[k] * light;
col[k] = (c <= 0) ? 0 : (c > 0xff) ? 0xff : (uchar)(c);
}
}
@ -401,7 +412,7 @@ void Quad::run()
int nptype = -1;
if (g->mc >= MC_1_18) nptype = g->bn.nptype;
if (g->mc <= MC_B1_7) nptype = g->bnb.nptype;
if (dim == 0 && nptype >= 0)
if (dim == DIM_OVERWORLD && nptype >= 0)
{ // climate parameter
const int *extremes = getBiomeParaExtremes(g->mc);
int cmin = extremes[nptype*2 + 0];
@ -420,7 +431,7 @@ void Quad::run()
g_mutex.unlock();
biomesToImage(rgb, g_biomeColors, biomes, w, h, 1, 1);
if (lopt.mode == LOPT_HEIGHT_4 && dim == 0)
if (lopt.mode == LOPT_HEIGHT_4)
{
int stepbits = (hd ? 0 : 2);
applyHeightShading(rgb, r, g, sn, stepbits, lopt.disp[lopt.mode], false, isdel);
@ -782,7 +793,7 @@ void QWorld::setDim(int dim, LayerOpt lopt)
this->dim = dim;
this->lopt = lopt;
applySeed(&g, dim, wi.seed);
initSurfaceNoise(&sn, DIM_OVERWORLD, g.seed);
initSurfaceNoise(&sn, dim, g.seed);
int pixs, lcnt;
if (g.mc >= MC_1_18 || dim != DIM_OVERWORLD)
@ -862,15 +873,17 @@ QString QWorld::getBiomeName(Pos p)
return c + "=" + QString::number(id);
}
QString ret = getBiomeDisplay(wi.mc, id);
if (lopt.mode == LOPT_HEIGHT_4 && dim == DIM_OVERWORLD)
ret = QString::asprintf("Y~%d ", estimateSurface(p)) + ret;
if (lopt.mode == LOPT_HEIGHT_4)
{
int y = estimateSurface(p);
if (y > 0)
ret = QString::asprintf("Y~%d ", y) + ret;
}
return ret;
}
int QWorld::estimateSurface(Pos p)
{
if (dim != DIM_OVERWORLD)
return 64;
float y = 0;
mapApproxHeight(&y, 0, &g, &sn, p.x>>2, p.z>>2, 1, 1);
return (int) floor(y);