[Tutorial] Fix review blockers: null VDS guard, overlay QPointer, bounded fill, upgrade seeding

This commit is contained in:
Lukas Brübach 2026-10-11 15:10:43 +02:00
parent 5c024c6338
commit 86f6098d43
6 changed files with 32 additions and 5 deletions

View file

@ -135,7 +135,10 @@ TutorialSequence DeckViewContainer::generateTutorialSequence()
{this, tr("There are multiple ways to select a deck:\n\n- From a local file"
"\n- From the contents of your clipboard\nFrom an external online service")});
visualDeckStorageWidget->generateTutorialSequence(deckViewContainerSequence);
// The VDS widget only exists when "show visual deck storage in game" is on.
if (visualDeckStorageWidget) {
visualDeckStorageWidget->generateTutorialSequence(deckViewContainerSequence);
}
return deckViewContainerSequence;
}

View file

@ -98,7 +98,7 @@ private:
QWidget *tutorializedWidget;
TutorialOverlay *tutorialOverlay;
QVector<TutorialSequence> sequences;
QList<TutorialSequence> sequences;
bool tutorialCompleted = false;

View file

@ -1,6 +1,7 @@
#ifndef TUTORIAL_OVERLAY_H
#define TUTORIAL_OVERLAY_H
#include <QPointer>
#include <QWidget>
class QFrame;
@ -49,7 +50,9 @@ private:
void recomputeLayout();
QRect computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const;
QWidget *targetWidget = nullptr;
// QPointer: the target can be deleted while the overlay lives on (e.g. toggling
// the in-game VDS setting deleteLater()s it), so never hold a raw pointer across steps.
QPointer<QWidget> targetWidget;
QFrame *controlBar = nullptr;
QLabel *titleLabel = nullptr;
QPushButton *nextButton = nullptr;

View file

@ -220,6 +220,11 @@ void TabGame::finishTutorialInitialization()
tutorialInitialized = true;
}
// No sequence building (and no widget access) once the tutorial is done.
if (SettingsCache::instance().userInterface().getTutorialCompleted()) {
return;
}
auto deckViewSequence = deckViewContainers.first()->generateTutorialSequence();
tutorialController->addSequence(deckViewSequence);
@ -376,7 +381,7 @@ void TabGame::finishTutorialInitialization()
tutorialController->addSequence(gamePlaySequence);
if (tutorialStarted || SettingsCache::instance().userInterface().getTutorialCompleted()) {
if (tutorialStarted) {
return;
}
// The tab may have been shown before the local player joined; start now that

View file

@ -508,8 +508,14 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
organizationStep.text = "Let's look at how cards are organized and displayed now.\n\nWe'll add some random cards "
"from the database to your deck, so you can see it in action properly.";
organizationStep.onExit = [this]() {
while (deckListModel->getDeckList()->getCardList().size() < 60) {
// getCardList() counts distinct card nodes, not copies, so once every random pick only bumps an
// existing node's count the size never grows. With fewer than ~60 distinct cards in the database
// this would loop forever, so bound the attempts to always terminate.
static constexpr int MaxFillAttempts = 600;
int attempts = 0;
while (deckListModel->getDeckList()->getCardList().size() < 60 && attempts < MaxFillAttempts) {
deckListModel->addCard(CardDatabaseManager::query()->getRandomCard(), DECK_ZONE_MAIN);
++attempts;
}
};

View file

@ -5,6 +5,8 @@
#include "libcockatrice/utility/playmat_params.h"
#include <QChar>
#include <QDir>
#include <QFileInfo>
#include <QList>
#include <QVariant>
#include <QtGlobal>
@ -248,6 +250,14 @@ QStringList InterfaceSettings::getUserListExpandedSections() const
bool InterfaceSettings::getTutorialCompleted() const
{
// Upgrading users have settings files but no tutorial key yet: treat the tutorial as seen so they are
// not overlaid on upgrade. Genuinely new installs have no settings files at all, so they still get it.
if (!getSettings().contains("interface/tutorialCompleted")) {
QDir settingsDir = QFileInfo(settingPath).dir();
if (!settingsDir.entryList({"*.ini"}, QDir::Files).isEmpty()) {
return true;
}
}
return getValue("tutorialCompleted", QString(), QString(), false).toBool();
}