Changes for MC 1.21

* added 1.21.2 to versions, as well as an experimental 1.21.3 for the pale_garden biome
* added trial chamber finder
* added inner linked gateways
* fixed inaccurate End generation at large distances from 0,0
* fixed copying seeds from matching seed list (#302)
* fixed non-persistent search progress and results list in headless mode (#310)
This commit is contained in:
Cubitect 2024-10-07 14:29:13 +02:00
parent abcbfbd0bf
commit a5376bfd72
19 changed files with 176 additions and 51 deletions

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

View File

@ -141,6 +141,7 @@ HEADERS += \
$$CUPATH/finders.h \
$$CUPATH/generator.h \
$$CUPATH/layers.h \
$$CUPATH/biomes.h \
$$CUPATH/quadbase.h \
$$CUPATH/util.h \
$$LUAPATH/lapi.h \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

After

Width:  |  Height:  |  Size: 350 B

View File

@ -60,7 +60,7 @@ bool WorldInfo::equals(const WorldInfo& wi) const
void WorldInfo::reset()
{
mc = MC_NEWEST;
mc = MC_DEFAULT;
large = false;
seed = 0;
y = 255;
@ -92,7 +92,7 @@ bool WorldInfo::read(const QString& line)
{
mc = str2mc(buf);
if (mc < 0)
mc = MC_NEWEST;
mc = MC_DEFAULT;
return true;
}
if (sscanf(p, "#Large: %d", &tmp) == 1)

View File

@ -15,6 +15,7 @@
#define PRECOMPUTE48_BUFSIZ ((int64_t)1 << 30)
enum { MC_DEFAULT = MC_1_21_2 };
struct ExtGenConfig
{
@ -77,7 +78,7 @@ enum {
LOPT_NOOCEAN_1,
LOPT_BETA_T_1,
LOPT_BETA_H_1,
LOPT_HEIGHT_4,
LOPT_HEIGHT,
LOPT_STRUCTS,
LOPT_MAX,
};

View File

@ -336,6 +336,17 @@ void FormSearchControl::setSearchRange(uint64_t smin, uint64_t smax)
searchProgressReset();
}
bool FormSearchControl::getSeed(int row, uint64_t *seed)
{
QAbstractItemModel *model = ui->results->model();
if (row < 0 || row >= model->rowCount())
return false;
QModelIndex idx = model->index(row, SeedTableModel::COL_SEED);
*seed = model->data(idx, Qt::UserRole).toULongLong();
return true;
}
void FormSearchControl::on_buttonClear_clicked()
{
model->reset();
@ -444,13 +455,9 @@ void FormSearchControl::on_buttonMore_clicked()
void FormSearchControl::onSeedSelectionChanged()
{
int row = ui->results->currentIndex().row();
if (row >= 0 && row < ui->results->model()->rowCount())
{
QModelIndex idx = ui->results->model()->index(row, SeedTableModel::COL_SEED);
uint64_t s = ui->results->model()->data(idx, Qt::UserRole).toULongLong();
uint64_t s;
if (getSeed(ui->results->currentIndex().row(), &s))
emit selectedSeedChanged(s);
}
}
void FormSearchControl::on_results_clicked(const QModelIndex &)
@ -809,10 +816,9 @@ void FormSearchControl::removeCurrent()
void FormSearchControl::copySeed()
{
QModelIndex index = ui->results->currentIndex();
if (index.isValid())
uint64_t seed;
if (getSeed(ui->results->currentIndex().row(), &seed))
{
uint64_t seed = ui->results->model()->data(index, Qt::UserRole).toULongLong();
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(QString::asprintf("%" PRId64, seed));
}
@ -824,11 +830,10 @@ void FormSearchControl::copyResults()
int n = ui->results->model()->rowCount();
for (int i = 0; i < n; i++)
{
QModelIndex idx = ui->results->model()->index(i, SeedTableModel::COL_SEED);
uint64_t seed = ui->results->model()->data(idx, Qt::UserRole).toULongLong();
text += QString::asprintf("%" PRId64 "\n", seed);
uint64_t seed;
if (getSeed(i, &seed))
text += QString::asprintf("%" PRId64 "\n", seed);
}
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(text);
}

View File

@ -114,6 +114,8 @@ public:
void setSearchMode(int mode);
bool getSeed(int row, uint64_t *seed);
signals:
void selectedSeedChanged(uint64_t seed);
void searchStatusChanged(bool running);

View File

@ -5,8 +5,11 @@
#include <QApplication>
#include <QDateTime>
#include <QFileInfo>
#include <QStandardPaths>
#include <stdio.h>
#if defined(_WIN32)
#include <windows.h>
short get_term_width()
@ -34,19 +37,20 @@ static QTextStream& qOut()
return out;
}
Headless::Headless(QString sessionpath, QString resultspath, QObject *parent)
Headless::Headless(QString sessionpath, QString resultspath, bool reset, QObject *parent)
: QThread(parent)
, sthread(nullptr)
, sessionpath(sessionpath)
, resultfile(resultspath)
, resultstream(stdout)
, progressfp()
{
sthread.isdone = true;
QSettings settings(APP_STRING, APP_STRING);
g_extgen.load(settings);
if (!loadSession(sessionpath))
if (!loadSession(sessionpath, reset))
return;
if (!sthread.set(nullptr, session))
@ -82,7 +86,7 @@ static bool load_seeds(std::vector<uint64_t>& seeds, QString path)
return false;
}
bool Headless::loadSession(QString sessionpath)
bool Headless::loadSession(QString sessionpath, bool reset)
{
qOut() << "Loading session: \"" << sessionpath << "\"\n";
qOut().flush();
@ -100,6 +104,11 @@ bool Headless::loadSession(QString sessionpath)
return false;
}
if (reset)
session.sc.startseed = 0;
else
results = session.slist;
if (session.cv.empty())
{
warn(nullptr, "Session defines no search constraints.");
@ -149,15 +158,32 @@ void Headless::run()
session.writeHeader(resultstream);
resultstream.flush();
sthread.startSearch();
elapsed.start();
if (resultfile.isOpen())
{
// open a separate write channel to the same result file and
// reserve space for a progress field after the header
QByteArray path = QFileInfo(resultfile).absoluteFilePath().toLocal8Bit();
progressfp = fopen(path.data(), "rb+");
if (progressfp)
{
fseek(progressfp, resultfile.size(), SEEK_SET);
resultstream << QString::asprintf("#Progress: %20" PRId64 "\n", session.sc.startseed);
resultstream.flush();
}
for (uint64_t s : results)
{
resultstream << (int64_t) s << "\n";
resultstream.flush();
}
qOut() << "\n\n\n\n\n\n\n";
qOut().flush();
timer.start(250);
}
sthread.startSearch();
elapsed.start();
}
void Headless::searchResult(uint64_t seed)
@ -174,6 +200,11 @@ void Headless::searchFinish(bool done)
timer.stop();
progressTimeout();
}
if (progressfp)
{
fclose(progressfp);
progressfp = NULL;
}
if (done)
qOut() << "Search done!\n";
qOut() << "Stopping event loop.\n";
@ -188,6 +219,13 @@ void Headless::progressTimeout()
qreal min, avg, max;
sthread.getProgress(&status, &prog, &end, &seed, &min, &avg, &max);
if (progressfp)
{
long pos = ftell(progressfp);
fprintf(progressfp, "#Progress: %20" PRId64 "\n", seed);
fseek(progressfp, pos, SEEK_SET);
}
short width = get_term_width();
if (width <= 24)
return;

View File

@ -12,10 +12,10 @@ class Headless : public QThread
Q_OBJECT
public:
Headless(QString sessionpath, QString resultspath, QObject *parent = 0);
Headless(QString sessionpath, QString resultspath, bool reset, QObject *parent = 0);
virtual ~Headless();
bool loadSession(QString sessionpath);
bool loadSession(QString sessionpath, bool reset);
public slots:
void run();
@ -33,6 +33,7 @@ public:
std::vector<uint64_t> results;
QFile resultfile;
QTextStream resultstream;
FILE *progressfp;
QTimer timer;
QElapsedTimer elapsed;
};

View File

@ -20,7 +20,7 @@ bool getLayerOptionInfo(LayerOptInfo *info, int mode, int disp, WorldInfo wi)
if (disp == 3) txt = "1:64";
if (disp == 4) txt = "1:256";
break;
case LOPT_HEIGHT_4:
case LOPT_HEIGHT:
if (disp == 0) txt = QApplication::translate("LayerDialog", "Grayscale");
if (disp == 1) txt = QApplication::translate("LayerDialog", "Shaded biome map");
if (disp == 2) txt = QApplication::translate("LayerDialog", "Contours on biomes");
@ -113,7 +113,7 @@ LayerDialog::LayerDialog(QWidget *parent, WorldInfo wi)
radio[LOPT_NOOCEAN_1] = ui->radioNoOcean;
radio[LOPT_BETA_T_1] = ui->radioBetaT;
radio[LOPT_BETA_H_1] = ui->radioBetaH;
radio[LOPT_HEIGHT_4] = ui->radioHeight;
radio[LOPT_HEIGHT] = ui->radioHeight;
radio[LOPT_STRUCTS] = ui->radioStruct;
combo[LOPT_BIOMES] = ui->comboBiomes;
@ -122,7 +122,7 @@ LayerDialog::LayerDialog(QWidget *parent, WorldInfo wi)
combo[LOPT_NOISE_C_4] = ui->comboNoiseC;
combo[LOPT_NOISE_E_4] = ui->comboNoiseE;
combo[LOPT_NOISE_W_4] = ui->comboNoiseW;
combo[LOPT_HEIGHT_4] = ui->comboHeight;
combo[LOPT_HEIGHT] = ui->comboHeight;
for (int i = 0; i < LOPT_MAX; i++)
{

View File

@ -34,6 +34,7 @@ int main(int argc, char *argv[])
bool version = false;
bool nogui = false;
bool clear = false;
bool reset = false;
bool usage = false;
QString sessionpath;
@ -45,6 +46,8 @@ int main(int argc, char *argv[])
version = true;
else if (strcmp(argv[i], "--nogui") == 0)
nogui = true;
else if (strcmp(argv[i], "--reset") == 0)
clear = true;
else if (strcmp(argv[i], "--reset-all") == 0)
reset = true;
else if (strncmp(argv[i], "--session=", 10) == 0)
@ -67,6 +70,7 @@ int main(int argc, char *argv[])
" --help Display this help and exit.\n"
" --version Output version information and exit.\n"
" --nogui Run in headless search mode.\n"
" --reset Discard results and reset starting seed.\n"
" --reset-all Clear settings and remove all session data.\n"
" --session=file Open this session file.\n"
" --out=file Write matching seeds to this file while searching.\n"
@ -105,7 +109,7 @@ int main(int argc, char *argv[])
if (nogui)
{
QCoreApplication app(argc, argv);
Headless headless(sessionpath, resultspath, &app);
Headless headless(sessionpath, resultspath, clear, &app);
QObject::connect(&headless, SIGNAL(finished()), &app, SLOT(quit()));
QTimer::singleShot(0, &headless, SLOT(run()));

View File

@ -111,7 +111,7 @@ MainWindow::MainWindow(QString sessionpath, QString resultspath, QWidget *parent
laction[LOPT_NOOCEAN_1] = ui->actionNoOceans;
laction[LOPT_BETA_T_1] = ui->actionBetaTemperature;
laction[LOPT_BETA_H_1] = ui->actionBetaHumidity;
laction[LOPT_HEIGHT_4] = ui->actionHeight;
laction[LOPT_HEIGHT] = ui->actionHeight;
laction[LOPT_STRUCTS] = ui->actionStructures;
QActionGroup *grp = new QActionGroup(this);
@ -365,7 +365,7 @@ bool MainWindow::getSeed(WorldInfo *wi, bool applyrand)
{
if (applyrand)
qDebug() << "Unknown MC version: " << mcs.c_str();
wi->mc = MC_NEWEST;
wi->mc = MC_DEFAULT;
ok = false;
}
@ -697,13 +697,13 @@ void MainWindow::setMCList(bool experimental)
if (ui->comboBoxMC->count())
getSeed(&wi, false);
else
wi.mc = MC_NEWEST;
wi.mc = MC_DEFAULT;
QStringList mclist;
for (int mc = MC_NEWEST; mc > MC_UNDEF; mc--)
{
if (!experimental && mc != wi.mc)
{
if (mc <= MC_1_0 || mc == MC_1_16_1 || mc == MC_1_19_2)
if (mc <= MC_1_0 || mc == MC_1_16_1 || mc == MC_1_19_2 || mc == MC_1_21_3)
continue;
}
const char *mcs = mc2str(mc);

View File

@ -502,7 +502,7 @@ void MapView::paintEvent(QPaintEvent *)
QPoint cur = mapFromGlobal(QCursor::pos());
qreal bx = (cur.x() - width()/2.0) / blocks2pix + fx;
qreal bz = (cur.y() - height()/2.0) / blocks2pix + fz;
Pos p = {(int)clampimax(bx), (int)clampimax(bz)};
Pos p = {(int)clampimax(floor(bx)), (int)clampimax(floor(bz))};
overlay->pos = p;
overlay->bname = world->getBiomeName(p);

View File

@ -1275,6 +1275,7 @@ L_qm_any:
case F_PORTALN:
case F_ANCIENT_CITY:
case F_TRAILS:
case F_CHAMBERS:
case F_FORTRESS:
case F_BASTION:

View File

@ -91,6 +91,7 @@ enum
F_TRAILS,
F_BIOME_SAMPLE,
F_NOISE_SAMPLE,
F_CHAMBERS,
// new filters should be added here at the end to keep some downwards compatibility
FILTER_MAX,
};
@ -504,6 +505,13 @@ static const struct FilterList : private FilterInfo
""
};
list[F_CHAMBERS] = FilterInfo{
CAT_STRUCT, 1, LOC_RAD, Trial_Chambers, 1, BR_CLUST, MC_1_21, MC_NEWEST, 0, 0, disp++,
"chambers",
QT_TRANSLATE_NOOP("Filter", "Trial chambers"),
""
};
list[F_PORTAL] = FilterInfo{
CAT_STRUCT, 0, LOC_RAD, Ruined_Portal, 1, BR_CLUST, MC_1_16_1, MC_NEWEST, 0, 0, disp++,
"portal",

View File

@ -156,6 +156,8 @@ QString getBiomeDisplay(int mc, int id)
case mangrove_swamp: return QApplication::translate("Biome", "Mangrove Swamp");
// 1.20
case cherry_grove: return QApplication::translate("Biome", "Cherry Grove");
// 1.21.3 (Winter Drop Version TBA)
case pale_garden: return QApplication::translate("Biome", "Pale Garden");
}
const char *name = biome2str(mc, id);
return name ? name : "";

View File

@ -136,7 +136,7 @@ QStringList VarPos::detail() const
Quad::Quad(const Level* l, int64_t i, int64_t j)
: wi(l->wi),dim(l->dim),lopt(l->lopt),g(&l->g),sn(&l->sn),hd(l->hd),scale(l->scale)
: wi(l->wi),dim(l->dim),lopt(l->lopt),g(&l->g),sn(&l->sn),highres(l->highres),scale(l->scale)
, ti(i),tj(j),blocks(l->blocks),pixs(l->pixs),sopt(l->sopt)
, biomes(),rgb(),img(),spos()
{
@ -267,7 +267,12 @@ void applyHeightShading(unsigned char *rgb, Range r,
pw += 2*bd; ph += 2*bd;
}
std::vector<float> buf(pw * ph);
if (ps == 0)
if (ps == 0 && r.scale <= 8 && g->dim == DIM_END)
{
mapEndSurfaceHeight(&buf[0], &g->en, sn, px, pz, pw, ph, r.scale, 0);
mapEndIslandHeight(&buf[0], &g->en, g->seed, px, pz, pw, ph, r.scale);
}
else if (ps == 0 && r.scale == 4)
{
mapApproxHeight(&buf[0], 0, g, sn, px, pz, pw, ph);
}
@ -286,6 +291,7 @@ void applyHeightShading(unsigned char *rgb, Range r,
}
}
}
// interpolate height
std::vector<float> height((w+2) * (h+2));
for (int j = 0; j < h+2; j++)
@ -382,7 +388,8 @@ void Quad::run()
if (pixs > 0)
{
if (lopt.mode == LOPT_STRUCTS && dim == DIM_OVERWORLD)
if ((lopt.mode == LOPT_STRUCTS && dim == DIM_OVERWORLD) ||
(g->mc <= MC_1_17 && scale > 256 && dim == DIM_OVERWORLD))
{
img = new QImage();
done = true;
@ -431,9 +438,13 @@ void Quad::run()
g_mutex.unlock();
biomesToImage(rgb, g_biomeColors, biomes, w, h, 1, 1);
if (lopt.mode == LOPT_HEIGHT_4)
if (lopt.mode == LOPT_HEIGHT)
{
int stepbits = (hd ? 0 : 2);
int stepbits = 0; // interpolated_step = (1 << stepbits)
if (scale > 16)
{
stepbits = 1;
}
applyHeightShading(rgb, r, g, sn, stepbits, lopt.disp[lopt.mode], false, isdel);
}
}
@ -459,7 +470,7 @@ void Quad::run()
Level::Level()
: cells(),g(),sn(),entry(),lopt(),wi(),dim()
, tx(),tz(),tw(),th()
, hd(),scale(),blocks(),pixs()
, highres(),scale(),blocks(),pixs()
, sopt()
{
}
@ -480,7 +491,7 @@ void Level::init4map(QWorld *w, int pix, int layerscale)
tx = tz = tw = th = 0;
hd = (layerscale == 1);
highres = (layerscale == 1);
scale = layerscale;
pixs = pix;
blocks = pix * layerscale;
@ -504,7 +515,7 @@ void Level::init4map(QWorld *w, int pix, int layerscale)
case LOPT_NOISE_E_4:
case LOPT_NOISE_D_4:
case LOPT_NOISE_W_4:
case LOPT_HEIGHT_4:
//case LOPT_HEIGHT_4:
case LOPT_RIVER_4:
case LOPT_STRUCTS:
optlscale = 4;
@ -796,7 +807,13 @@ void QWorld::setDim(int dim, LayerOpt lopt)
initSurfaceNoise(&sn, dim, g.seed);
int pixs, lcnt;
if (g.mc >= MC_1_18 || dim != DIM_OVERWORLD)
if (lopt.mode == LOPT_HEIGHT)
{
pixs = 32;
lcnt = 6;
qual = 4.0;
}
else if (g.mc > MC_1_17 || dim != DIM_OVERWORLD)
{
pixs = 128;
lcnt = 6;
@ -873,7 +890,7 @@ QString QWorld::getBiomeName(Pos p)
return c + "=" + QString::number(id);
}
QString ret = getBiomeDisplay(wi.mc, id);
if (lopt.mode == LOPT_HEIGHT_4)
if (lopt.mode == LOPT_HEIGHT)
{
int y = estimateSurface(p);
if (y > 0)
@ -885,7 +902,15 @@ QString QWorld::getBiomeName(Pos p)
int QWorld::estimateSurface(Pos p)
{
float y = 0;
mapApproxHeight(&y, 0, &g, &sn, p.x>>2, p.z>>2, 1, 1);
if (g.dim == DIM_END)
{ // use end surface generator for 1:1 scale
mapEndSurfaceHeight(&y, &g.en, &sn, p.x, p.z, 1, 1, 1, 0);
mapEndIslandHeight(&y, &g.en, g.seed, p.x, p.z, 1, 1, 1);
}
else
{
mapApproxHeight(&y, 0, &g, &sn, p.x>>2, p.z>>2, 1, 1);
}
return (int) floor(y);
}
@ -1094,9 +1119,9 @@ struct SpawnStronghold : public Scheduled
}
};
static bool draw_grid_rec(QPainter& painter, QRect &rec, qreal pix, int64_t x, int64_t z)
static bool draw_grid_rec(QPainter& painter, QColor col, QRect &rec, qreal pix, int64_t x, int64_t z)
{
painter.setPen(QPen(QColor(0, 0, 0, 96), 1));
painter.setPen(QPen(col, 1));
painter.drawRect(rec);
if (pix < 50)
return false;
@ -1142,6 +1167,7 @@ void QWorld::draw(QPainter& painter, int vw, int vh, qreal focusx, qreal focusz,
smallfont.setPointSize(oldfont.pointSize() - 2);
painter.setFont(smallfont);
QColor gridcol = lopt.mode == LOPT_HEIGHT ? QColor(192, 0, 0, 96) : QColor(0, 0, 0, 96);
int gridpix = 128;
// 128px is approximately the size of:
//gridpix = painter.fontMetrics().boundingRect("-30000000,-30000000").width();
@ -1166,7 +1192,7 @@ void QWorld::draw(QPainter& painter, int vw, int vh, qreal focusx, qreal focusz,
if (sshow[D_GRID] && !gridspacing)
{
draw_grid_rec(painter, rec, ps, q->ti*q->blocks, q->tj*q->blocks);
draw_grid_rec(painter, gridcol, rec, ps, q->ti*q->blocks, q->tj*q->blocks);
}
}
}
@ -1190,7 +1216,7 @@ void QWorld::draw(QPainter& painter, int vw, int vh, qreal focusx, qreal focusz,
qreal px = vw/2.0 + (x+i) * ps - focusx * blocks2pix;
qreal pz = vh/2.0 + (z+j) * ps - focusz * blocks2pix;
QRect rec(px, pz, ps, ps);
draw_grid_rec(painter, rec, ps, (x+i)*gs, (z+j)*gs);
draw_grid_rec(painter, gridcol, rec, ps, (x+i)*gs, (z+j)*gs);
}
}
break;
@ -1264,6 +1290,41 @@ void QWorld::draw(QPainter& painter, int vw, int vh, qreal focusx, qreal focusz,
}
}
if (showBB && sshow[D_GATEWAY] && dim == DIM_END && g.mc > MC_1_12)
{
if (endgates.empty())
{
endgates.resize(40);
getFixedEndGateways(g.mc, g.seed, &endgates[0]);
for (int i = 0; i < 20; i++)
endgates[20 + i] = getLinkedGatewayPos(&g.en, &sn, g.seed, endgates[i]);
}
for (int i = 0; i < 20; i++)
{
qreal xsrc = vw/2.0 + (0.5 + endgates[i].x - focusx) * blocks2pix;
qreal ysrc = vh/2.0 + (0.5 + endgates[i].z - focusz) * blocks2pix;
qreal xdst = vw/2.0 + (0.5 + endgates[i+20].x - focusx) * blocks2pix;
qreal ydst = vh/2.0 + (0.5 + endgates[i+20].z - focusz) * blocks2pix;
QPen pen = painter.pen();
painter.setPen(QPen(QColor(192, 0, 0, 160), i == 0 ? 1.5 : 0.5));
painter.drawLine(QPointF(xsrc,ysrc), QPointF(xdst,ydst));
painter.setPen(pen);
if (blocks2pix >= 1.0 && abs(xsrc) < vw && abs(ysrc) < vh)
{
QString s = QString::number(i+1);
QRect rec = painter.fontMetrics().boundingRect(s);
qreal dx = xsrc - xdst;
qreal dy = ysrc - ydst;
qreal df = rec.height() / sqrt(dx*dx + dy*dy);
rec.moveCenter(QPoint(xsrc + dx * df, ysrc + dy * df));
painter.drawText(rec, s);
}
}
}
for (Shape& s : shapes)
{
if (s.dim != DIM_UNDEF && s.dim != dim)

View File

@ -64,7 +64,7 @@ struct Quad : public Scheduled
LayerOpt lopt;
const Generator *g;
const SurfaceNoise *sn;
int hd;
int highres;
int scale;
int ti, tj;
int blocks;
@ -101,7 +101,7 @@ struct Level
WorldInfo wi;
int dim;
int tx, tz, tw, th;
int hd;
int highres;
int scale;
int blocks;
int pixs;
@ -204,6 +204,7 @@ public:
QAtomicPointer<Pos> spawn;
QAtomicPointer<PosElement> strongholds;
QAtomicPointer<QVector<QuadInfo>> qsinfo;
QVector<Pos> endgates;
// isdel is a flag for the worker thread to stop
std::atomic_bool isdel;